public class ProgressDialogBuilder extends AlertDialog.Builder {

    private TextView mMessageView;

    public ProgressDialogBuilder(Context context) {
        super(context);
        init();
    }

    public ProgressDialogBuilder(Context context, int theme) {
        super(context, theme);
        init();
    }

    private void init() {
        @SuppressLint("InflateParams")
        View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_progress, null);
        mMessageView = (TextView) view.findViewById(R.id.message);
        super.setView(view);
    }

    @Override
    public ProgressDialogBuilder setView(int layoutResId) {
        throw new IllegalStateException("Can't setView");
    }

    @Override
    public ProgressDialogBuilder setView(View view) {
        throw new IllegalStateException("Can't setView");
    }

    @Override
    public ProgressDialogBuilder setView(View view, int viewSpacingLeft,
            int viewSpacingTop,int viewSpacingRight, int viewSpacingBottom) {
        throw new IllegalStateException("Can't setView");
    }

    @Override
    public ProgressDialogBuilder setMessage(CharSequence message) {
        mMessageView.setText(message);
        return this;
    }

    @Override
    public ProgressDialogBuilder setMessage(int messageId) {
        mMessageView.setText(messageId);
        return this;
    }
} public final class Constants {

    public static final int MESSENGER_ID_CHANGE_THEME;
    public static final int MESSENGER_ID_UPDATE_RECORD;
    public static final int MESSENGER_ID_REPLY;
    public static final int MESSENGER_ID_CREATE_POST;
    public static final int MESSENGER_ID_FAST_SCROLLER;

    static {
        Messenger messenger = Messenger.getInstance();
        MESSENGER_ID_CHANGE_THEME = messenger.newId();
        MESSENGER_ID_UPDATE_RECORD = messenger.newId();
        MESSENGER_ID_REPLY = messenger.newId();
        MESSENGER_ID_CREATE_POST = messenger.newId();
        MESSENGER_ID_FAST_SCROLLER = messenger.newId();
    }
} public final class NMBApplication extends Application
        implements Thread.UncaughtExceptionHandler, Messenger.Receiver, Runnable {

    private static final String TAG = NMBApplication.class.getSimpleName();

    private static final boolean LOG_NATIVE_MEMORY = false;

    private static final String AC_CDN_PATH_FILENAME = "ac_cdn_path";

    private Thread.UncaughtExceptionHandler mDefaultHandler;

    private SimpleCookieStore mSimpleCookieStore;
    private NMBClient mNMBClient;
    private Conaco<ImageWrapper> mConaco;
    private ImageWrapperHelper mImageWrapperHelper;
    private OkHttpClient mOkHttpClient;

    private boolean mConnectedWifi;

    private List<WeakReference<Activity>> mActivities = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();

        // Prepare to crash
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);

        NMBAppConfig.initialize(this);
        File logFile = NMBAppConfig.getFileInAppDir("nimingban.log");
        if (logFile != null) {
            Say.initSayFile(logFile);
        }
        Settings.initialize(this);
        DB.initialize(this);
        HttpCookieDB.initialize(this);
        ReadableTime.initialize(this);
        BitmapUtils.initialize(this);
        ResImageGetter.initialize(this);
        Emoji.initialize(this);

        // Remove temp file
        FileUtils.deleteContent(NMBAppConfig.getTempDir());

        // Check network state
        updateNetworkState(this);
        registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateNetworkState(context);
            }
        }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));

        // Theme
        setTheme(Settings.getDarkTheme() ? R.style.AppTheme_Dark : R.style.AppTheme);
        Messenger.getInstance().register(Constants.MESSENGER_ID_CHANGE_THEME, this);

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                mActivities.add(new WeakReference<>(activity));
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
                Iterator<WeakReference<Activity>> iterator = mActivities.iterator();
                while (iterator.hasNext()) {
                    WeakReference<Activity> reference = iterator.next();
                    Activity a = reference.get();
                    // Remove current activity and null
                    if (a == null || a == activity) {
                        iterator.remove();
                    }
                }
            }
            @Override
            public void onActivityStarted(Activity activity) {}
            @Override
            public void onActivityResumed(Activity activity) {}
            @Override
            public void onActivityPaused(Activity activity) {}
            @Override
            public void onActivityStopped(Activity activity) {}
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
        });

        try {
            update();
        } catch (PackageManager.NameNotFoundException e) {
            // Ignore
        }

        if (LOG_NATIVE_MEMORY) {
            SimpleHandler.getInstance().post(this);
        }

        start();
    }

    private void start() {
        updateACCdnPath();
        updateACForums();
        updateACHost();

        long lastForumAging = Settings.getLastForumAging();
        long time = System.currentTimeMillis();
        if (time - lastForumAging > 24 * 60 * 60 * 1000) { // 24 hr * 60 min * 60 sec * 1000 milli
            ForumAutoSortingUtils.ageACForumFrequency();
            Settings.setLastForumAging(time);
        }
    }

    private void readACCdnPathFromFile() {
        File file = new File(getFilesDir(), AC_CDN_PATH_FILENAME);
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            String str = IOUtils.readString(is, "utf-8");
            List<ACCdnPath> list = JSON.parseArray(str, ACCdnPath.class);
            ACSite.getInstance().setCdnPath(list);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    private void writeACCdnPathToFile(List<ACCdnPath> cdnPaths) {
        File file = new File(getFilesDir(), AC_CDN_PATH_FILENAME);
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            os.write(JSON.toJSONString(cdnPaths).getBytes("utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(os);
        }
    }

    private void updateACCdnPath() {
        // First read cdn path from file
        readACCdnPathFromFile();

        NMBRequest request = new NMBRequest();
        request.setSite(ACSite.getInstance());
        request.setMethod(NMBClient.METHOD_GET_CDN_PATH);
        request.setCallback(new NMBClient.Callback<List<ACCdnPath>>(){
            @Override
            public void onSuccess(List<ACCdnPath> result) {
                ACSite.getInstance().setCdnPath(result);
                writeACCdnPathToFile(result);
            }

            @Override
            public void onFailure(Exception e) {
                e.printStackTrace();
            }

            @Override
            public void onCancel() {
            }
        });
        getNMBClient(this).execute(request);
    }

    private void updateACForums() {
        NMBRequest request = new NMBRequest();
        request.setSite(ACSite.getInstance());
        request.setMethod(NMBClient.METHOD_GET_FORUM_LIST);
        request.setCallback(new NMBClient.Callback<List<ACForumGroup>>(){
            @Override
            public void onSuccess(List<ACForumGroup> result) {
                List<ACForum> list = new LinkedList<>();
                for (ACForumGroup forumGroup : result) {
                    list.addAll(forumGroup.forums);
                }
                DB.setACForums(list);
            }
            @Override
            public void onFailure(Exception e) { }
            @Override
            public void onCancel() { }
        });
        getNMBClient(this).execute(request);
    }

    @SuppressLint("StaticFieldLeak")
    private void updateACHost() {
        getNMBClient(this).execute(new AsyncTask<Object, Object, String>() {
            @Override
            protected String doInBackground(Object... objects) {
                OkHttpClient client = getOkHttpClient(NMBApplication.this);
                Request request = new Request.Builder().url("http://adnmb.com").build();
                Call call = client.newCall(request);
                try {
                    Response response = call.execute();
                    String url = response.request().url().toString();
                    response.close();

                    // Find third '/', make url like http://adnmb.com
                    int count = 0;
                    for (int i = 0, n = url.length(); i < n; i++) {
                        if (url.charAt(i) == '/') {
                            count++;
                        }
                        if (count == 3) {
                            url = url.substring(0, i);
                            break;
                        }
                    }

                    return url;
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            protected void onPostExecute(String host) {
                Log.d(TAG, "ac host = " + host);
                Settings.putAcHost(host);
            }
        });
    }

    private void update() throws PackageManager.NameNotFoundException {
        int oldVersionCode = Settings.getVersionCode();
        PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES);
        Settings.putVersionCode(pi.versionCode);

        if (oldVersionCode < 6) {
            updateCookies(this);
        }

        if (oldVersionCode < 14) {
            Settings.putGuideListActivity(true);
        }

        if (oldVersionCode < 42) {
            Settings.putSetAnalysis(false);
            Settings.putAnalysis(false);
        }

        // Fix cookie lost when save to file in 1.2.29 and below
        if (oldVersionCode < 44) {
            NMBApplication.getSimpleCookieStore(this).fixLostCookiePath();
        }
    }

    public static void updateCookies(Context context) {
        SimpleCookieStore cookieStore = NMBApplication.getSimpleCookieStore(context);

        URL url = ACSite.getInstance().getSiteUrl();
        HttpCookieWithId hcwi = cookieStore.getCookie(url, "userId");
        if (hcwi != null) {
            HttpCookie oldCookie = hcwi.httpCookie;
            cookieStore.remove(url, oldCookie);

            HttpCookie newCookie = new HttpCookie("userhash", oldCookie.getValue());
            newCookie.setComment(oldCookie.getComment());
            newCookie.setCommentURL(oldCookie.getCommentURL());
            newCookie.setDiscard(oldCookie.getDiscard());
            newCookie.setDomain(oldCookie.getDomain());
            newCookie.setMaxAge(oldCookie.getMaxAge());
            newCookie.setPath(oldCookie.getPath());
            newCookie.setPortlist(oldCookie.getPortlist());
            newCookie.setSecure(oldCookie.getSecure());
            newCookie.setVersion(oldCookie.getVersion());

            cookieStore.add(url, newCookie);
        }
    }

    public void bringActivitiesToForeground() {
        int i = mActivities.size();
        while (--i >= 0) {
            Activity activity = mActivities.get(i).get();
            if (activity != null && !"com.hippo.nimingban.wxapi.WXEntryActivity".equals(activity.getClass().getName())) {
                Intent intent = new Intent(activity, BringToForegroundActivity.class);
                activity.startActivity(intent);
            }
        }
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);

        if (level ==  TRIM_MEMORY_BACKGROUND ) {
            if (mConaco != null) {
                mConaco.clearMemoryCache();
            }
        }
    }

    public static void updateNetworkState(Context context) {
        ((NMBApplication) context.getApplicationContext()).mConnectedWifi =
                NetworkUtils.isConnectedWifi(context);
    }

    public static boolean isConnectedWifi(Context context) {
        return ((NMBApplication) context.getApplicationContext()).mConnectedWifi;
    }

    public static SimpleCookieStore getSimpleCookieStore(@NonNull Context context) {
        NMBApplication application = ((NMBApplication) context.getApplicationContext());
        if (application.mSimpleCookieStore == null) {
            application.mSimpleCookieStore = new SimpleCookieStore();
        }
        return application.mSimpleCookieStore;
    }

    @NonNull
    public static NMBClient getNMBClient(@NonNull Context context) {
        NMBApplication application = ((NMBApplication) context.getApplicationContext());
        if (application.mNMBClient == null) {
            application.mNMBClient = new NMBClient(application);
        }
        return application.mNMBClient;
    }

    private static int getMemoryCacheMaxSize(Context context) {
        final ActivityManager activityManager = (ActivityManager) context.
                getSystemService(Context.ACTIVITY_SERVICE);
        return Math.min(20 * 1024 * 1024,
                Math.round(0.2f * activityManager.getMemoryClass() * 1024 * 1024));
    }

    @NonNull
    public static Conaco<ImageWrapper> getConaco(@NonNull Context context) {
        NMBApplication application = ((NMBApplication) context.getApplicationContext());
        if (application.mConaco == null) {
            Conaco.Builder<ImageWrapper> builder = new Conaco.Builder<>();
            builder.hasMemoryCache = true;
            builder.memoryCacheMaxSize = getMemoryCacheMaxSize(context);
            builder.hasDiskCache = true;
            builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
            builder.diskCacheMaxSize = 80 * 1024 * 1024; // 80MB
            builder.okHttpClient = getOkHttpClient(context);
            builder.objectHelper = getImageWrapperHelper(context);
            application.mConaco = builder.build();
        }
        return application.mConaco;
    }

    @NonNull
    public static ImageWrapperHelper getImageWrapperHelper(@NonNull Context context) {
        NMBApplication application = ((NMBApplication) context.getApplicationContext());
        if (application.mImageWrapperHelper == null) {
            application.mImageWrapperHelper = new ImageWrapperHelper();
        }
        return application.mImageWrapperHelper;
    }

    public static OkHttpClient getOkHttpClient(@NonNull Context context) {
        NMBApplication application = ((NMBApplication) context.getApplicationContext());
        if (application.mOkHttpClient == null) {
            application.mOkHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(15, TimeUnit.SECONDS)
                    .readTimeout(15, TimeUnit.SECONDS)
                    .writeTimeout(15, TimeUnit.SECONDS)
                    .dns(new NMBDns())
                    .cookieJar(new CookieDBJar(getSimpleCookieStore(context)))
                    .addInterceptor(new NMBInterceptor())
                    .build();
        }
        return application.mOkHttpClient;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            mDefaultHandler.uncaughtException(thread, ex);
        }
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(1);
    }

    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        try {
            ex.printStackTrace();
            Crash.saveCrashInfo2File(this, ex);
            return true;
        } catch (Throwable tr) {
            return false;
        }
    }

    @Override
    public void onReceive(int id, Object obj) {
        setTheme((Boolean) obj ? R.style.AppTheme_Dark : R.style.AppTheme);
    }

    @Override
    public void run() {
        Log.i(TAG, "Native " + FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));
        SimpleHandler.getInstance().postDelayed(this, 3000);
    }
} public class NMBClient {

    public static final String TAG = NMBClient.class.getSimpleName();

    public static final int METHOD_NOTICE = -6;

    public static final int METHOD_CONVERT = -5;

    public static final int METHOD_COMMON_POSTS = -4;

    public static final int METHOD_DISC = -3;

    public static final int METHOD_UPDATE = -2;

    public static final int METHOD_GET_FORUM_LIST = -1;

    public static final int METHOD_GET_COOKIE = 0;

    public static final int METHOD_GET_POST_LIST = 1;

    public static final int METHOD_GET_POST = 2;

    public static final int METHOD_GET_REFERENCE = 3;

    public static final int METHOD_REPLY = 4;

    public static final int METHOD_GET_FEED = 5;

    public static final int METHOD_ADD_FEED = 6;

    public static final int METHOD_DEL_FEED = 7;

    public static final int METHOD_CREATE_POST = 8;

    public static final int METHOD_SEARCH = 10;

    public static final int METHOD_GET_CDN_PATH = 11;

    private final ThreadPoolExecutor mRequestThreadPool;
    private final OkHttpClient mOkHttpClient;

    public NMBClient(Context context) {
        ThreadFactory threadFactory = new PriorityThreadFactory(TAG,
                android.os.Process.THREAD_PRIORITY_BACKGROUND);
        mRequestThreadPool = FixedThreadPoolExecutor.newInstance(
            3, 32, 1L, TimeUnit.SECONDS, threadFactory);
        mOkHttpClient = NMBApplication.getOkHttpClient(context);
    }

    public void execute(NMBRequest request) {
        if (!request.isCancelled()) {
            Task task = new Task(request.method, request.site, request.callback);
            task.executeOnExecutor(mRequestThreadPool, request.args);
            request.task = task;
        } else {
            request.callback.onCancel();
        }
    }

    public void execute(AsyncTask<?, ?, ?> task) {
        task.executeOnExecutor(mRequestThreadPool);
    }

    public class Task extends AsyncTask<Object, Void, Object> {

        private int mMethod;
        private Site mSite;
        private Callback mCallback;
        private Call mCall;

        private boolean mStop;

        public Task(int method, Site site, Callback callback) {
            mMethod = method;
            mSite = site;
            mCallback = callback;
        }

        public void stop() {
            if (!mStop) {
                mStop = true;

                if (mCallback != null) {
                    final Callback finalCallback = mCallback;
                    SimpleHandler.getInstance().post(new Runnable() {
                        @Override
                        public void run() {
                            finalCallback.onCancel();
                        }
                    });
                }

                Status status = getStatus();
                if (status == Status.PENDING) {
                    cancel(false);
                } else if (status == Status.RUNNING) {
                    if (mCall != null) {
                        mCall.cancel();
                    }
                }

                // Clear
                mCall = null;
                mCallback = null;
            }
        }

        private Object getCommonPosts() throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetCommonPosts(mOkHttpClient);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetCommonPosts(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getForumList() throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetForumList(mOkHttpClient);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetForumList(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getCookie() throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetCookie(mOkHttpClient);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetCookie(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getPostList(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetPostList(mOkHttpClient, (String) params[0], (Integer) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetPostList(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getPost(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetPost(mOkHttpClient, (String) params[0], (Integer) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetPost(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getReference(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetReference(mOkHttpClient, (String) params[0]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetReference(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object reply(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareReply(mOkHttpClient, (ACReplyStruct) params[0]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doReply(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }


        private Object getFeed(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetFeed(mOkHttpClient, (String) params[0], (Integer) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetFeed(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object addFeed(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareAddFeed(mOkHttpClient, (String) params[0], (String) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doAddFeed(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object delFeed(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareDelFeed(mOkHttpClient, (String) params[0], (String) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doDelFeed(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object createPost(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareCreatePost(mOkHttpClient, (ACPostStruct) params[0]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doCreatePost(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object search(Object... params) throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareSearch(mOkHttpClient, (String) params[0], (Integer) params[1]);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doSearch(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        private Object getCdnPath() throws Exception {
            switch (mSite.getId()) {
                case Site.AC:
                    Call call = ACEngine.prepareGetCdnPath(mOkHttpClient);
                    if (!mStop) {
                        mCall = call;
                        return ACEngine.doGetCdnPath(call);
                    } else {
                        throw new CancelledException();
                    }
                default:
                    return new IllegalStateException("Can't detect site " + mSite);
            }
        }

        @Override
        protected Object doInBackground(Object... params) {
            try {
                switch (mMethod) {
                    case METHOD_NOTICE: {
                        Log.d(TAG, "http://cover.acfunwiki.org/nmb-notice.json");
                        Request request = new Request.Builder().url("http://cover.acfunwiki.org/nmb-notice.json").build();
                        Call call = mOkHttpClient.newCall(request);
                        if (!mStop) {
                            mCall = call;
                            Response response = call.execute();
                            Notice notice = JSON.parseObject(response.body().string(), Notice.class);
                            if (notice == null) {
                                throw new Exception("No notice");
                            }
                            return notice;
                        } else {
                            throw new CancelledException();
                        }
                    }
                    case METHOD_CONVERT: {
                        Call call = ConvertEngine.prepareConvert(mOkHttpClient, (String) params[0], (String) params[1]);
                        if (!mStop) {
                            mCall = call;
                            return ConvertEngine.doConvert(call);
                        } else {
                            throw new CancelledException();
                        }
                    }
                    case METHOD_COMMON_POSTS:
                        return getCommonPosts();
                    case METHOD_DISC:
                        return DiscEngine.spider(mOkHttpClient, (String) params[0], (String) params[1]);
                    case METHOD_UPDATE: {
                        Call call = UpdateEngine.prepareUpdate(mOkHttpClient);
                        if (!mStop) {
                            mCall = call;
                            return UpdateEngine.doUpdate(call);
                        } else {
                            throw new CancelledException();
                        }
                    }
                    case METHOD_GET_FORUM_LIST:
                        return getForumList();
                    case METHOD_GET_COOKIE:
                        return getCookie();
                    case METHOD_GET_POST_LIST:
                        return getPostList(params);
                    case METHOD_GET_POST:
                        return getPost(params);
                    case METHOD_GET_REFERENCE:
                        return getReference(params);
                    case METHOD_REPLY:
                        return reply(params);
                    case METHOD_GET_FEED:
                        return getFeed(params);
                    case METHOD_ADD_FEED:
                        return addFeed(params);
                    case METHOD_DEL_FEED:
                        return delFeed(params);
                    case METHOD_CREATE_POST:
                        return createPost(params);
                    case METHOD_SEARCH:
                        return search(params);
                    case METHOD_GET_CDN_PATH:
                        return getCdnPath();
                    default:
                        return new IllegalStateException("Can't detect method " + mMethod);
                }
            } catch (Exception e) {
                return e;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void onPostExecute(Object result) {
            if (mCallback != null) {
                if (!(result instanceof CancelledException)) {
                    if (result instanceof Exception) {
                        mCallback.onFailure((Exception) result);
                    } else {
                        mCallback.onSuccess(result);
                    }
                } else {
                    // onCancel is called in stop
                }
            }

            // Clear
            mCall = null;
            mCallback = null;
        }
    }

    public interface Callback<E> {

        void onSuccess(E result);

        void onFailure(Exception e);

        void onCancel();
    }
} public class ACUrl {

    public static final String APP_ID = "nimingban";

    public static final String DOMAIN = "adnmb1.com";

    public static final String API_POST_LIST = "/Api/showf?appid=" + APP_ID;

    public static final String API_TIME_LINE = "/Api/timeline?appid=" + APP_ID;

    public static final String API_POST = "/Api/thread?appid=" + APP_ID;

    public static final String API_REFERENCE = "/Home/Forum/ref?appid=" + APP_ID;

    public static final String API_FEED = "/Api/feed?appid=" + APP_ID;

    public static final String API_ADD_FEED = "/Api/addFeed?appid=" + APP_ID;

    public static final String API_DEL_FEED = "/Api/delFeed?appid=" + APP_ID;

    public static final String API_CREATE_POST = "/Home/Forum/doPostThread.html?appid=" + APP_ID;

    public static final String API_GET_COOKIE = "/Api/getCookie?appid=" + APP_ID;

    public static final String API_GET_CDN_PATH = "/Api/getCdnPath?appid=" + APP_ID;

    public static final String API_GET_FORUM_LIST = "/Api/getForumList?appid=" + APP_ID;

    public static final String API_REPLY = "/Home/Forum/doReplyThread.html?appid=" + APP_ID;

    public static final String API_COMMON_POSTS = "http://nimingban.herokuapp.com/common_posts";

    public static final String API_SEARCH = "/Api/search?appid=" + APP_ID;

    public static final String FORUM_ID_TIME_LINE = "-1";

    public static String getHost() {
        if (Settings.getEnableCustomizedAcHost()) {
            return Settings.getCustomizedAcHost();
        } else {
            return Settings.getAcHost();
        }
    }

    public static String getPostListUrl(String forum, int page) {
        if (FORUM_ID_TIME_LINE.equals(forum)) {
            return getTimeLineUrl(page);
        } else {
            return getHost() + API_POST_LIST + "&id=" + forum + "&page=" + (page + 1);
        }
    }

    public static String getTimeLineUrl(int page) {
        return getHost() + API_TIME_LINE + "&page=" + (page + 1);
    }

    public static String getPostUrl(String id, int page) {
        return getHost() + API_POST + "&id=" + id + "&page=" + (page + 1);
    }

    public static String getReferenceUrl(String id) {
        return getHost() + API_REFERENCE + "&id=" + id;
    }

    public static String getFeedUrl(String uuid, int page) {
        return getHost() + API_FEED + "&uuid=" + uuid + "&page=" + (page + 1);
    }

    public static String getAddFeedUrl(String uuid, String tid) {
        return getHost() + API_ADD_FEED + "&uuid=" + uuid + "&tid=" + tid;
    }

    public static String getDelFeedUrl(String uuid, String tid) {
        return getHost() + API_DEL_FEED + "&uuid=" + uuid + "&tid=" + tid;
    }

    public static String getBrowsablePostUrl(String id, int page) {
        StringBuilder sb = new StringBuilder();
        sb.append(getHost()).append("/t/").append(id);
        if (page != 0) {
            sb.append("&page=").append(page + 1);
        }
        return sb.toString();
    }

    public static String getBingSearchUrl(String keyword, int page) throws UnsupportedEncodingException {
        return "http://www.bing.com/search?q=" + URLEncoder.encode(keyword, "UTF-8") + "+site%3ah.nimingban.com&first=" + (page * 10 + 1);
    }

    public static String getSearchUrl(String keyword, int page) throws UnsupportedEncodingException {
        return getHost() + API_SEARCH + "&q=" + URLEncoder.encode(keyword, "UTF-8") + "&pageNo=" + (page + 1);
    }
} public class ACPostStruct {

    public String name;
    public String email;
    public String title;
    public String content;
    public String fid;
    public InputStreamPipe image;
    public String imageType;
    public boolean water;
} public class ACSite extends Site {

    private static final String TAG = ACSite.class.getSimpleName();

    private static final String DEFAULT_PICTURE_PREFIX = ACUrl.getHost() + "/Public/Upload/";

    private static final List<ACCdnPath> DEFAULT_AC_CDN_PATH_LIST = Collections.singletonList(new ACCdnPath());
    private static final String[] DEFAULT_AC_CDN_HOST_LIST = { ACCdnPath.DEFAULT_CDN_HOST };

    private URL mSiteUrl;

    private List<ACCdnPath> mCdnPathList = DEFAULT_AC_CDN_PATH_LIST;
    private float mRateSum;

    private boolean mCdnHostsDirty;
    private String[] mCdnHosts = DEFAULT_AC_CDN_HOST_LIST;

    private static ACSite sInstance;

    public static ACSite getInstance() {
        if (sInstance == null) {
            sInstance = new ACSite();
        }
        return sInstance;
    }

    private ACSite() {
        try {
            mSiteUrl = new URL(ACUrl.getHost());
        } catch (MalformedURLException e) {
            throw new IllegalStateException(e);
        }
    }

    public URL getSiteUrl() {
        return mSiteUrl;
    }

    @Override
    public int getId() {
        return Site.AC;
    }

    @Override
    public String getReadableName(Context context) {
        return "ac";
    }

    @Override
    public long getCookieMaxAge(Context context) {
        SimpleCookieStore cookieStore = NMBApplication.getSimpleCookieStore(context);
        HttpCookieWithId cookie = cookieStore.getCookie(mSiteUrl, "userhash");
        if (cookie == null) {
            return -2;
        } else {
            return cookie.getMaxAge();
        }
    }

    @Override
    public void setCookieMaxAge(Context context, long maxAge) {
        SimpleCookieStore cookieStore = NMBApplication.getSimpleCookieStore(context);
        HttpCookieWithId cookie = cookieStore.getCookie(mSiteUrl, "userhash");
        if (cookie != null) {
            // Remove it
            cookieStore.remove(mSiteUrl, "userhash");

            // Update it
            HttpCookie httpCookie = cookie.httpCookie;
            httpCookie.setMaxAge(maxAge);
            cookieStore.add(mSiteUrl, httpCookie);
        }
    }

    @Override
    public String getUserId(Context context) {
        return Settings.getFeedId();
    }

    @Override
    public String getPostTitle(Context context, String postId) {
        return "No." + postId;
    }

    @Override
    public String getReportForumId() {
        return "18"; // TODO how to get it ?
    }

    public synchronized void setCdnPath(List<ACCdnPath> list) {
        if (list == null || list.isEmpty()) {
            return;
        }

        // Remove invalid cdn path
        List<ACCdnPath> paths = new ArrayList<>(list.size());
        for (ACCdnPath path : list) {
            if (path.url != null && HttpUrl.parse(path.url) != null && path.rate > 0) {
                paths.add(path);
            }
        }
        if (paths.isEmpty()) {
            return;
        }

        mCdnPathList = paths;

        mRateSum = 0.0f;
        for (int i = 0, size = list.size(); i < size; i++) {
            mRateSum += list.get(i).rate;
        }

        // Set cdn hosts dirty
        mCdnHostsDirty = true;
    }

    private ACCdnPath getCdnPath() {
        final float r = MathUtils.random(mRateSum);
        float sum = 0.0f;
        List<ACCdnPath> list = mCdnPathList;
        ACCdnPath cdnPath = null;
        for (int i = 0, size = list.size(); i < size; i++) {
            cdnPath = list.get(i);
            sum += cdnPath.rate;
            if (r <= sum) {
                return cdnPath;
            }
        }
        return cdnPath;
    }

    @NonNull
    public String[] getCdnHosts() {
        if (mCdnHostsDirty || mCdnHosts == null) {
            if (mCdnPathList == null) {
                mCdnHosts = new String[0];
            } else {
                List<String> hosts = new ArrayList<>();
                for (ACCdnPath cdn : mCdnPathList) {
                    if (cdn.url == null) {
                        continue;
                    }
                    HttpUrl url = HttpUrl.parse(cdn.url);
                    if (url != null) {
                        hosts.add(url.host());
                    }
                }
                mCdnHosts = hosts.toArray(new String[hosts.size()]);
            }
        }
        return mCdnHosts;
    }

    public synchronized String getPictureUrl(String key) {
        String url;
        ACCdnPath cdnPath;

        if (mCdnPathList != null && (cdnPath = getCdnPath()) != null) {
            url = UrlUtils.join(cdnPath.url, key);
        } else {
            url = DEFAULT_PICTURE_PREFIX + key;
        }

        return url;
    }
} public class DisplayForum extends Forum {

    public Site site;
    public String id;
    public String displayname;
    public int priority;
    public boolean visibility;
    public String msg;
    public boolean official;

    private CharSequence name;

    @Override
    public String toString() {
        return "site = " + site + ", id = " + id + ", displayname = " + displayname +
                ", priority = " + priority + ", visibility = " + visibility;
    }

    @Override
    public Site getNMBSite() {
        return site;
    }

    @Override
    public String getNMBId() {
        return id;
    }

    @Override
    public CharSequence getNMBDisplayname() {
        if (name == null) {
            if (displayname == null) {
                name = "Forum";
            } else {
                name = Html.fromHtml(displayname);
            }
        }
        return name;
    }

    @Override
    public String getNMBMsg() {
        return msg;
    }

    public boolean getVisibility() {
        return visibility;
    }
} public abstract class Site {

    public static final int RANDOM = 0;
    public static final int AC = 1;
    public static final int SITE_MIN = AC;
    public static final int SITE_MAX = AC;

    public static Site fromId(int id) {
        switch (id) {
            case RANDOM:
                return fromId(MathUtils.random(SITE_MIN, SITE_MAX + 1));
            case AC:
                return ACSite.getInstance();
            default:
                throw new IllegalStateException("Unknown site " + id);
        }
    }

    public static boolean isValid(int id) {
        return id >= SITE_MIN && id <= SITE_MAX;
    }

    public abstract int getId();

    public abstract String getReadableName(Context context);

    /**
     * @return 0 or negative for none
     */
    public abstract long getCookieMaxAge(Context context);

    public abstract void setCookieMaxAge(Context context, long maxAge);

    public abstract String getUserId(Context context);

    public abstract String getPostTitle(Context context, String postId);

    public abstract String getReportForumId();
} public class UniversalProvider extends SimpleFileProvider {

  private static final String AUTHORITY = "com.hippo.nimingban.universal";

  @NonNull
  @Override
  public String getAuthority() {
    return AUTHORITY;
  }

  @Nullable
  @Override
  public UniFile getFile(Uri uri) {
    File file = new File(uri.getPath());
    return UniFile.fromFile(file);
  }

  public static Uri buildUri(String filePath) {
    return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(AUTHORITY)
        .appendPath(filePath)
        .build();
  }
} public final class DraftActivity extends TranslucentActivity implements EasyRecyclerView.OnItemClickListener {

    private LazyList<DraftRaw> mLazyList;

    private View mTip;
    private EasyRecyclerView mRecyclerView;
    private ViewTransition mViewTransition;

    private RecyclerView.LayoutManager mLayoutManager;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.Adapter mWrappedAdapter;
    private RecyclerViewSwipeManager mRecyclerViewSwipeManager;
    private RecyclerViewTouchActionGuardManager mRecyclerViewTouchActionGuardManager;

    @Override
    protected int getLightThemeResId() {
        return Settings.getColorStatusBar() ? R.style.NormalActivity : R.style.NormalActivity_NoStatus;
    }

    @Override
    protected int getDarkThemeResId() {
        return Settings.getColorStatusBar() ? R.style.NormalActivity_Dark : R.style.NormalActivity_Dark_NoStatus;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setStatusBarColor(ResourcesUtils.getAttrColor(this, R.attr.colorPrimaryDark));
        ToolbarActivityHelper.setContentView(this, R.layout.activity_draft);
        setActionBarUpIndicator(DrawableManager.getDrawable(this, R.drawable.v_arrow_left_dark_x24));

        mTip = findViewById(R.id.tip);
        mRecyclerView = (EasyRecyclerView) findViewById(R.id.recycler_view);
        mViewTransition = new ViewTransition(mTip, mRecyclerView);

        // Layout Manager
        mLayoutManager = new LinearLayoutManager(this);

        // touch guard manager  (this class is required to suppress scrolling while swipe-dismiss animation is running)
        mRecyclerViewTouchActionGuardManager = new RecyclerViewTouchActionGuardManager();
        mRecyclerViewTouchActionGuardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
        mRecyclerViewTouchActionGuardManager.setEnabled(true);

        // swipe manager
        mRecyclerViewSwipeManager = new RecyclerViewSwipeManager();

        mAdapter = new DraftAdapter();
        mAdapter.setHasStableIds(true);
        mWrappedAdapter = mRecyclerViewSwipeManager.createWrappedAdapter(mAdapter);      // wrap for swiping

        final GeneralItemAnimator animator = new SwipeDismissItemAnimator();

        // Change animations are enabled by default since support-v7-recyclerview v22.
        // Disable the change animation in order to make turning back animation of swiped item works properly.
        animator.setSupportsChangeAnimations(false);

        mRecyclerView.hasFixedSize();
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mWrappedAdapter);  // requires *wrapped* adapter
        mRecyclerView.setItemAnimator(animator);
        mRecyclerView.setOnItemClickListener(this);

        // NOTE:
        // The initialization order is very important! This order determines the priority of touch event handling.
        //
        // priority: TouchActionGuard > Swipe > DragAndDrop
        mRecyclerViewTouchActionGuardManager.attachRecyclerView(mRecyclerView);
        mRecyclerViewSwipeManager.attachRecyclerView(mRecyclerView);

        updateLazyList();
        checkEmpty(false);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mRecyclerViewSwipeManager != null) {
            mRecyclerViewSwipeManager.release();
            mRecyclerViewSwipeManager = null;
        }

        if (mRecyclerViewTouchActionGuardManager != null) {
            mRecyclerViewTouchActionGuardManager.release();
            mRecyclerViewTouchActionGuardManager = null;
        }

        if (mRecyclerView != null) {
            mRecyclerView.setItemAnimator(null);
            mRecyclerView.setAdapter(null);
            mRecyclerView = null;
        }

        if (mWrappedAdapter != null) {
            WrapperAdapterUtils.releaseAll(mWrappedAdapter);
            mWrappedAdapter = null;
        }
        mAdapter = null;
        mLayoutManager = null;

        if (mLazyList != null) {
            mLazyList.close();
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    // Remember to notify
    private void updateLazyList() {
        LazyList<DraftRaw> lazyList = DB.getDraftLazyList();
        if (mLazyList != null) {
            mLazyList.close();
        }
        mLazyList = lazyList;
    }

    private void checkEmpty(boolean animation) {
        if (mAdapter.getItemCount() == 0) {
            mViewTransition.showView(0, animation);
        } else {
            mViewTransition.showView(1, animation);
        }
    }

    @Override
    public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) {
        DraftRaw draftRaw = mLazyList.get(position);
        ClipboardManager cbm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        cbm.setPrimaryClip(ClipData.newPlainText(null, draftRaw.getContent()));
        Toast.makeText(this, R.string.draft_copied_clipboard, Toast.LENGTH_SHORT).show();
        return false;
    }

    private class DraftHolder extends AbstractSwipeableItemViewHolder {

        private View swipable;
        public TextView time;
        public FontTextView content;

        public DraftHolder(View itemView) {
            super(itemView);

            swipable = itemView.findViewById(R.id.swipable);
            time = (TextView) itemView.findViewById(R.id.time);
            content = (FontTextView) itemView.findViewById(R.id.content);
        }

        @Override
        public View getSwipeableContainerView() {
            return swipable;
        }
    }

    private class DraftAdapter extends RecyclerView.Adapter<DraftHolder>
            implements SwipeableItemAdapter<DraftHolder> {

        @Override
        public DraftHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new DraftHolder(DraftActivity.this.getLayoutInflater().inflate(R.layout.item_draft, parent, false));
        }

        @Override
        public void onBindViewHolder(DraftHolder holder, int position) {
            DraftRaw draftRaw = mLazyList.get(position);
            holder.time.setText(ReadableTime.getDisplayTime(draftRaw.getTime()));
            holder.content.setText(draftRaw.getContent());

            holder.content.setTextSize(Settings.getFontSize());
            holder.content.setLineSpacing(LayoutUtils.dp2pix(DraftActivity.this, Settings.getLineSpacing()), 1.0f);
            if (Settings.getFixEmojiDisplay()) {
                holder.content.useCustomTypeface();
            } else {
                holder.content.useOriginalTypeface();
            }
        }

        @Override
        public long getItemId(int position) {
            return mLazyList.get(position).getId();
        }

        @Override
        public int getItemCount() {
            return mLazyList.size();
        }

        @Override
        public int onGetSwipeReactionType(DraftHolder holder, int position, int x, int y) {
            return SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H;
        }

        @Override
        public void onSetSwipeBackground(DraftHolder holder, int position, int type) {
            // Empty
        }

        @Override
        public void onSwipeItemStarted(DraftHolder holder, int position) {
            // Empty
        }

        @Override
        public SwipeResultAction onSwipeItem(DraftHolder holder, int position, int result) {
            switch (result) {
                // swipe right
                case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
                case SwipeableItemConstants.RESULT_SWIPED_LEFT:
                    return new DeleteAction(position);
                case SwipeableItemConstants.RESULT_CANCELED:
                default:
                    return null;
            }
        }
    }

    private class DeleteAction extends SwipeResultActionRemoveItem {

        private int mPosition;

        public DeleteAction(int position) {
            mPosition = position;
        }

        @Override
        protected void onPerformAction() {
            super.onPerformAction();

            final int position = mPosition;
            final DraftRaw raw = mLazyList.get(position);
            if (raw != null) {
                DB.removeDraft(raw.getId());
                updateLazyList();
                mAdapter.notifyItemRemoved(position);
                checkEmpty(true);

                Snackbar snackbar = Snackbar.make(mRecyclerView, R.string.draft_deleted, Snackbar.LENGTH_LONG);
                snackbar.setAction(R.string.undo, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        DB.addDraft(raw.getContent(), raw.getTime());
                        updateLazyList();
                        mAdapter.notifyDataSetChanged();
                        checkEmpty(true);
                    }
                });
                snackbar.show();
            }
        }
    }



} public class GalleryActivity2 extends SwipeBackActivity {

    public static final String[] IMAGE_EXTENSIONS = {
            "jpg",
            "jpeg",
            "png",
            "gif"
    };

    public static final String ACTION_SINGLE_IMAGE = "com.hippo.nimingban.ui.GalleryActivity2.action.SINGLE_IMAGE";
    public static final String ACTION_MULTIMAGE = "com.hippo.nimingban.ui.GalleryActivity2.action.MULTIMAGE";
    public static final String ACTION_IMAGE_FILE = "com.hippo.nimingban.ui.GalleryActivity2.action.IMAGE_FILE";

    public static final String KEY_SITE = "site";
    public static final String KEY_ID = "id";
    public static final String KEY_KEY = "key";
    public static final String KEY_IMAGE = "image";
    public static final String KEY_UNI_FILE_URI = "uni_file_uri";

    private ViewPager mViewPager;
    private GalleryAdapter mGalleryAdapter;

    private SaveTask mSaveTask;

    private boolean handlerIntent(Intent intent) {
        if (intent == null) {
            return false;
        }

        String action = intent.getAction();
        if (ACTION_SINGLE_IMAGE.equals(action)) {
            int site = intent.getIntExtra(KEY_SITE, -1);
            String id = intent.getStringExtra(KEY_ID);
            String key = intent.getStringExtra(KEY_KEY);
            String image = intent.getStringExtra(KEY_IMAGE);
            if (Site.isValid(site) && id != null && key != null && image != null) {
                mGalleryAdapter = new SingleImageAdapter(Site.fromId(site), id, key, image);
                return true;
            }
        } else if (ACTION_IMAGE_FILE.equals(action)) {
            Uri fileUri = intent.getParcelableExtra(KEY_UNI_FILE_URI);
            UniFile file = UniFile.fromUri(this, fileUri);
            if (file != null) {
                mGalleryAdapter = new ImageFileAdapter(file);
                return true;
            }
        }
        return false;
    }

    @Override
    protected int getLightThemeResId() {
        return Settings.getColorStatusBar() ? R.style.SwipeActivity : R.style.SwipeActivity_NoStatus;
    }

    @Override
    protected int getDarkThemeResId() {
        return Settings.getColorStatusBar() ? R.style.SwipeActivity_Dark : R.style.SwipeActivity_Dark_NoStatus;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!handlerIntent(getIntent())) {
            finish();
            return;
        }

        setStatusBarColor(ResourcesUtils.getAttrColor(this, R.attr.colorPrimaryDark));
        ToolbarActivityHelper.setContentView(this, R.layout.activity_gallery_2);
        setActionBarUpIndicator(DrawableManager.getDrawable(this, R.drawable.v_arrow_left_dark_x24));

        mViewPager = (ViewPager) findViewById(R.id.view_pager);

        mViewPager.setAdapter(mGalleryAdapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mViewPager != null) {
            // Unload all pager
            for (int i = 0, n = mViewPager.getChildCount(); i < n; i++) {
                View child = mViewPager.getChildAt(i);
                if (child instanceof GalleryPage) {
                    ((GalleryPage) child).unload();
                }
            }
        }

        if (mSaveTask != null) {
            mSaveTask.onActivityDestory();
            mSaveTask = null;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_gallery_2, menu);

        if (mGalleryAdapter instanceof ImageFileAdapter) {
            menu.removeItem(R.id.action_refresh);
            menu.removeItem(R.id.action_search);
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        final int id = item.getItemId();
        switch (id) {
            case android.R.id.home:
                finish();
                return true;
            case R.id.action_save:
                if (mSaveTask == null) {
                    mGalleryAdapter.saveCurrentImage(false);
                }
                return true;
            case R.id.action_share:
                if (mSaveTask == null) {
                    mGalleryAdapter.saveCurrentImage(true);
                }
                return true;
            case R.id.action_refresh:
                if (mSaveTask == null) {
                    mGalleryAdapter.reloadCurrentImage();
                }
                return true;
            case R.id.action_search_google:
            case R.id.action_search_baidu:
            case R.id.action_search_sogou:
            case R.id.action_search_tineye:
            case R.id.action_search_whatanime:
            case R.id.action_search_saucenao:
            case R.id.action_search_iqdb:
            case R.id.action_search_iqdb_3d:
                if (mGalleryAdapter instanceof SingleImageAdapter) {
                    String url = ImageSearch.getImageSearchUrl(id,
                            ((SingleImageAdapter) mGalleryAdapter).getCurrentImageUrl());
                    if (url != null) {
                        OpenUrlHelper.openUrl(this, url, false);
                    }
                }
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * @param uri the save image file url, null for fail
     * @param share true for share
     */
    public void onSaveTaskOver(Uri uri, boolean share) {
        if (mSaveTask != null) {
            mSaveTask = null;
        }

        if (share) {
            if (uri == null) {
                Toast.makeText(this, R.string.cant_save_image, Toast.LENGTH_SHORT).show();
            } else {
                String mimeType = getContentResolver().getType(uri);
                if (TextUtils.isEmpty(mimeType)) {
                    mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
                            MimeTypeMap.getFileExtensionFromUrl(uri.toString()));
                    if (TextUtils.isEmpty(mimeType)) {
                        mimeType = "image/*";
                    }
                }
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_SEND);
                intent.putExtra(Intent.EXTRA_STREAM, ImageProvider.buildUri(uri.getLastPathSegment()));
                intent.setType(mimeType);
                startActivity(Intent.createChooser(intent, getString(R.string.share_image)));
            }
        } else {
            Toast.makeText(this, uri != null ? R.string.save_successfully : R.string.save_failed, Toast.LENGTH_SHORT).show();
        }

        // Notify media scanner
        if (uri != null) {
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
        }
    }

    private class GalleryHolder extends PagerHolder {

        public GalleryPage galleryPage;

        public GalleryHolder(View itemView) {
            super(itemView);

            galleryPage = (GalleryPage) itemView;
        }
    }

    private abstract class GalleryAdapter extends RecyclerPagerAdapter<GalleryHolder> {

        @NonNull
        @Override
        public GalleryHolder createPagerHolder(ViewGroup container) {
            return new GalleryHolder(GalleryActivity2.this.getLayoutInflater()
                    .inflate(R.layout.item_gallery, container, false));
        }

        public abstract void reloadCurrentImage();

        public abstract void saveCurrentImage(boolean share);
    }

    private class ImageFileAdapter extends GalleryAdapter {

        private UniFile mImageFile;

        public ImageFileAdapter(UniFile imageFile) {
            mImageFile = imageFile;
        }

        @Override
        public void reloadCurrentImage() {
            // Empty
        }

        @Override
        public void saveCurrentImage(boolean share) {
            GalleryHolder holder = getPagerHolder(0);
            UniFile dir = Settings.getImageSaveLocation();
            if (holder == null || !holder.galleryPage.isLoaded() || dir == null) {
                onSaveTaskOver(null, share);
                return;
            }

            mSaveTask = new ImageFileSaveTask(GalleryActivity2.this, mImageFile, dir, share);
            mSaveTask.execute();
        }

        @Override
        public void bindPagerHolder(GalleryHolder holder, int position) {
            Object object = NMBApplication.getImageWrapperHelper(GalleryActivity2.this)
                    .decode(new UniFileInputStreamPipe(mImageFile));
            if (object != null) {
                ImageWrapper imageWrapper = (ImageWrapper) object;
                imageWrapper.start();
                Drawable drawable = new ImageDrawable(imageWrapper);
                holder.galleryPage.showDrawable(drawable);
            } else {
                holder.galleryPage.showFailedText();
            }
        }

        @Override
        public void unbindPagerHolder(GalleryHolder holder, int position) {
            holder.galleryPage.unload();
        }

        @Override
        public int getCount() {
            return 1;
        }
    }

    private class SingleImageAdapter extends GalleryAdapter {

        private Site mSite;
        private String mId;
        private String mKey;
        private String mImage;

        public SingleImageAdapter(Site site, String id, String key, String image) {
            mSite = site;
            mId = id;
            mKey = key;
            mImage = image;
        }

        @Override
        public void bindPagerHolder(GalleryHolder holder, int position) {
            String key;
            DataContainer container;
            UniFile dir = Settings.getImageSaveLocation();
            if (Settings.getSaveImageAuto() && dir != null) {
                key = null;
                container = new UniFileDataContain(GalleryActivity2.this, dir,
                        mSite.getReadableName(GalleryActivity2.this) + "-" + mId);
            } else {
                key = mKey;
                container = null;
            }

            holder.galleryPage.load(key, mImage, container);
        }

        @Override
        public void unbindPagerHolder(GalleryHolder holder, int position) {
            holder.galleryPage.unload();
        }

        @Override
        public int getCount() {
            return 1;
        }

        public String getCurrentImageUrl() {
            return mImage;
        }

        @Override
        public void reloadCurrentImage() {
            GalleryHolder holder = getPagerHolder(0);
            if (holder == null) {
                return;
            }

            // Unload
            holder.galleryPage.unload();

            // Remove in cache
            NMBApplication.getConaco(GalleryActivity2.this).getBeerBelly().remove(mImage);

            // Remove all in save location
            UniFile dir = Settings.getImageSaveLocation();
            if (dir != null) {
                String name = mSite.getReadableName(GalleryActivity2.this) + "-" + mId;
                for (String extension : IMAGE_EXTENSIONS) {
                    UniFile file = dir.findFile(name + '.' + extension);
                    if (file != null) {
                        file.delete();
                    }
                }
            }

            // Load
            bindPagerHolder(holder, 0);
        }

        @Override
        public void saveCurrentImage(boolean share) {
            GalleryHolder holder = getPagerHolder(0);
            UniFile dir = Settings.getImageSaveLocation();
            if (holder == null || !holder.galleryPage.isLoaded() || dir == null) {
                onSaveTaskOver(null, share);
                return;
            }

            String name = mSite.getReadableName(GalleryActivity2.this) + "-" + mId;

            mSaveTask = new SingleImageSaveTask(GalleryActivity2.this, dir, name, mKey, share);
            mSaveTask.execute();
        }
    }

    /*
    private class MultimageAdapter extends GalleryAdapter {

        private Site mSite;

        @Override
        public void reloadCurrentImage() {

        }

        @Override
        public void saveCurrentImage(boolean share) {

        }

        @Override
        public void bindPagerHolder(GalleryHolder holder, int position) {

        }

        @Override
        public void unbindPagerHolder(GalleryHolder holder, int position) {

        }

        @Override
        public int getCount() {
            return 0;
        }
    }
    */

    private static UniFile findFileForName(UniFile dir, String name, String[] extensions, String[] resultExtension) {
        for (String extension : extensions) {
            UniFile file = dir.findFile(name + '.' + extension);
            if (file != null) {
                if (resultExtension != null && resultExtension.length > 0) {
                    resultExtension[0] = extension;
                }
                return file;
            }
        }
        return null;
    }

    /**
     * Let conaco store image to image save location directly
     */
    private static class UniFileDataContain implements DataContainer {

        private Context mContext;
        private UniFile mDir;
        private String mName;
        @Nullable
        private String mFilename;

        public UniFileDataContain(Context context, @NonNull UniFile dir, String name) {
            mContext = context.getApplicationContext();
            mDir = dir;
            mName = name;
        }

        @Override
        public void onUrlMoved(String requestUrl, String responseUrl) {
        }

        @Override
        public boolean save(InputStream is, long length, String mediaType, ProgressNotify notify) {
            // Get extension and filename
            String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType);
            if (!TextUtils.isEmpty(extension)) {
                mFilename = mName + '.' + extension;
            } else {
                mFilename = mName;
            }

            UniFile file = mDir.createFile(mFilename);
            if (file == null) {
                return false;
            }

            OutputStream os = null;
            try {

                os = file.openOutputStream();

                final byte buffer[] = new byte[1024 * 4];
                long receivedSize = 0;
                int bytesRead;

                while((bytesRead = is.read(buffer)) !=-1) {
                    os.write(buffer, 0, bytesRead);
                    receivedSize += bytesRead;
                    if (length > 0) {
                        notify.notifyProgress((long) bytesRead, receivedSize, length);
                    }
                }
                os.flush();
                IOUtils.closeQuietly(os);

                // Notify media scanner
                mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, file.getUri()));

                return true;
            } catch (IOException e) {
                IOUtils.closeQuietly(os);
                file.delete();
                return false;
            }
        }

        @Override
        public InputStreamPipe get() {
            if (mFilename != null) {
                UniFile file = mDir.findFile(mFilename);
                if (file.exists()) {
                    return new UniFileInputStreamPipe(file);
                }
            }

            String[] extension = new String[1];
            UniFile file = findFileForName(mDir, mName, IMAGE_EXTENSIONS, extension);
            if (file != null) {
                return new UniFileInputStreamPipe(file);
            }

            return null;
        }

        @Override
        public void remove() {
            // Empty
        }
    }

    private static abstract class SaveTask extends AsyncTask<Void, Void, Uri> {

        public abstract void onActivityDestory();
    }

    private static class ImageFileSaveTask extends SaveTask {

        private Context mContext;
        private UniFile mFrom;
        private UniFile mSaveDir;
        private boolean mShare;

        public ImageFileSaveTask(Context context, UniFile from, UniFile saveDir, boolean share) {
            mContext = context;
            mFrom = from;
            mSaveDir = saveDir;
            mShare = share;
        }

        public UniFile createSaveFile(String mineType) {
            String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mineType);
            String displayName;
            if (TextUtils.isEmpty(extension)) {
                displayName = mFrom.getName();
            } else {
                displayName = mFrom.getName() + '.' + extension;
            }
            return mSaveDir.createFile(displayName);
        }

        private static String getFilePathForUri(Uri uri) {
            if (UniFile.isFileUri(uri)) {
                return uri.getPath();
            } else {
                return null;
            }
        }

        /**
         * @return Null for failed
         */
        @Override
        protected Uri doInBackground(Void... params) {
            UniFile to = null;

            // Get from MineType and get out save file
            InputStream is = null;
            try {
                is = mFrom.openInputStream();
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(is, null, options);
                if (options.outMimeType != null) {
                    to = createSaveFile(options.outMimeType);
                }
            } catch (IOException e) {
                return null;
            } finally {
                IOUtils.closeQuietly(is);
            }

            // Check save file null
            if (to == null) {
                return null;
            }

            // Is from and to is the same ?
            Uri fromUri = mFrom.getUri();
            Uri toUri = to.getUri();
            if (fromUri.equals(toUri)) {
                return fromUri;
            }
            String fromPath = getFilePathForUri(fromUri);
            String toPath = getFilePathForUri(toUri);
            if (fromPath != null && fromPath.equals(toPath)) {
                return fromUri;
            }

            boolean ok;
            OutputStream os = null;
            try {
                is = mFrom.openInputStream();
                os = to.openOutputStream();
                IOUtils.copy(is, os);
                ok = true;
            } catch (IOException e) {
                ok = false;
            } finally {
                IOUtils.closeQuietly(is);
                IOUtils.closeQuietly(os);
            }

            if (!ok) {
                to.delete();
                return null;
            }

            return to.getUri();
        }

        @Override
        protected void onPostExecute(Uri uri) {
            if (mContext instanceof GalleryActivity2) {
                ((GalleryActivity2) mContext).onSaveTaskOver(uri, mShare);
            }
        }

        @Override
        public void onActivityDestory() {
            mContext = mContext.getApplicationContext();
        }
    }

    private static class SingleImageSaveTask extends SaveTask {

        private Context mContext;
        private UniFile mSaveDir;
        private String mName;
        private String mKey;
        private boolean mShare;

        public SingleImageSaveTask(Context context, UniFile saveDir, String name, String key, boolean share) {
            mContext = context;
            mSaveDir = saveDir;
            mName = name;
            mKey = key;
            mShare = share;
        }

        public UniFile createSaveFile(String mineType) {
            String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mineType);
            String displayName;
            if (TextUtils.isEmpty(extension)) {
                displayName = mName;
            } else {
                displayName = mName + '.' + extension;
            }
            return mSaveDir.createFile(displayName);
        }

        @Override
        protected Uri doInBackground(Void... params) {
            // First try to find file in dir
            UniFile file = findFileForName(mSaveDir, mName, IMAGE_EXTENSIONS, null);
            if (file != null) {
                // Check is it a image
                Bitmap bitmap = BitmapUtils.decodeStream(new UniFileInputStreamPipe(file), 100, 100, -1, true, false, null);
                if (bitmap != null) {
                    // It is image
                    bitmap.recycle();
                    return file.getUri();
                } else {
                    file.delete();
                }
            }

            // Save image to temp file
            File temp = NMBAppConfig.createTempFile();
            if (temp == null) {
                return null;
            }
            boolean ok = true;
            OutputStream os = null;
            try {
                os = new FileOutputStream(temp);
                Conaco conaco = NMBApplication.getConaco(mContext);
                ok = conaco.getBeerBelly().pullFromDiskCache(mKey, os);
            } catch (IOException e) {
                ok = false;
            } finally {
                IOUtils.closeQuietly(os);
            }
            if (!ok) {
                temp.delete();
                return null;
            }

            UniFile to = null;
            // Get extension and create save file
            InputStream is = null;
            try {
                is = new FileInputStream(temp);
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(is, null, options);
                if (options.outMimeType != null) {
                    to = createSaveFile(options.outMimeType);
                }
            } catch (IOException e) {
                return null;
            } finally {
                IOUtils.closeQuietly(is);
            }

            // Check save file null
            if (to == null) {
                temp.delete();
                return null;
            }

            // Copy temp to save file
            try {
                is = new FileInputStream(temp);
                os = to.openOutputStream();
                IOUtils.copy(is, os);
                ok = true;
            } catch (IOException e) {
                ok = false;
            } finally {
                IOUtils.closeQuietly(is);
                IOUtils.closeQuietly(os);
            }

            // Clean up
            temp.delete();
            if (ok) {
                return to.getUri();
            } else {
                to.delete();
                return null;
            }
        }

        @Override
        protected void onPostExecute(Uri uri) {
            if (mContext instanceof GalleryActivity2) {
                ((GalleryActivity2) mContext).onSaveTaskOver(uri, mShare);
            }
        }

        @Override
        public void onActivityDestory() {
            mContext = mContext.getApplicationContext();
        }
    }
} public class QRCodeScanActivity extends TranslucentActivity
        implements QRCodeReaderView.OnQRCodeReadListener,
        ActivityCompat.OnRequestPermissionsResultCallback {

  private static final int REQUEST_CODE_PICK_IMAGE = 0;
  private static final int REQUEST_CODE_CAMERA = 0;

  private QRCodeReaderView qrCodeReaderView;

  private AlertDialog progressDialog;

  @Override
  protected int getLightThemeResId() {
    return Settings.getColorStatusBar() ? R.style.NormalActivity : R.style.NormalActivity_NoStatus;
  }

  @Override
  protected int getDarkThemeResId() {
    return Settings.getColorStatusBar() ? R.style.NormalActivity_Dark : R.style.NormalActivity_Dark_NoStatus;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_qr_code_scan);

    qrCodeReaderView = (QRCodeReaderView) findViewById(R.id.qrdecoderview);
    qrCodeReaderView.setOnQRCodeReadListener(this);
    qrCodeReaderView.setBackCamera();

    findViewById(R.id.choose_pic).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        try {
          startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
        } catch (Exception e) {
          // Ignore
        }
      }
    });

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
        PackageManager.PERMISSION_GRANTED) {
      ActivityCompat.requestPermissions(this,
          new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAMERA);
    }
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_CODE_CAMERA) {
      if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        recreate();
      } else {
        Toast.makeText(this, R.string.main_add_cookies_denied, Toast.LENGTH_SHORT).show();
      }
    } else {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
  }

  @Override
  protected void onResume() {
    super.onResume();
    qrCodeReaderView.startCamera();
  }

  @Override
  protected void onPause() {
    super.onPause();
    qrCodeReaderView.stopCamera();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_PICK_IMAGE) {
      if (resultCode == RESULT_OK && data.getData() != null) {
        scanImage(data.getData());
      }
    } else {
      super.onActivityResult(requestCode, resultCode, data);
    }
  }

  @SuppressLint("StaticFieldLeak")
  private void scanImage(final Uri uri) {
    if (progressDialog != null) return;
    progressDialog = new ProgressDialogBuilder(this)
        .setTitle(R.string.please_wait)
        .setMessage(R.string.qr_scan_processing)
        .setCancelable(false)
        .show();

    new AsyncTask<Void, Void, String>() {
      @Override
      protected String doInBackground(Void... voids) {
        UniFile file = UniFile.fromUri(QRCodeScanActivity.this, uri);
        if (file == null) return null;

        // ZXing can't process large image
        Bitmap bitmap = BitmapUtils.decodeStream(new UniFileInputStreamPipe(file), 1024, 1024);
        if (bitmap == null) return null;

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        bitmap.recycle();

        int[] newPixels = null;
        for (int i = 0; i < 4; i++) {
          if (i > 0) {
            newPixels = BitmapUtils.rotate(pixels, width, height, newPixels);
            int temp = width;
            width = height;
            height = temp;
            int[] tempPixels = pixels;
            pixels = newPixels;
            newPixels = tempPixels;
          }

          RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
          final HybridBinarizer hybBin = new HybridBinarizer(source);
          final BinaryBitmap bBitmap = new BinaryBitmap(hybBin);

          QRCodeReader reader = new QRCodeReader();
          Map<DecodeHintType, Boolean> hints = new HashMap<>();

          try {
            return reader.decode(bBitmap, hints).getText();
          } catch (NotFoundException | FormatException | ChecksumException e) {
            // Try PURE_BARCODE
            hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
            reader.reset();
            try {
              return reader.decode(bBitmap, hints).getText();
            } catch (NotFoundException | FormatException | ChecksumException ee) {
              // pass
            }
          }
        }

        return null;
      }

      @Override
      protected void onPostExecute(String text) {
        if (progressDialog != null) {
          progressDialog.dismiss();
          progressDialog = null;
        }

        if (text != null) {
          processCookieText(text);
        } else {
          Toast.makeText(QRCodeScanActivity.this, R.string.qr_scan_invalid, Toast.LENGTH_SHORT).show();
        }
      }
    }.execute();
  }

  @Override
  public void onQRCodeRead(String text, PointF[] points) {
    if (!isFinishing()) {
      processCookieText(text);
    }
  }

  private void processCookieText(String text) {
    try {
      JSONObject jo = new JSONObject(text);
      String userhash = jo.getString("cookie");
      if (userhash == null) {
        throw new NullPointerException("cookie is null");
      }

      HttpCookie cookie = new HttpCookie("userhash", userhash);
      cookie.setDomain(ACUrl.DOMAIN);
      cookie.setPath("/");
      cookie.setMaxAge(-1);

      SimpleCookieStore store = NMBApplication.getSimpleCookieStore(this);
      store.add(new URL(ACUrl.getHost()), cookie);

      Toast.makeText(this, R.string.qr_scan_succeed, Toast.LENGTH_SHORT).show();
      finish();
    } catch (Exception e) {
      e.printStackTrace();

      Toast.makeText(this, R.string.qr_scan_invalid, Toast.LENGTH_SHORT).show();
    }
  }
} public class BitmapUtils {

    public static Context sContext;

    public static void initialize(Context context) {
        sContext = context.getApplicationContext();
    }

    public static long availableMemory() {
        final Runtime runtime = Runtime.getRuntime();
        final long used = runtime.totalMemory() - runtime.freeMemory();

        final ActivityManager activityManager = (ActivityManager) sContext.
                getSystemService(Context.ACTIVITY_SERVICE);
        final long total = activityManager.getMemoryClass() * 1024 * 1024;

        return total - used;
    }

    public static Bitmap decodeStream(@NonNull InputStreamPipe isp, int maxWidth, int maxHeight,
            int pixels, boolean checkMemory, boolean justCalc, int[] sampleSize) {
        try {
            isp.obtain();

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(isp.open(), null, options);
            isp.close();

            int width = options.outWidth;
            int height = options.outHeight;
            if (width <= 0 || height <= 0) {
                if (sampleSize != null && sampleSize.length >= 1) {
                    sampleSize[0] = -1;
                }
                return null;
            }

            int scaleW = 1;
            int scaleH = 1;
            int scaleP = 1;
            int scaleM = 1;
            if (maxWidth > 0 && width > maxWidth) {
                scaleW = MathUtils.ceilDivide(width, maxWidth);
            }
            if (maxHeight > 0 && height > maxHeight) {
                scaleH = MathUtils.ceilDivide(height, maxHeight);
            }
            if (pixels > 0 && width * height > pixels) {
                scaleP = (int) Math.ceil(Math.sqrt(width * height / (float) pixels));
            }
            if (checkMemory) {
                long m = availableMemory() - 5 * 1024 * 1024; // Leave 5m
                if (m < 0) {
                    if (sampleSize != null && sampleSize.length >= 1) {
                        sampleSize[0] = -1;
                    }
                    return null;
                }
                if (width * height * 3 > m) {
                    scaleM = (int) Math.ceil(Math.sqrt(width * height * 3 / (float) m));
                }
            }
            options.inSampleSize = MathUtils.nextPowerOf2(MathUtils.max(scaleW, scaleH, scaleP, scaleM, 1));
            if (sampleSize != null && sampleSize.length >= 1) {
                sampleSize[0] = options.inSampleSize;
            }

            options.inJustDecodeBounds = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;

            if (justCalc) {
                return null;
            } else {
                try {
                    return BitmapFactory.decodeStream(isp.open(), null, options);
                } catch (OutOfMemoryError e) {
                    if (sampleSize != null && sampleSize.length >= 1) {
                        sampleSize[0] = -1;
                    }
                    return null;
                }
            }
        } catch (IOException e) {
            if (sampleSize != null && sampleSize.length >= 1) {
                sampleSize[0] = -1;
            }
            return null;
        } finally {
            isp.close();
            isp.release();
        }
    }

    /**
     * @return null or the bitmap
     */
    public static Bitmap decodeStream(@NonNull InputStreamPipe isp, int maxWidth, int maxHeight) {
        return decodeStream(isp, maxWidth, maxHeight, -1, false, false, null);
    }

    /**
     * Rotate the pixels clockwise
     */
    public static int[] rotate(int[] pixels, int width, int height, @Nullable int[] newPixels) {
        newPixels = newPixels == null ? new int[width * height] : newPixels;

        for (int i = 0, n = width * height; i < n; i++) {
            int x = i % width;
            int y = i / width;

            int newX = height - 1 - y;
            int newY = x;

            newPixels[newY * height + newX] = pixels[i];
        }

        return newPixels;
    }
} public final class DB {

    private static String[] AC_FORUM_ID_ARRAY = {"-1", "4", "20", "11", "30", "32", "40", "35", "56", "110", "15", "19", "106", "17", "98", "102", "75", "97", "89", "96", "81", "111", "14", "12", "90", "99", "87", "64", "5", "93", "101", "6", "103", "2", "23", "72", "3", "25", "107", "24", "22", "70", "38", "86", "51", "10", "28", "108", "45", "34", "29", "16", "100", "13", "55", "39", "31", "37", "33", "18", "112", };
    private static String[] AC_FORUM_NAME_ARRAY = {"时间线", "综合版1", "欢乐恶搞", "推理(脑洞)", "技术(码农)", "料理(美食<font style='color:#fff'>·汪版</font>)", "喵版(主子)", "音乐(推歌)", "学业(校园)", "社畜<font color=\"red\">New!</font>", "科学(理学)", "小说(连载)", "买买买(剁手)", "绘画涂鸦(二创)", "姐妹1(淑女)", "LGBT", "数码(装机)", "女装(时尚)", "日记(树洞)", "圈内(版务讨论)", "都市怪谈(灵异)", "跑团<font style='color:#f00'>New!</font>", "动画", "漫画", "美漫(小马)", "国漫", "轻小说", "GALGAME", "东方Project", "舰娘", "LoveLive", "VOCALOID", "文学(推书)", "游戏综合版", "暴雪游戏<font color=\"red\">New!</font>", "DNF", "手游<font color=\"red\">New!</font>", "任天堂<font color=\"red\">NS</font>", "Steam", "索尼", "LOL", "DOTA", "精灵宝可梦", "战争雷霆", "坦克战机战舰世界", "Minecraft", "怪物猎人", "辐射4", "卡牌桌游", "音乐游戏", "AC大逃杀", "日本偶像(AKB)", "中国偶像(SNH)", "眼科(Cosplay)", "声优", "模型(手办)", "电影/电视", "军武", "体育", "值班室", "城墙", };

    private static DaoSession sDaoSession;

    public static class DBOpenHelper extends DaoMaster.OpenHelper {

        private boolean mCreate;
        private boolean mUpgrade;
        private int mOldVersion;
        private int mNewVersion;

        public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
            super(context, name, factory);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            super.onCreate(db);
            mCreate = true;
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            mUpgrade = true;
            mOldVersion = oldVersion;
            mNewVersion = newVersion;

            switch (oldVersion) {
                case 1:
                    ACRecordDao.createTable(db, true);
                case 2:
                    ACCommonPostDao.createTable(db, true);
                case 3:
                    db.execSQL("ALTER TABLE '" + ACForumDao.TABLENAME + "' ADD COLUMN '" +
                            ACForumDao.Properties.Msg.columnName + "' TEXT");
                case 4:
                    db.execSQL("ALTER TABLE '" + ACForumDao.TABLENAME + "' ADD COLUMN '" +
                            ACForumDao.Properties.Official.columnName + "' INTEGER DEFAULT 0");
                case 5:
                    db.execSQL("ALTER TABLE '" + ACForumDao.TABLENAME + "' ADD COLUMN '" +
                            ACForumDao.Properties.Frequency.columnName + "' INTEGER DEFAULT 0");
            }
        }

        public boolean isCreate() {
            return mCreate;
        }

        public void clearCreate() {
            mCreate = false;
        }

        public boolean isUpgrade() {
            return mUpgrade;
        }

        public void clearUpgrade() {
            mUpgrade = false;
        }

        public int getOldVersion() {
            return mOldVersion;
        }
    }

    public static void initialize(Context context) {
        DBOpenHelper helper = new DBOpenHelper(
                context.getApplicationContext(), "nimingban", null);

        SQLiteDatabase db = helper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(db);

        sDaoSession = daoMaster.newSession();

        if (helper.isCreate()) {
            helper.clearCreate();

            insertDefaultACForums();
            insertDefaultACCommonPosts();
        }

        if (helper.isUpgrade()) {
            helper.clearUpgrade();

            switch (helper.getOldVersion()) {
                case 1:
                case 2:
                    insertDefaultACCommonPosts();
                case 3:
            }
        }
    }

    private static void insertDefaultACForums() {
        ACForumDao dao = sDaoSession.getACForumDao();
        dao.deleteAll();

        for (int i = 0; i < AC_FORUM_NAME_ARRAY.length; i++) {
            ACForumRaw raw = new ACForumRaw();
            raw.setPriority(i);
            raw.setForumid(AC_FORUM_ID_ARRAY[i]);
            raw.setDisplayname(AC_FORUM_NAME_ARRAY[i]);
            raw.setVisibility(true);
            raw.setOfficial(true);
            raw.setFrequency(0);
            dao.insert(raw);

            if (AC_FORUM_ID_ARRAY[i].equals("-1")) {
                // hardcode: -1 is the timeline id
                ForumAutoSortingUtils.pinACForum(raw);
            }
        }
    }

    private static void insertDefaultACCommonPosts() {
        ACCommonPostDao dao = sDaoSession.getACCommonPostDao();
        dao.deleteAll();

        int size = 13;
        String[] names = {
                "人，是会思考的芦苇", "丧尸图鉴", "壁纸楼", "足控福利", "淡定红茶",
                "胸器福利", "黑妹", "总有一天", "这是芦苇", "赵日天",
                "二次元女友", "什么鬼", "Banner画廊"};
        String[] ids = {
                "6064422", "585784", "117617", "103123", "114373",
                "234446", "55255", "328934", "49607", "1738904",
                "553505", "5739391", "6538597"};
        AssertUtils.assertEquals("ids.size must be size", size, ids.length);
        AssertUtils.assertEquals("names.size must be size", size, names.length);

        for (int i = 0; i < size; i++) {
            ACCommonPostRaw raw = new ACCommonPostRaw();
            raw.setName(names[i]);
            raw.setPostid(ids[i]);
            dao.insert(raw);
        }
    }

    public static List<DisplayForum> getACForums(boolean onlyVisible, boolean autoSorting) {
        ACForumDao dao = sDaoSession.getACForumDao();
        QueryBuilder<ACForumRaw> query = dao.queryBuilder();
        if (autoSorting) {
            query = query.orderDesc(ACForumDao.Properties.Frequency);
        }
        query = query.orderAsc(ACForumDao.Properties.Priority);
        List<ACForumRaw> list = query.list();
        List<DisplayForum> result = new ArrayList<>();
        for (ACForumRaw raw : list) {
            if (onlyVisible && !raw.getVisibility()) {
                continue;
            }

            DisplayForum dForum = new DisplayForum();
            dForum.site = ACSite.getInstance();
            dForum.id = raw.getForumid();
            dForum.displayname = raw.getDisplayname();
            dForum.priority = raw.getPriority();
            dForum.visibility = raw.getVisibility();
            dForum.msg = raw.getMsg();
            dForum.official = raw.getOfficial();
            result.add(dForum);
        }

        return result;
    }

    private static String getACForumName(ACForum forum) {
        String name = forum.showName;
        if (name == null || name.isEmpty()) {
            name = forum.name;
        }
        return name;
    }

    private static ACForumRaw toACForumRaw(ACForum forum, int priority, boolean visibility, int frequency) {
        ACForumRaw raw = new ACForumRaw();
        raw.setDisplayname(getACForumName(forum));
        raw.setForumid(forum.id);
        raw.setPriority(priority);
        raw.setVisibility(visibility);
        raw.setMsg(forum.msg);
        raw.setOfficial(true);
        raw.setFrequency(frequency);
        return raw;
    }

    /**
     * Add official forums.
     */
    public static void setACForums(List<ACForum> list) {
        ACForumDao dao = sDaoSession.getACForumDao();

        List<ACForumRaw> currentList = sDaoSession.getACForumDao().queryBuilder()
                .orderAsc(ACForumDao.Properties.Priority).list();
        List<ACForum> addList = new ArrayList<>(list);
        List<ACForumRaw> newList = new ArrayList<>();

        for (int i = 0, n = addList.size(); i < n; i++) {
            ACForum forum = addList.get(i);
            for (int j = 0, m = currentList.size(); j < m; j++) {
                ACForumRaw raw = currentList.get(j);
                if (ObjectUtils.equal(forum.id, raw.getForumid())) {
                    newList.add(toACForumRaw(forum, raw.getPriority(), raw.getVisibility(), raw.getFrequency()));
                    addList.remove(i);
                    i--;
                    n--;
                    break;
                }
            }
        }

        int i = getACForumMaxPriority() + 1;
        for (ACForum forum : addList) {
            newList.add(toACForumRaw(forum, i, true, 0));
            i++;
        }

        dao.deleteAll();
        dao.insertInTx(newList);
    }

    private static int getACForumMaxPriority() {
        List<ACForumRaw> list = sDaoSession.getACForumDao().queryBuilder()
                .orderDesc(ACForumDao.Properties.Priority).limit(1).list();
        if (list.isEmpty()) {
            return -1;
        } else {
            return list.get(0).getPriority();
        }
    }

    public static ACForumRaw getACForumForForumid(String id) {
        List<ACForumRaw> list = sDaoSession.getACForumDao().queryBuilder()
                .where(ACForumDao.Properties.Forumid.eq(id)).limit(1).list();
        if (list.isEmpty()) {
            return null;
        } else {
            return list.get(0);
        }
    }

    public static void removeACForum(ACForumRaw raw) {
        sDaoSession.getACForumDao().delete(raw);
    }

    public static void updateACForum(ACForumRaw raw) {
        sDaoSession.getACForumDao().update(raw);
    }

    public static LazyList<ACForumRaw> getACForumLazyList(boolean autoSorting) {
        QueryBuilder<ACForumRaw> query = sDaoSession.getACForumDao().queryBuilder();
        if (autoSorting) {
            query = query.orderDesc(ACForumDao.Properties.Frequency);
        }
        query = query.orderAsc(ACForumDao.Properties.Priority);
        return query.listLazy();
    }

    public static void setACForumVisibility(ACForumRaw raw, boolean visibility) {
        raw.setVisibility(visibility);
        sDaoSession.getACForumDao().update(raw);
    }

    public static void updateACForum(Iterable<ACForumRaw> entities) {
        sDaoSession.getACForumDao().updateInTx(entities);
    }

    public static LazyList<DraftRaw> getDraftLazyList() {
        return sDaoSession.getDraftDao().queryBuilder().orderDesc(DraftDao.Properties.Time).listLazy();
    }

    public static void addDraft(String content) {
        addDraft(content, -1);
    }

    public static void addDraft(String content, long time) {
        DraftRaw raw = new DraftRaw();
        raw.setContent(content);
        raw.setTime(time == -1 ? System.currentTimeMillis() : time);
        sDaoSession.getDraftDao().insert(raw);
    }

    public static void removeDraft(long id) {
        sDaoSession.getDraftDao().deleteByKey(id);
    }

    public static final int AC_RECORD_POST = 0;
    public static final int AC_RECORD_REPLY = 1;

    public static LazyList<ACRecordRaw> getACRecordLazyList() {
        return sDaoSession.getACRecordDao().queryBuilder().orderDesc(ACRecordDao.Properties.Time).listLazy();
    }

    public static void addACRecord(int type, String recordid, String postid, String content, String image) {
        addACRecord(type, recordid, postid, content, image, -1);
    }

    public static void addACRecord(int type, String recordid, String postid, String content, String image, long time) {
        ACRecordRaw raw = new ACRecordRaw();
        raw.setType(type);
        raw.setRecordid(recordid);
        raw.setPostid(postid);
        raw.setContent(content);
        raw.setImage(image);
        raw.setTime(time == -1 ? System.currentTimeMillis() : time);
        sDaoSession.getACRecordDao().insert(raw);
    }

    public static void removeACRecord(ACRecordRaw raw) {
        sDaoSession.getACRecordDao().delete(raw);
    }

    public static List<CommonPost> getAllACCommentPost() {
        ACCommonPostDao dao = sDaoSession.getACCommonPostDao();
        List<ACCommonPostRaw> list = dao.queryBuilder().orderAsc(ACCommonPostDao.Properties.Id).list();
        List<CommonPost> result = new ArrayList<>();
        for (ACCommonPostRaw raw : list) {
            CommonPost cp = new CommonPost();
            cp.name = raw.getName();
            cp.id = raw.getPostid();
            result.add(cp);
        }
        return result;
    }

    public static void setACCommonPost(List<CommonPost> list) {
        ACCommonPostDao dao = sDaoSession.getACCommonPostDao();
        dao.deleteAll();

        List<ACCommonPostRaw> insertList = new ArrayList<>();
        for (CommonPost cp : list) {
            ACCommonPostRaw raw = new ACCommonPostRaw();
            raw.setName(cp.name);
            raw.setPostid(cp.id);
            insertList.add(raw);
        }

        dao.insertInTx(insertList);
    }

    public static void setACForumFrequency(ACForumRaw raw, int freq) {
        raw.setFrequency(freq);
        sDaoSession.getACForumDao().update(raw);
    }
} public final class ReadableTime {

    private static Resources sResources;

    public static final long SECOND_MILLIS = 1000;
    public static final long MINUTE_MILLIS = 60 * SECOND_MILLIS;
    public static final long HOUR_MILLIS = 60 * MINUTE_MILLIS;
    public static final long DAY_MILLIS = 24 * HOUR_MILLIS;
    public static final long WEEK_MILLIS = 7 * DAY_MILLIS;
    public static final long YEAR_MILLIS = 365 * DAY_MILLIS;

    public static final int SIZE = 5;

    public static final long[] MULTIPLES = {
            YEAR_MILLIS,
            DAY_MILLIS,
            HOUR_MILLIS,
            MINUTE_MILLIS,
            SECOND_MILLIS
    };

    public static final int[] UNITS = {
            R.plurals.year,
            R.plurals.day,
            R.plurals.hour,
            R.plurals.minute,
            R.plurals.second
    };

    private static final Calendar sCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+08:00"));
    private static final Object sCalendarLock = new Object();

    private static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR =
            new SimpleDateFormat("MMM d", Locale.getDefault());

    private static final SimpleDateFormat DATE_FORMAT_WIT_YEAR =
            new SimpleDateFormat("yyy MMM d", Locale.getDefault());

    private static final SimpleDateFormat DATE_FORMAT =
            new SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault());
    private static final Object sDateFormatLock1 = new Object();

    private static final SimpleDateFormat FILENAMABLE_DATE_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.getDefault());
    private static final Object sDateFormatLock2 = new Object();

    static {
        // The website use GMT+08:00, so tell user the same
        DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));

        DATE_FORMAT_WITHOUT_YEAR.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
    }

    public static void initialize(Context context) {
        sResources = context.getApplicationContext().getResources();
    }

    public static String getDisplayTime(long time) {
        if (Settings.getPrettyTime()) {
            return getTimeAgo(time);
        } else {
            return getPlainTime(time);
        }
    }

    public static String getPlainTime(long time) {
        synchronized (sDateFormatLock1) {
            return DATE_FORMAT.format(new Date(time));
        }
    }

    public static String getTimeAgo(long time) {
        Resources resources = sResources;

        long now = System.currentTimeMillis();
        if (time > now + (2 * MINUTE_MILLIS) || time <= 0) {
            return resources.getString(R.string.from_the_future);
        }

        final long diff = now - time;
        if (diff < MINUTE_MILLIS) {
            return resources.getString(R.string.just_now);
        } else if (diff < 2 * MINUTE_MILLIS) {
            return resources.getQuantityString(R.plurals.some_minutes_ago, 1, 1);
        } else if (diff < 50 * MINUTE_MILLIS) {
            int minutes = (int) (diff / MINUTE_MILLIS);
            return resources.getQuantityString(R.plurals.some_minutes_ago, minutes, minutes);
        } else if (diff < 90 * MINUTE_MILLIS) {
            return resources.getQuantityString(R.plurals.some_hours_ago, 1, 1);
        } else if (diff < 24 * HOUR_MILLIS) {
            int hours = (int) (diff / HOUR_MILLIS);
            return resources.getQuantityString(R.plurals.some_hours_ago, hours, hours);
        } else if (diff < 48 * HOUR_MILLIS) {
            return resources.getString(R.string.yesterday);
        } else if (diff < WEEK_MILLIS) {
            int days = (int) (diff / DAY_MILLIS);
            return resources.getString(R.string.some_days_ago, days);
        } else {
            synchronized (sCalendarLock) {
                Date nowDate = new Date(now);
                Date timeDate = new Date(time);
                sCalendar.setTime(nowDate);
                int nowYear = sCalendar.get(Calendar.YEAR);
                sCalendar.setTime(timeDate);
                int timeYear = sCalendar.get(Calendar.YEAR);

                if (nowYear == timeYear) {
                    return DATE_FORMAT_WITHOUT_YEAR.format(timeDate);
                } else {
                    return DATE_FORMAT_WIT_YEAR.format(timeDate);
                }
            }
        }
    }

    public static String getTimeInterval(long time) {
        StringBuilder sb = new StringBuilder();
        Resources resources = sResources;

        long leftover = time;
        boolean start = false;

        for (int i = 0; i < SIZE; i++) {
            long multiple = MULTIPLES[i];
            long quotient = leftover / multiple;
            long remainder = leftover % multiple;
            if (start || quotient != 0 || i == SIZE - 1) {
                if (start) {
                    sb.append(" ");
                }
                sb.append(quotient)
                        .append(" ")
                        .append(resources.getQuantityString(UNITS[i], (int) quotient));
                start = true;
            }
            leftover = remainder;
        }

        return sb.toString();
    }

    public static String getFilenamableTime(long time) {
        synchronized (sDateFormatLock2) {
            return FILENAMABLE_DATE_FORMAT.format(new Date(time));
        }
    }
} public final class Settings {

    private static Context sContext;
    private static SharedPreferences sSettingsPre;

    private static boolean sCheckExtendFeedId = false;
    private static String sExtendFeedId = null;

    public static void initialize(Context context) {
        sContext = context.getApplicationContext();
        sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
    }

    public static boolean getBoolean(String key, boolean defValue) {
        return sSettingsPre.getBoolean(key, defValue);
    }

    public static void putBoolean(String key, boolean value) {
        sSettingsPre.edit().putBoolean(key, value).apply();
    }

    public static int getInt(String key, int defValue) {
        return sSettingsPre.getInt(key, defValue);
    }

    public static void putInt(String key, int value) {
        sSettingsPre.edit().putInt(key, value).apply();
    }

    public static long getLong(String key, long defValue) {
        return sSettingsPre.getLong(key, defValue);
    }

    public static void putLong(String key, long value) {
        sSettingsPre.edit().putLong(key, value).apply();
    }

    public static float getFloat(String key, float defValue) {
        return sSettingsPre.getFloat(key, defValue);
    }

    public static void putFloat(String key, float value) {
        sSettingsPre.edit().putFloat(key, value).apply();
    }

    public static String getString(String key, String defValue) {
        return sSettingsPre.getString(key, defValue);
    }

    public static void putString(String key, String value) {
        sSettingsPre.edit().putString(key, value).apply();
    }

    public static Set<String> getStringSet(String key, Set<String> defValue) {
        return sSettingsPre.getStringSet(key, defValue);
    }

    public static void putStringSet(String key, Set<String> value) {
        sSettingsPre.edit().remove(key).apply(); // See: https://bon-app-etit.blogspot.com/2013/08/android-bug-stringset-in.html
        sSettingsPre.edit().putStringSet(key, value).apply();
    }

    public static int getIntFromStr(String key, int defValue) {
        return NumberUtils.parseIntSafely(sSettingsPre.getString(key, Integer.toString(defValue)), defValue);
    }

    public static void putIntToStr(String key, int value) {
        sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
    }

    public static final int IMAGE_LOADING_STRATEGY_ALL = 0;
    public static final int IMAGE_LOADING_STRATEGY_WIFI = 1;
    public static final int IMAGE_LOADING_STRATEGY_NO = 2;

    public static final String KEY_DARK_THEME = "dark_theme";
    public static final boolean DEFAULT_DARK_THEME = false;

    public static final String KEY_PRETTY_TIME = "pretty_time";
    public static final boolean DEFAULT_PRETTY_TIME = true;

    public static final String KEY_FONT_SIZE = "font_size";
    public static final int DEFAULT_FONT_SIZE = 16;

    public static final String KEY_LINE_SPACING = "line_spacing";
    public static final int DEFAULT_LINE_SPACING = 1;

    public static final String KEY_DYNAMIC_COMMENTS = "dynamic_comments";
    public static final boolean DEFAULT_DYNAMIC_COMMENTS = true;

    public static final String KEY_FAST_SCROLLER = "fast_scroller";
    public static final boolean DEFAULT_FAST_SCROLLER = true;

    public static final String KEY_COLOR_STATUS_BAR = "color_status_bar";
    public static final boolean DEFAULT_COLOR_STATUS_BAR = true;

    public static final String KEY_FIX_EMOJI_DISPLAY = "fix_emoji_display";
    public static final boolean DEFAULT_FIX_EMOJI_DISPLAY = false;

    public static final String KEY_WATERMARK = "watermark";
    public static final boolean DEFAULT_WATERMARKD = true;

    public static final String KEY_FEED_ID = "feed_id";
    public static final String DEFAULT_FEED_ID = null;

    public static final String KEY_SAVE_IMAGE_AUTO = "save_image_auto";
    public static final boolean DEFAULT_SAVE_IMAGE_AUTO = false;

    public static final String KEY_IMAGE_LOADING_STRATEGY = "image_loading_strategy";
    public static final int DEFAULT_IMAGE_LOADING_STRATEGY = 0;
    public static final String KEY_IMAGE_SAVE_LOACTION = "image_save_location";

    public static final String KEY_IMAGE_LOADING_STRATEGY_2 = "image_loading_strategy_2";
    public static final boolean DEFAULT_IMAGE_LOADING_STRATEGY_2 = false;

    public static final String KEY_IMAGE_SAVE_SCHEME = "image_scheme";
    public static final String KEY_IMAGE_SAVE_AUTHORITY = "image_authority";
    public static final String KEY_IMAGE_SAVE_PATH = "image_path";
    public static final String KEY_IMAGE_SAVE_QUERY = "image_query";
    public static final String KEY_IMAGE_SAVE_FRAGMENT = "image_fragment";

    public static final String KEY_CHAOS_LEVEL = "chaos_level";
    public static final int DEFAULT_CHAOS_LEVEL = 0;

    public static final String KEY_SET_ANALYSIS = "set_analysis";
    public static final boolean DEFAULT_SET_ANALYSIS = false;
    public static final String KEY_ANALYSIS = "analysis";
    public static final boolean DEFAULT_ANALYSIS = false;

    public static final String KEY_FORUM_AUTO_SORTING = "forum_auto_sorting";
    public static final boolean DEFAULT_FORUM_AUTO_SORTING = false;

    public static final String KEY_LAST_FORUM_AGING = "last_forum_aging";
    public static final long DEFAULT_LAST_FORUM_AGING = 0;

    public static boolean getDarkTheme() {
        return getBoolean(KEY_DARK_THEME, DEFAULT_DARK_THEME);
    }

    public static void putDarkTheme(boolean value) {
        putBoolean(KEY_DARK_THEME, value);
    }

    public static boolean getPrettyTime() {
        return getBoolean(KEY_PRETTY_TIME, DEFAULT_PRETTY_TIME);
    }

    public static int getFontSize() {
        return getInt(KEY_FONT_SIZE, DEFAULT_FONT_SIZE);
    }

    public static void putFontSize(int value) {
        putInt(KEY_FONT_SIZE, value);
    }

    public static int getLineSpacing() {
        return getInt(KEY_LINE_SPACING, DEFAULT_LINE_SPACING);
    }

    public static void putLineSpacing(int value) {
        putInt(KEY_LINE_SPACING, value);
    }

    public static boolean getDynamicComments() {
        return getBoolean(KEY_DYNAMIC_COMMENTS, DEFAULT_DYNAMIC_COMMENTS);
    }

    public static boolean getFastScroller() {
        return getBoolean(KEY_FAST_SCROLLER, DEFAULT_FAST_SCROLLER);
    }

    public static boolean getColorStatusBar() {
        return getBoolean(KEY_COLOR_STATUS_BAR, DEFAULT_COLOR_STATUS_BAR);
    }

    public static boolean getFixEmojiDisplay() {
        return getBoolean(KEY_FIX_EMOJI_DISPLAY, DEFAULT_FIX_EMOJI_DISPLAY);
    }

    public static boolean getWatermark() {
        return getBoolean(KEY_WATERMARK, DEFAULT_WATERMARKD);
    }

    public static String getFeedId() {
        if (!sCheckExtendFeedId) {
            sCheckExtendFeedId = true;
            // Get extend feed id
            File feedIdFile = NMBAppConfig.getFileInAppDir(KEY_FEED_ID);
            if (feedIdFile != null) {
                FileInputStream fis;
                try {
                    fis = new FileInputStream(feedIdFile);
                    String feedId = IOUtils.readString(fis, "UTF-8");
                    if (!TextUtils.isEmpty(feedId)) {
                        sExtendFeedId = feedId;
                        putFeedId(sExtendFeedId);
                    }
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        if (sExtendFeedId != null) {
            return sExtendFeedId;
        } else {
            String feedId = getString(KEY_FEED_ID, DEFAULT_FEED_ID);
            if (!TextUtils.isEmpty(feedId)) {
                return feedId;
            } else {
                feedId = getRandomFeedId();
                putFeedId(feedId);
                return feedId;
            }
        }
    }

    public static void putFeedId(String value) {
        putString(KEY_FEED_ID, value);

        sExtendFeedId = value;

        // Put it to file
        File file = NMBAppConfig.getFileInAppDir(KEY_FEED_ID);
        if (file != null) {
            FileOutputStream os = null;
            try {
                os = new FileOutputStream(file);
                IOUtils.copy(new ByteArrayInputStream(value.getBytes()), os);
                os.flush();
            } catch (IOException e) {
                file.delete();
            } finally {
                IOUtils.closeQuietly(os);
            }
        }
    }

    public static @NonNull String getRandomFeedId() {
        int length = 20;
        StringBuilder sb = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            if (MathUtils.random(0, 1 + 1) == 0) {
                sb.append((char) MathUtils.random('a', 'z' + 1));
            } else {
                sb.append((char) MathUtils.random('0', '9' + 1));
            }
        }

        return sb.toString();
    }

    public static String getMacFeedId() {
        WifiManager wifi = (WifiManager) sContext.getSystemService(Context.WIFI_SERVICE);
        WifiInfo info = wifi.getConnectionInfo();
        if (info == null) {
            return null;
        }

        String mac = info.getMacAddress();
        if (mac == null) {
            return null;
        }

        String id;
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(mac.getBytes());
            id = bytesToHexString(digest.digest());
        } catch (NoSuchAlgorithmException e) {
            id = String.valueOf(mac.hashCode());
        }

        return id;
    }

    public static boolean getSaveImageAuto() {
        return getBoolean(KEY_SAVE_IMAGE_AUTO, DEFAULT_SAVE_IMAGE_AUTO);
    }

    public static int getImageLoadingStrategy() {
        return getIntFromStr(KEY_IMAGE_LOADING_STRATEGY, DEFAULT_IMAGE_LOADING_STRATEGY);
    }

    public static boolean getImageLoadingStrategy2() {
        return getBoolean(KEY_IMAGE_LOADING_STRATEGY_2, DEFAULT_IMAGE_LOADING_STRATEGY_2);
    }

    @Nullable
    public static UniFile getImageSaveLocation() {
        UniFile dir = null;
        try {
            Uri.Builder builder = new Uri.Builder();
            builder.scheme(getString(KEY_IMAGE_SAVE_SCHEME, null));
            builder.encodedAuthority(getString(KEY_IMAGE_SAVE_AUTHORITY, null));
            builder.encodedPath(getString(KEY_IMAGE_SAVE_PATH, null));
            builder.encodedQuery(getString(KEY_IMAGE_SAVE_QUERY, null));
            builder.encodedFragment(getString(KEY_IMAGE_SAVE_FRAGMENT, null));
            dir = UniFile.fromUri(sContext, builder.build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dir != null ? dir : UniFile.fromFile(NMBAppConfig.getImageDir());
    }

    public static void putImageSaveLocation(@NonNull UniFile location) {
        Uri uri = location.getUri();
        putString(KEY_IMAGE_SAVE_SCHEME, uri.getScheme());
        putString(KEY_IMAGE_SAVE_AUTHORITY, uri.getEncodedAuthority());
        putString(KEY_IMAGE_SAVE_PATH, uri.getEncodedPath());
        putString(KEY_IMAGE_SAVE_QUERY, uri.getEncodedQuery());
        putString(KEY_IMAGE_SAVE_FRAGMENT, uri.getEncodedFragment());
    }

    public static int getChaosLevel() {
        return getIntFromStr(KEY_CHAOS_LEVEL, DEFAULT_CHAOS_LEVEL);
    }

    public static boolean getSetAnalysis() {
        return getBoolean(KEY_SET_ANALYSIS, DEFAULT_SET_ANALYSIS);
    }

    public static void putSetAnalysis(boolean value) {
        putBoolean(KEY_SET_ANALYSIS, value);
    }

    public static boolean getAnalysis() {
        return getBoolean(KEY_ANALYSIS, DEFAULT_ANALYSIS);
    }

    public static void putAnalysis(boolean value) {
        putBoolean(KEY_ANALYSIS, value);
    }

    public static boolean getForumAutoSorting() {
        return getBoolean(KEY_FORUM_AUTO_SORTING, DEFAULT_FORUM_AUTO_SORTING);
    }

    public static void putForumAutoSorting(boolean value) {
        putBoolean(KEY_FORUM_AUTO_SORTING, value);
    }

    public static long getLastForumAging() {
        return getLong(KEY_LAST_FORUM_AGING, DEFAULT_LAST_FORUM_AGING);
    }

    public static void setLastForumAging(long value) {
        putLong(KEY_LAST_FORUM_AGING, value);
    }

    private static final String KEY_VERSION_CODE = "version_code";
    private static final int DEFAULT_VERSION_CODE = 0;

    public static int getVersionCode() {
        return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
    }

    public static void putVersionCode(int value) {
        putInt(KEY_VERSION_CODE, value);
    }

    public static final String KEY_GUIDE_LIST_ACTIVITY = "guide_list_activity";
    public static final boolean VALUE_GUIDE_LIST_ACTIVITY = true;

    public static boolean getGuideListActivity() {
        return getBoolean(KEY_GUIDE_LIST_ACTIVITY, VALUE_GUIDE_LIST_ACTIVITY);
    }

    public static void putGuideListActivity(boolean value) {
        putBoolean(KEY_GUIDE_LIST_ACTIVITY, value);
    }

    public static final String KEY_NEW_ICON = "new_icon";
    public static final boolean VALUE_NEW_ICON = true;

    public static boolean getNewIcon() {
        return getBoolean(KEY_NEW_ICON, VALUE_NEW_ICON);
    }

    public static void putNewIcon(boolean value) {
        putBoolean(KEY_NEW_ICON, value);
    }

    public static final String[] ICON_ACTIVITY_ARRAY = {
            "com.hippo.nimingban.ui.ListActivity",
            "com.hippo.nimingban.ui.ListActivityIbuki",
    };

    public static String getDefaultIconActivity() {
        return ICON_ACTIVITY_ARRAY[0];
    }

    public static String getCurrentIconActivity() {
        return ICON_ACTIVITY_ARRAY[getIntFromStr("app_icon", 0)];
    }

    public static final String KEY_GUIDE_SORT_FORUMS_ACTIVITY = "guide_sort_forums_activity";
    public static final boolean VALUE_GUIDE_SORT_FORUMS_ACTIVITY = true;

    public static boolean getGuideSortForumsActivity() {
        return getBoolean(KEY_GUIDE_SORT_FORUMS_ACTIVITY, VALUE_GUIDE_SORT_FORUMS_ACTIVITY);
    }

    public static void putGuideSortForumsActivity(boolean value) {
        putBoolean(KEY_GUIDE_SORT_FORUMS_ACTIVITY, value);
    }

    public static final String KEY_GUIDE_SORTING_FOUR_BARS = "guide_sorting_four_bars";
    public static final boolean VALUE_GUIDE_SORTING_FOUR_BARS = true;

    public static boolean getGuideSortingFourBars() {
        return getBoolean(KEY_GUIDE_SORTING_FOUR_BARS, VALUE_GUIDE_SORTING_FOUR_BARS);
    }

    public static void putGuideSortingFourBars(boolean value) {
        putBoolean(KEY_GUIDE_SORTING_FOUR_BARS, value);
    }

    public static final String KEY_GUIDE_PINNING_STAR = "guide_pinning_star";
    public static final boolean VALUE_GUIDE_PINNING_STAR = true;

    public static boolean getGuidePinningStar() {
        return getBoolean(KEY_GUIDE_PINNING_STAR, VALUE_GUIDE_PINNING_STAR);
    }

    public static void putGuidePinningStar(boolean value) {
        putBoolean(KEY_GUIDE_PINNING_STAR, value);
    }

    public static final String KEY_GUIDE_TYPE_SEND = "guide_type_send";
    public static final boolean VALUE_GUIDE_TYPE_SEND = true;

    public static boolean getGuideTypeSend() {
        return getBoolean(KEY_GUIDE_TYPE_SEND, VALUE_GUIDE_TYPE_SEND);
    }

    public static void putGuideTypeSend(boolean value) {
        putBoolean(KEY_GUIDE_TYPE_SEND, value);
    }

    public static final String KEY_NOTICE_DATE = "notice_date";
    public static final long VALUE_NOTICE_DATE = -1;

    public static long getNoticeDate() {
        return getLong(KEY_NOTICE_DATE, VALUE_NOTICE_DATE);
    }

    public static void putNoticeDate(long value) {
        putLong(KEY_NOTICE_DATE, value);
    }

    public static final String KEY_CRASH_FILENAME = "crash_filename";
    public static final String VALUE_CRASH_FILENAME = null;

    public static String getCrashFilename() {
        return getString(KEY_CRASH_FILENAME, VALUE_CRASH_FILENAME);
    }

    @SuppressLint("CommitPrefEdits")
    public static void putCrashFilename(String value) {
        sSettingsPre.edit().putString(KEY_CRASH_FILENAME, value).commit();
    }

    public static final String KEY_AC_HOST = "ac_host";
    public static final String VALUE_AC_HOST = "https://adnmb1.com";

    public static String getAcHost() {
        return getString(KEY_AC_HOST, VALUE_AC_HOST);
    }

    public static void putAcHost(String value) {
        putString(KEY_AC_HOST, value);
    }

    public static final String KEY_ENABLE_CUSTOMIZED_AC_HOST = "enable_customized_ac_host";
    public static final boolean VALUE_ENABLE_CUSTOMIZED_AC_HOST = false;

    public static boolean getEnableCustomizedAcHost() {
        return getBoolean(KEY_ENABLE_CUSTOMIZED_AC_HOST, VALUE_ENABLE_CUSTOMIZED_AC_HOST);
    }

    public static final String KEY_CUSTOMIZED_AC_HOST = "customized_ac_host";
    public static final String VALUE_CUSTOMIZED_AC_HOST = "https://adnmb1.com";

    public static String getCustomizedAcHost() {
        return getString(KEY_CUSTOMIZED_AC_HOST, VALUE_CUSTOMIZED_AC_HOST);
    }

    public static void putCustomizedAcHost(String value) {
        putString(KEY_CUSTOMIZED_AC_HOST, value);
    }

    public static final String KEY_STRICT_IGNORE_MODE = "strict_ignore_post_mode";
    public static final boolean VALUE_STRICT_IGNORE_MODE = false;

    public static boolean getEnableStrictIgnoreMode() {
        return getBoolean(KEY_STRICT_IGNORE_MODE, VALUE_STRICT_IGNORE_MODE);
    }

    /**
     * http://stackoverflow.com/questions/332079
     *
     * @param bytes The bytes to convert.
     * @return A {@link String} converted from the bytes of a hashable key used
     *         to store a filename on the disk, to hex digits.
     */
    private static String bytesToHexString(final byte[] bytes) {
        final StringBuilder builder = new StringBuilder();
        for (final byte b : bytes) {
            final String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                builder.append('0');
            }
            builder.append(hex);
        }
        return builder.toString();
    }
} public class FontTextView extends TextView {

    static Typeface sTypeface;

    private Typeface mOriginalTypeface;

    public FontTextView(Context context) {
        super(context);
        init(context);
    }

    public FontTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public FontTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void ensureTypeface(Context context) {
        if (sTypeface == null) {
            sTypeface = Typeface.createFromAsset(context.getAssets(), "missing_characters.ttf");
        }
    }

    private void init(Context context) {
        mOriginalTypeface = getTypeface();
        ensureTypeface(context);

        if (Settings.getFixEmojiDisplay()) {
            useCustomTypeface();
        }
    }

    public void useCustomTypeface() {
        setTypeface(sTypeface);
    }

    public void useOriginalTypeface() {
        setTypeface(mOriginalTypeface);
    }
} public class NMBEditText extends AppCompatEditText {

    private Typeface mOriginalTypeface;

    public NMBEditText(Context context) {
        super(context);
        init(context);
    }

    public NMBEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public NMBEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void ensureTypeface(Context context) {
        if (FontTextView.sTypeface == null) {
            FontTextView.sTypeface = Typeface.createFromAsset(context.getAssets(), "missing_characters.ttf");
        }
    }

    private void init(Context context) {
        mOriginalTypeface = getTypeface();
        ensureTypeface(context);

        if (Settings.getFixEmojiDisplay()) {
            useCustomTypeface();
        }
    }

    public void useCustomTypeface() {
        setTypeface(FontTextView.sTypeface);
    }

    public void useOriginalTypeface() {
        setTypeface(mOriginalTypeface);
    }

    @Override
    public boolean onTextContextMenuItem(int id) {
        // Get text in clipboard
        ClipboardManager cbm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        if (cbm.hasPrimaryClip()) {
            ClipData clipData = cbm.getPrimaryClip();
            if (clipData.getItemCount() > 0) {
                // Convert to plain text
                CharSequence text = clipData.getItemAt(0).getText();
                if (text != null) {
                    cbm.setPrimaryClip(ClipData.newPlainText(null, text.toString()));
                }
            }
        }

        return super.onTextContextMenuItem(id);
    }
} public final class ExceptionUtils {

    private static final String TAG = ExceptionUtils.class.getSimpleName();

    @NonNull
    public static String getReadableString(@NonNull Context context, @NonNull Exception e) {
        if (e instanceof ConnectTimeoutException ||
                e instanceof SocketTimeoutException) {
            return context.getString(R.string.em_timeout);
        } else if (e instanceof UnknownHostException) {
            return context.getString(R.string.em_unknown_host);
        } else if (e instanceof ResponseCodeException) {
            ResponseCodeException responseCodeException = (ResponseCodeException) e;
            String error = context.getString(R.string.em_response_code, responseCodeException.getResponseCode());
            if (responseCodeException.isIdentifiedResponseCode()) {
                error += ", " + responseCodeException.getMessage();
            }
            return error;
        } else if (e instanceof ProtocolException && e.getMessage().startsWith("Too many follow-up requests:")) {
            return context.getString(R.string.em_redirection);
        } else if (e instanceof SocketException) {
            return context.getString(R.string.em_socket);
        } else if (e instanceof NMBException) {
            return e.getMessage();
        } else {
            Say.d(TAG, "Can't recognize this Exception", e);
            return context.getString(R.string.em_unknown);
        }
    }

    public static @Nullable String getReasonString(Context context, Exception e) {
        /*
        if (e instanceof UnknownHostException) {
            return context.getString(R.string.erm_unknown_host);
        } else {
            return null;
        }
        */
        return null;
    }
}
import android.app.Activity;
import android.app.Dialog;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.Toolbar;
import android.text.Layout;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.hippo.app.ProgressDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.SimpleHolder;
import com.hippo.io.UriInputStreamPipe;
import com.hippo.nimingban.Constants;
import com.hippo.nimingban.Emoji;
import com.hippo.nimingban.NMBAppConfig;
import com.hippo.nimingban.NMBApplication;
import com.hippo.nimingban.R;
import com.hippo.nimingban.client.NMBClient;
import com.hippo.nimingban.client.NMBRequest;
import com.hippo.nimingban.client.ac.ACUrl;
import com.hippo.nimingban.client.ac.data.ACPostStruct;
import com.hippo.nimingban.client.ac.data.ACReplyStruct;
import com.hippo.nimingban.client.data.ACSite;
import com.hippo.nimingban.client.data.DisplayForum;
import com.hippo.nimingban.client.data.Site;
import com.hippo.nimingban.content.UniversalProvider;
import com.hippo.nimingban.drawable.RoundSideDrawable;
import com.hippo.nimingban.network.SimpleCookieStore;
import com.hippo.nimingban.ui.DoodleActivity;
import com.hippo.nimingban.ui.DraftActivity;
import com.hippo.nimingban.ui.GalleryActivity2;
import com.hippo.nimingban.ui.QRCodeScanActivity;
import com.hippo.nimingban.util.BitmapUtils;
import com.hippo.nimingban.util.DB;
import com.hippo.nimingban.util.OpenUrlHelper;
import com.hippo.nimingban.util.ReadableTime;
import com.hippo.nimingban.util.Settings;
import com.hippo.nimingban.widget.FontTextView;
import com.hippo.nimingban.widget.NMBEditText;
import com.hippo.ripple.Ripple;
import com.hippo.util.DrawableManager;
import com.hippo.util.ExceptionUtils;
import com.hippo.widget.SimpleImageView;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.Messenger;
import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
/*
 * Copyright 2015 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.nimingban.ui.fragment;




public final class TypeSendFragment extends BaseFragment implements View.OnClickListener {

    private static final String TAG = TypeSendFragment.class.getSimpleName();

    @IntDef({METHOD_REPLY, METHOD_CREATE_POST})
    @Retention(RetentionPolicy.SOURCE)
    private @interface Method {}

    public static final String ACTION_REPLY = "com.hippo.nimingban.ui.TypeSendActivity.action.REPLY";
    public static final String ACTION_CREATE_POST = "com.hippo.nimingban.ui.TypeSendActivity.action.CREATE_POST";
    public static final String ACTION_REPORT = "com.hippo.nimingban.ui.TypeSendActivity.action.REPORT";

    public static final String KEY_ACTION = "action";
    public static final String KEY_TYPE = "type";
    public static final String KEY_SITE = "site";
    public static final String KEY_ID = "id";
    public static final String KEY_TEXT = "text";
    public static final String KEY_EXTRA_TEXT = Intent.EXTRA_TEXT;
    public static final String KEY_EXTRA_STREAM = Intent.EXTRA_STREAM;

    public static final int REQUEST_CODE_SELECT_IMAGE = 0;
    public static final int REQUEST_CODE_DRAFT = 1;
    public static final int REQUEST_CODE_DOODLE = 2;
    public static final int REQUEST_CODE_CAMERA = 3;

    public static final int METHOD_REPLY = 0;
    public static final int METHOD_CREATE_POST = 1;

    private List<DisplayForum> mForums;
    private CharSequence[] mForumNames;

    private boolean mReport;

    @Method
    private int mMethod = METHOD_CREATE_POST;
    private Site mSite;
    private String mId;
    private String mTitleText;
    private String mPresetText;

    private Uri mSeletedImageUri;
    private String mSeletedImageType;
    private Bitmap mSeletedImageBitmap;

    private Uri mCameraImageUri;

    private NMBClient mNMBClient;

    private NMBEditText mEditText;
    private SimpleImageView mEmoji;
    private View mImage;
    private View mDraw;
    private View mDraft;
    private View mSend;
    private View mImagePreview;
    private ImageView mPreview;
    private View mDelete;
    private ImageView mIndicator;
    private View mWritableItem;
    private EditText mName;
    private EditText mEmail;
    private EditText mTitle;
    private CheckBox mWatermark;
    private TextView mForumText;
    private EasyRecyclerView mEmojiKeyboard;

    private Drawable mEmojiOff;
    private Drawable mEmojiOn;

    private Dialog mProgressDialog;
    private NMBRequest mNMBRequest;

    private Callback mCallback;

    private IWXAPI mWxApi;

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    private boolean handleArgs(Bundle args) {
        if (args == null) {
            return false;
        }

        boolean share = false;
        mReport = false;
        String action = args.getString(KEY_ACTION);
        String type = args.getString(KEY_TYPE);

        if (ACTION_REPLY.equals(action)) {
            mMethod = METHOD_REPLY;
            mTitleText = getString(R.string.reply);
        } else if (ACTION_CREATE_POST.equals(action)) {
            mMethod = METHOD_CREATE_POST;
            mTitleText = getString(R.string.create_post);
        } else if (ACTION_REPORT.equals(action)) {
            mMethod = METHOD_CREATE_POST;
            mTitleText = getString(R.string.report);
            mReport = true;
        } else if (Intent.ACTION_SEND.equals(action) && type != null) {
            mMethod = METHOD_CREATE_POST;
            mTitleText = getString(R.string.create_post);
            share = true;
        } else {
            return false;
        }

        if (share && type != null) {
            // Share
            // TODO for other site
            mSite = ACSite.getInstance();
            if ("text/plain".equals(type)) {
                mPresetText = args.getString(KEY_EXTRA_TEXT);
                return true;
            } else if (type.startsWith("image/")) {
                mSeletedImageUri = args.getParcelable(KEY_EXTRA_STREAM);
                return true;
            }
        } else {
            int site = args.getInt(KEY_SITE, -1);
            String id = args.getString(KEY_ID);
            mPresetText = args.getString(KEY_TEXT);
            if (Site.isValid(site) && id != null) {
                mSite = Site.fromId(site);
                mId = id;
                return true;
            }
        }

        return false;
    }

    private void prepareForCreatePost() {
        // Get all forums
        boolean sorting = Settings.getForumAutoSorting();
        mForums = DB.getACForums(false, sorting);