package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_HIGH;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_MID;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_LOW;

import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.text.ClipboardManager;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebView.HitTestResult;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.gr.java_conf.shiseissi.commonlib.APILevelWrapper;
import jp.gr.java_conf.shiseissi.commonlib.ViewUtil;

// TODO videoNumberとThumbInfoでなくWatchVideoを中心にした処理に変更

public class WebBrowserFragment extends Fragment
implements Handler.Callback {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

    public static final String DOMAIN_NICO_JP = "www.nicovideo.jp";
    public static final String DOMAIN_NICO_TW = "tw.nicovideo.jp";
    public static final String DOMAIN_NICO_ES = "es.nicovideo.jp";
    public static final String DOMAIN_NICO_DE = "de.nicovideo.jp";
    public static final String DOMAIN_NICO_NINE = "nine.nicovideo.jp";

    public static final String PATTERN_VIDEO_URL =
        "http://(?:www|tw|es|de|nine|sp)\\.nicovideo\\.jp/watch/([a-z0-9]+)";
    public static final String PATTERN_GET_USERID_FROM_USERSESSION =
        "user_session=user_session_([^_]+)_";
    private static final String PATTERN_IS_NOT_HTML =
        "(\\.jpg|\\.jpeg|\\.JPG|\\.JPEG|\\.png|\\.PNG|\\.gif|\\.GIF|\\.js|\\.JS|\\.css|\\.CSS)$";
    private static final String PATTERN_JIKKYO_URL =
        "http://jk\\.nicovideo\\.jp/watch/([a-z0-9]+)";
    private static final String PATTERN_JIKKYO_APP =
        "^nicojk:(.+)";
    private static final String PATTERN_LIVE_URL =
        "http://(?:sp\\.)?live\\.nicovideo\\.jp/watch/([a-z0-9]+)";
    private static final String PATTERN_GET_TITLE =
        "^(.+)(?:\\s*-\\s*)?ニコニコ生放送\\s*$";
    public static final String PATTERN_VIDEO_URL_HOST =
        "(http://(?:www|tw|es|de|nine|sp)\\.nicovideo\\.jp/watch/).*";
    private static final String PATTERN_CAN_SWITCH_PC_SP =
        "https?://(?:www|live|ch|sp|sp\\.live|sp\\.ch)\\.nicovideo\\.jp";
    private static final String PATTERN_MYLIST_URL =
        "http://(?:www|tw|es|de|nine|sp)\\.nicovideo\\.jp/mylist/([0-9]+)";
    private static final String PATTERN_PLAYLIST_URL =
        "http://(?:www|tw|es|de|nine|sp)\\.nicovideo\\.jp/playlist/mylist/([0-9]+)";

    private static final String URL_ICON_DIC_ON =
        "http://res.nimg.jp/img/common/icon/dic_on.png";
    private static final String URL_DIC_A = "http://dic.nicovideo.jp/a/";

    private static final int MSG_ID_RUN_CACHING = 0;
    private static final int MSG_ID_FINISHED_CACHE = 1;
    private static final int MSG_ID_UPDATE_TITLE_URL = 2;
    private static final int MSG_ID_STARTED_CACHE = 3;
    private static final int MSG_ID_ACTION_START_PLAY = 4;
    private static final int MSG_ID_ACTION_START_CACHE = 5;
    private static final int MSG_ID_ACTION_RELATED_VIDEO = 6;
    private static final int MSG_ID_ACTION_DELETE_CACHE = 7;
    private static final int MSG_ID_ACTION_START_JIKKYO = 8;
    private static final int MSG_ID_ACTION_START_LIVE = 9;
    private static final int MSG_ID_GO_BACK_WEBVIEW = 10;
    private static final int MSG_ID_FLASH_SET_POS = 11;
    private static final int MSG_ID_FLASH_MATCH_FULL = 12;
    private static final int MSG_ID_UPDATE_OPTIONS_MENU = 13;
    private static final int MSG_ID_ACTION_ADD_MYLIST = 14;
    private static final int MSG_ID_FINISHED_READ_VIDEO = 15;
    private static final int MSG_ID_ACTION_STOP_CACHE = 16;
    private static final int MSG_ID_ON_UPDATE_VIDEO = 17;

    private static final int WEBVIEW_POOL_MIN_SIZE = 5;

    private static final int REQUEST_PLAY_VIDEO = 0;

    private static final String TAG_VIDEO_CACHE_SERVICE = "WebBrowserFragment";

    private class NicoroWebChromeClient extends WebChromeClient {
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebChromeClient#onProgressChanged: newProgress=")
                        .append(newProgress).toString());
            }
            super.onProgressChanged(view, newProgress);
//            NicoroWebBrowser.this.setProgress(newProgress * 100);
            mProgressLoadPage.setProgress(newProgress);
            if (newProgress < 100) {
                mProgressLoadPage.setVisibility(View.VISIBLE);
            } else {
                mProgressLoadPage.setVisibility(View.GONE);
            }

            // TODO newProgressが適当
            if (newProgress > 30 && mJsObj.urlRss == null) {
//                assert view == mWebView;
                view.loadUrl(mResJsFindRss);
            }

            if (!mWasJsCalled && newProgress > 70) {
                mJsObj.video = null;
                view.loadUrl(mResJsReadVideo);
                if (mWithoutFlash) {
                    view.loadUrl(mResJsReplacePlayerbox);
                }
                view.loadUrl(mResJsGetStreamDescription);
                view.loadUrl(mJsMarginBottom);
                mWasJsCalled = true;
            }
        }
        @Override
        public void onReceivedTitle(WebView view, String title) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebChromeClient#onReceivedTitle: title=")
                        .append(title)
                        .toString());
            }
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_TITLE_URL);
        }
        @Override
        public boolean onCreateWindow(WebView view, boolean dialog,
                boolean userGesture, Message resultMsg) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebChromeClient#onCreateWindow: dialog=")
                        .append(dialog).append(" userGesture=")
                        .append(userGesture).append(" resultMsg=")
                        .append(resultMsg)
                        .toString());
            }
            assert view == mWebView;
            ViewGroup parent = (ViewGroup) view.getParent();
            if (parent == null) {
                assert false;
                return false;
            }
            LayoutInflater inflater = mLayoutInflater;
            WebView newWebView = ViewUtil.inflate(inflater,
                    R.layout.nicoro_webbrowser_webview,
                    parent, false);
            initializeWebView(newWebView);
            ViewUtil.replaceView(view, newWebView);

            mWebView = newWebView;
            mWebViewPool.addLast(view);
            mVideoPool.addLast(mVideo);
            mLiveVideoPool.addLast(mLiveVideo);
            mVideo = null;
            mLiveVideo = null;

            ((WebView.WebViewTransport) resultMsg.obj).setWebView(newWebView);
            resultMsg.sendToTarget();
            return true;
        }
        @Override
        public void onCloseWindow(WebView window) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebChromeClient#onCloseWindow: window=")
                        .append(window)
                        .toString());
            }
            assert window == mWebView;
            if (mWebViewPool.isEmpty()) {
                // TODO ここでActivity終わらせてしまって良いかどうか
                getActivity().finish();
            } else {
                assert window.getParent() != null;
                WebView lastWebView = mWebViewPool.removeLast();
                WatchVideo lastVideo = mVideoPool.removeLast();
                WatchVideo lastLiveVideo = mLiveVideoPool.removeLast();
                ViewUtil.replaceView(window, lastWebView);
                mWebView = lastWebView;
                mVideo = lastVideo;
                mLiveVideo = lastLiveVideo;
                String url = lastWebView.getUrl();
                updateLastUrl(url);
                unregisterForContextMenu(window);
                window.destroy();
                // XXX onChangePage前に復帰値で上書き
                synchronized (mEnableMenuJudgeAsync) {
                    mEnableMenuJudgeAsync.clearLastParameter();
                }
                if (lastVideo != null) {
                    mJsObj.updateVideo(lastVideo);
                } else if (lastLiveVideo != null) {
                    mJsObj.updateVideo(lastLiveVideo);
                }
                onChangePage(lastWebView, url);
                mHandler.sendEmptyMessage(MSG_ID_UPDATE_OPTIONS_MENU);
            }
        }
        @Override
        public void onRequestFocus(WebView view) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("onRequestFocus#onCloseWindow: view=")
                        .append(view)
                        .toString());
            }
//            onChangePage(view, view.getUrl());
        }
    }
    private class NicoroWebViewClient extends WebViewClient {
        @Override
        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebViewClient#doUpdateVisitedHistory: url=")
                        .append(url).append(" isReload=").append(isReload)
                        .toString());
            }
            if (!isReload) {
                if (url.equals("http://jk.nicovideo.jp/")) {
                    // ヒント表示
                    Util.showInfoToast(mContext,
                            R.string.toast_explain_launch_jikkyo);
                }
            }
        }

        @Override
        public void onLoadResource(WebView view, String url) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebViewClient#onLoadResource: url=")
                        .append(url)
                        .toString());
            }
            onChangePage(view, url);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebViewClient#onPageStarted: url=")
                        .append(url).append(" favicon=").append(favicon)
                        .toString());
            }
//            onChangePage(view, url);

//            // 念のため前のをキャンセル
//            mHandler.removeMessages(MSG_ID_UPDATE_TITLE_URL);
//            mHandler.sendEmptyMessageDelayed(MSG_ID_UPDATE_TITLE_URL,
//                    1000L);

//            boolean wasLogout;
//            if (url.equals("https://secure.nicovideo.jp/secure/logout")) {
//                // ログアウト
//                mCookieUserSession = null;
//                mUserId = null;
////                  CookieManager cookieManager = mCookieManager;
////                  cookieManager.removeExpiredCookie();
////                  cookieManager.setCookie("nicovideo.jp", "user_session=");
//                wasLogout = true;
//            } else {
//                wasLogout = false;
//            }
//
//            if (!wasLogout) {
//                if (mCookieUserSession == null && url.indexOf("nicovideo.jp") >= 0) {
//                    updateCookieUserSession();
//                }
//            }

            mJsObj.urlRss = null;
            mJsObj.titleRss = null;
            mJsObj.streamDescription = null;
            mJsObj.video = null;
            mWasJsCalled = false;
            synchronized (mEnableMenuJudgeAsync) {
                mEnableMenuJudgeAsync.clearLastParameter();
            }

            // XXX 若干遅らせてメニュー更新
            mHandler.sendEmptyMessageDelayed(MSG_ID_UPDATE_OPTIONS_MENU, 1000L);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebViewClient#onPageFinished: url=")
                        .append(url)
                        .toString());
            }
//            // 念のためもう一度実行
//            mHandler.removeMessages(MSG_ID_UPDATE_TITLE_URL);
//            mHandler.sendEmptyMessage(MSG_ID_UPDATE_TITLE_URL);

            if (mJsObj.video == null) {
                view.loadUrl(mResJsReadVideoOnPageFinished);
            }
            if (mWithoutFlash) {
                view.loadUrl(mResJsReplacePlayerbox);
            }
            if (mJsObj.streamDescription == null) {
                view.loadUrl(mResJsGetStreamDescription);
            }
            view.loadUrl(mJsMarginBottom);
            mWasJsCalled = true;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("WebViewClient#shouldOverrideUrlLoading: url=")
                        .append(url)
                        .toString());
            }
            Matcher matcherJikkyoApp = mMatcherJikkyoApp.get().reset(url);
            if (matcherJikkyoApp.find()) {
                // 実況アプリ起動ボタン
                startJikkyo(matcherJikkyoApp.group(1));
                return true;
            }
            if (mWithoutFlash) {
                Matcher matcherPlaylistUrl = mMatcherPlaylistUrl.get().reset(url);
                if (matcherPlaylistUrl.find()) {
                    // マイリスト連続再生ボタン
                    mMylistNumber = matcherPlaylistUrl.group(1);
                    startPlaylist();
                    return true;
                }
            }
            if (!mMatcherIsNicovideo.get().reset(url).find()
                    && !mMatcherIsNicoseiga.get().reset(url).find()) {
                // 外部サイトへのリンクの場合はIntent投げる
                startActivityViewUrl(url);
                return true;
            }

            return false;
        }
    }

    Context mContext;
    private LayoutInflater mLayoutInflater;

    private WebView mWebView;
    private Button mButtonAction;
    private ProgressBar mProgressLoadPage;

    private ProgressDialog mProgressDialogStartPlay;

    private CookieSyncManager mCookieSyncManager;
    private CookieManager mCookieManager;

    private LinkedList<WebView> mWebViewPool = new LinkedList<WebView>();
    @SuppressWarnings("unused")
    private WatchCache mWatchCache = new WatchCache(new Runnable() {
        @Override
        public void run() {
            if (mWebViewPool.size() > WEBVIEW_POOL_MIN_SIZE) {
                try {
                    WebView oldWebView = mWebViewPool.removeFirst();
                    oldWebView.destroy();
                    mVideoPool.removeFirst();
                    mLiveVideoPool.removeFirst();
                } catch (NoSuchElementException e) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, e.toString(), e);
                    }
                }
            }
        }
    });

    /** 動画視聴向けCookie */
    private String mCookieNicoHistory;

    // 設定
    private SharedPreferences mSharedPreferences;
    private SharedPreferences.OnSharedPreferenceChangeListener mSharedPreferencesChangeListener;
    /** 認証向けCookie */
    private String mCookieUserSession;
    private String mLastUrl;
    private String mUserAgent;

    private String mUserId;

    private String mStartUrl;

    private String mJikkyoNumber;
    private WatchVideo mVideo;
    private WatchVideo mLiveVideo;
    private LinkedList<WatchVideo> mVideoPool = new LinkedList<WatchVideo>();
    private LinkedList<WatchVideo> mLiveVideoPool = new LinkedList<WatchVideo>();
    private String mMylistNumber;

    private StateManager mStateManager = new StateManager();

    private boolean mWithoutFlash;
    private boolean mShowActionButton;

    private ThumbInfoCacher mThumbInfoCacher;

    private MatcherThreadLocal mMatcherVideoUrl =
        new MatcherThreadLocal(PATTERN_VIDEO_URL);
    private MatcherThreadLocal mMatcherGetUserID =
        new MatcherThreadLocal(PATTERN_GET_USERID_FROM_USERSESSION);
    private MatcherThreadLocal mMatcherIsNotHtml =
        new MatcherThreadLocal(PATTERN_IS_NOT_HTML);
    private MatcherThreadLocal mMatcherJikkyoApp =
        new MatcherThreadLocal(PATTERN_JIKKYO_APP);
    private MatcherThreadLocal mMatcherJikkyoUrl =
        new MatcherThreadLocal(PATTERN_JIKKYO_URL);
    private MatcherThreadLocal mMatcherIsNicovideo =
        new MatcherThreadLocal(NicoroAPIManager.PATTERN_NICOVIDEO_URL);
    private MatcherThreadLocal mMatcherIsNicoseiga =
        new MatcherThreadLocal(NicoroAPIManager.PATTERN_NICOSEIGA_URL);
    private MatcherThreadLocal mMatcherLiveUrl =
        new MatcherThreadLocal(PATTERN_LIVE_URL);
    private MatcherThreadLocal mMatcherCanSwitchPcSp =
        new MatcherThreadLocal(PATTERN_CAN_SWITCH_PC_SP);
    private MatcherThreadLocal mMatcherMylistUrl =
        new MatcherThreadLocal(PATTERN_MYLIST_URL);
    private MatcherThreadLocal mMatcherPlaylistUrl =
        new MatcherThreadLocal(PATTERN_PLAYLIST_URL);

    private DecimalFormat mDecimalFormatMB;

    private JsObj mJsObj = new JsObj();

    private boolean mWasJsCalled;

    private APILevelWrapper mAPILevelWrapper;

    private String mHitTestResultExtra;

    private EnableMenuJudge mEnableMenuJudgeAsync = new EnableMenuJudge();

    private EnableMenuJudge mEnableMenuJudgeOptionsMenu = new EnableMenuJudge();

    private Locale mCurrentLocale;
    // リソースのキャッシュ
    private String mResStringStatusLowShort;
    private String mResStringStatusMidShort;
    private String mResStringStatusHighShort;
    private String mResStringStatusHighLowShort;
//  private String mResStringStatus1NotLogin;
    private String mResStringStatus1CompletedCache;
    private String mResStringStatus1RunningCache;
    private String mResStringStatus1WaitingCache;
    private String mResStringStatus1NoneCache;
    private String mResStringStatus2RunningCache;
    private String mResStringStatus2WaitingCache;
    private String mResStringStatus2StartingCache;
    private String mResStringProgressStartPlay;

    private String mResStringPrefKeyBrowserZoomControls;
    private String mResStringPrefKeyWithoutFlash;
    private String mResStringPrefKeyShowActionButton;

    private String mResJsFindRss;
    /** 動画・生放送のflvplayer_containerをNicoRoのコントローラに置き換え */
    private String mResJsReplaceFlvplayerContainer;
    /** 実況のplayerboxをNicoRoのコントローラに置き換え */
    private String mResJsReplacePlayerbox;
    private String mResJsGetStreamDescription;
    private String mResJsGetOffsetFlvplayerContainer;
    /** 動画・生放送のVideo JavaScript変数を読み込み */
    private String mResJsReadVideo;
    /** 動画・生放送のVideo JavaScript変数を読み込み（onPageFinishedで実行） */
    private String mResJsReadVideoOnPageFinished;

    private String mJsMarginBottom;

    private final HandlerWrapper mHandler = new HandlerWrapper(this);

    @Override
    public boolean handleMessage(Message msg) {
        if (mStateManager.wasDestroyed()) {
            return true;
        }

        WebView webView = mWebView;
        switch (msg.what) {
        case MSG_ID_STARTED_CACHE:
            if (mWithoutFlash) {
                new EnableMenuJudgeUpdater().execute();
            }
            break;
        case MSG_ID_RUN_CACHING:
            if (mWithoutFlash) {
            }
            break;
        case MSG_ID_FINISHED_CACHE:
            if (mWithoutFlash) {
                new EnableMenuJudgeUpdater().execute();
            }
            break;
        case MSG_ID_UPDATE_TITLE_URL:
            if (webView != null) {
//                // ページのタイトルとURL表示を更新
//                String title = webView.getTitle();
//                if (title == null) {
//                    mHandler.sendEmptyMessageDelayed(MSG_ID_UPDATE_TITLE_URL,
//                            1000L);
//                } else {
//                    String newText = title + " : " + webView.getUrl();
//                    if (!newText.equals(mLabelTitleUrl.getText())) {
//                        mLabelTitleUrl.setText(newText);
//                    }
//                }

                // ブックマーク表示の更新
                Activity a = getActivity();
                if (a instanceof MainFragmentActivity) {
                    BookmarksFragment bookmarks = ((MainFragmentActivity) a).findFragmentInListMenu(
                            BookmarksFragment.class);
                    if (bookmarks != null) {
                        String title = webView.getTitle();
                        String url = webView.getUrl();
                        if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(url)) {
                            bookmarks.setAddBookmark(url, title);
                        }
                    }
                }
            }
            break;
        case MSG_ID_ACTION_START_PLAY:
            if (mWithoutFlash) {
                startPlay(mVideo, msg.arg1);
            }
            break;
        case MSG_ID_ACTION_START_CACHE:
            if (mWithoutFlash) {
                startCache(msg.arg1);
            }
            break;
        case MSG_ID_ACTION_RELATED_VIDEO:
            assert mVideo != null;
//            relatedVideo();
            showListMenu(webView, R.id.radio_related_video);
            break;
        case MSG_ID_ACTION_DELETE_CACHE:
            assert mVideo != null;
            deleteCache();
            break;
        case MSG_ID_ACTION_ADD_MYLIST:
            assert mVideo != null;
            addMylist();
            break;
        case MSG_ID_ACTION_START_JIKKYO:
            assert mJikkyoNumber != null;
            startJikkyo(mJikkyoNumber);
            break;
        case MSG_ID_ACTION_START_LIVE:
            if (mWithoutFlash) {
                assert mLiveVideo != null;
                startLive(mLiveVideo);
            }
            break;
        case MSG_ID_GO_BACK_WEBVIEW:
            goBackWebView();
            break;
        case MSG_ID_FLASH_SET_POS: {
            Rect r = (Rect) msg.obj;
            if (webView != null) {
                float scale = webView.getScale();
                webView.scrollTo((int) (r.left * scale), (int) (r.top * scale));
            }
        } break;
        case MSG_ID_FLASH_MATCH_FULL: {
            Rect r = (Rect) msg.obj;
            if (webView != null) {
                int widthView = webView.getWidth();
                int heightView = webView.getHeight();
                int widthFlash = r.width();
                int heightFlash = r.height();
                float scale;
                int widthScaleFlash;
                int heightScaleFlash;
                while (true) {
                    scale = webView.getScale();
                    widthScaleFlash = (int) (widthFlash * scale);
                    heightScaleFlash = (int) (heightFlash * scale);
                    if (widthScaleFlash * 1.25f < widthView
                            && heightScaleFlash * 1.25f < heightView) {
                        if (!webView.zoomIn()) {
                            break;
                        }
                    } else if (widthScaleFlash > widthView
                            && heightScaleFlash > heightView) {
                        if (!webView.zoomOut()) {
                            break;
                        }
                    } else {
                        break;
                    }
                }
                scale = webView.getScale();
                int x = (int) (r.left * scale);
                int y;
                heightScaleFlash = (int) (heightFlash * scale);
                if (heightScaleFlash <= heightView) {
                    y = (int) (r.top * scale);
                } else {
                    y = (int) ((r.top + (heightScaleFlash - heightView) / 2) * scale);
                }
                webView.scrollTo(x, y);
            }
        } break;
        case MSG_ID_UPDATE_OPTIONS_MENU: {
            Activity a = getActivity();
            if (a != null) {
                // 同時期に複数イベントが来ることがあるので最後を優先
                if (!mHandler.hasMessages(MSG_ID_UPDATE_OPTIONS_MENU)) {
                    ActivityCompat.invalidateOptionsMenu(a);
                }
            }
        } break;
        case MSG_ID_FINISHED_READ_VIDEO:
            if (mWithoutFlash) {
                if (webView != null) {
                    // 同時期に複数イベントが来ることがあるので最後を優先
                    if (!mHandler.hasMessages(MSG_ID_FINISHED_READ_VIDEO)) {
                        webView.loadUrl(mResJsReplaceFlvplayerContainer);
                    }
                }
            }
            break;
        case MSG_ID_ACTION_STOP_CACHE:
            assert mVideo != null;
            stopCache();
            break;
        case MSG_ID_ON_UPDATE_VIDEO:
            new EnableMenuJudgeUpdater().execute();
            break;
        default:
            assert false : msg.what;
            break;
        }
        return true;
    }

    private VideoCacheServiceConnection mVideoCacheServiceConnection;

    private class VideoCacheServiceConnectionCallback implements VideoCacheServiceConnection.Callback {
        public VideoCacheServiceConnectionCallback() {
        }

        private AtomicReference<StringBuffer> mStringBuffer =
            new AtomicReference<StringBuffer>(new StringBuffer(64));

        @Override
        public void onNotifyProgress(String videoV, int ecoType,
                int seekOffsetWrite, int contentLength)
                throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf()
                        .append("onNotifyProgress() videoV=").append(videoV)
                        .append(" ecoType=").append(ecoType)
                        .append(" seekOffsetWrite=").append(seekOffsetWrite)
                        .append(" contentLength=").append(contentLength)
                        .toString());
            }
            if (!f.mWithoutFlash) {
                return;
            }
            StringBuffer message = mStringBuffer.getAndSet(null);
            if (message == null) {
                // このコールバックは複数スレッドから同時に呼ばれることはない設計
                assert false;
                return;
            }
            message.setLength(0);

            message.append(videoV);
            if (ecoType == ECO_TYPE_LOW) {
                message.append(f.mResStringStatusLowShort);
            } else if (ecoType == ECO_TYPE_MID) {
                message.append(f.mResStringStatusMidShort);
            }
            message.append(f.mResStringStatus2RunningCache).append(": ");
            f.getRunCachingProgressMessage(message,
                    seekOffsetWrite, contentLength);
            addWaitingCacheMessage(message);
            f.mHandler.obtainMessage(MSG_ID_RUN_CACHING,
                    message.toString()).sendToTarget();

            StringBuffer temp = mStringBuffer.getAndSet(message);
            assert temp == null;
        }
        @Override
        public void onStart(String videoV, int ecoType)
        throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            if (!f.mWithoutFlash) {
                return;
            }
            StringBuilder message = new StringBuilder(32);
            message.append(videoV);
            if (ecoType == ECO_TYPE_LOW) {
                message.append(f.mResStringStatusLowShort);
            } else if (ecoType == ECO_TYPE_MID) {
                message.append(f.mResStringStatusMidShort);
            }
            message.append(f.mResStringStatus2StartingCache);
            addWaitingCacheMessage(message);
            f.mHandler.obtainMessage(MSG_ID_STARTED_CACHE,
                    message.toString()).sendToTarget();
        }
        @Override
        public void onFinished(String videoV, int ecoType)
        throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            if (!f.mWithoutFlash) {
                return;
            }
            f.mHandler.sendEmptyMessage(MSG_ID_FINISHED_CACHE);
        }
        @Override
        public void onAllFinished() throws RemoteException {
//              unbindService(mVideoCacheServiceConnection);
        }

        StringBuffer addWaitingCacheMessage(StringBuffer message)
        throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            IVideoCacheService videoCacheService =
                f.mVideoCacheServiceConnection.getIVideoCacheService();
            if (videoCacheService == null) {
                return message;
            }

            int waitRequestSize = videoCacheService.getWaitRequestSize();
            if (waitRequestSize > 0) {
                message.append(" （")
                    .append(f.mResStringStatus2WaitingCache)
                    .append(":")
                    .append(waitRequestSize)
                    .append("）");
            }
            return message;
        }
        StringBuilder addWaitingCacheMessage(StringBuilder message)
        throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            IVideoCacheService videoCacheService =
                f.mVideoCacheServiceConnection.getIVideoCacheService();
            if (videoCacheService == null) {
                return message;
            }

            int waitRequestSize = videoCacheService.getWaitRequestSize();
            if (waitRequestSize > 0) {
                message.append(" （")
                    .append(f.mResStringStatus2WaitingCache)
                    .append(":")
                    .append(waitRequestSize)
                    .append("）");
            }
            return message;
        }
        @Override
        public void onDeleted(String videoV) throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            // キャッシュ終了と同じ扱いで表示更新
            f.mHandler.sendEmptyMessage(MSG_ID_FINISHED_CACHE);
        }
        @Override
        public void onAllDeleted() throws RemoteException {
            WebBrowserFragment f = WebBrowserFragment.this;

            // キャッシュ終了と同じ扱いで表示更新
            f.mHandler.sendEmptyMessage(MSG_ID_FINISHED_CACHE);
        }
    };
    private final VideoCacheServiceConnectionCallback mVideoCacheServiceCallback = new VideoCacheServiceConnectionCallback();

    private AsyncStartPlay mAsyncStartPlay = null;
    private class AsyncStartPlay extends AsyncTask<Void, Void, Intent> {
        private HttpUriRequest mHttpRequestNicoHistory;
        private HttpUriRequest mHttpRequestUserSession;
        private HttpPost mHttpRequestAuthorizeCookie;
        private NicoroAPIManager.VideoPlayerParameterCreator mIntentCreator;
        private int mForceEco;
        private String mUrl;
        private WatchVideo mVideo;
        private Exception mException;

        @Override
        protected void onPreExecute() {
            WebView webView = mWebView;
            if (webView == null) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "AsyncStartPlay: mWebView is null");
                }
                mException = new FailPreparePlayVideoException();
                return;
            }
//          String url = webView.getOriginalUrl();
            String url = webView.getUrl();
            if (url == null) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "AsyncStartPlay: mWebView.getUrl() is null");
                }
                mException = new FailPreparePlayVideoException();
                return;
            }
            mUrl = url;

//          updateCookieUserSession();
//          if (DEBUG_LOGD) {
//              Log.d(LOG_TAG, "mCookieUserSession: " + mCookieUserSession);
//          }

            // 厳密なURLチェックやめる
//            String videoNumber = Util.getFirstMatch(
//                    mMatcherVideoUrl.get().reset(url));
//            if (!mVideo.hasVideoNumber(videoNumber)) {
//                mException = new FailPreparePlayVideoException();
//                return;
//            }

            if (NicoroAPIManager.isNotHigh(mForceEco)) {
//                createHttpRequestGetHistory(videoNumber);
                createHttpRequestGetHistory(mVideo.v());
            }
        }

        @Override
        protected Intent doInBackground(Void... params) {
            if (mException != null) {
                return null;
            }
            return doInBackgroundMain(false, NicoroAPIManager.isNotHigh(mForceEco));
        }

        private Intent doInBackgroundMain(boolean neverRetry,
                boolean forceHistory) {
            DefaultHttpClient httpClient = Util.createHttpClient();
            try {
                if (forceHistory) {
                    // 視聴履歴Cookie取り直す
                    mCookieNicoHistory = NicoroAPIManager.getCookieNicoHistory(
                            httpClient, mHttpRequestNicoHistory, mForceEco);
                } else {
                    mCookieNicoHistory = Util.getCookieValueFromManager(
                            mUrl, "nicohistory");
                }
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "mCookieNicoHistory: " + mCookieNicoHistory);
                }

                if (mCookieUserSession == null
                        || mUserId == null
                        || mCookieNicoHistory == null) {
                    throw new FailPreparePlayVideoException(
                            mContext.getString(R.string.errormessage_cookie_fail));
                }

                // キャッシュ処理が動作していれば停止
                // 時間がかかる恐れがあるのでdoInBackground内で実行
                IVideoCacheService videoCacheService =
                    mVideoCacheServiceConnection.getIVideoCacheService();
                if (videoCacheService != null) {
                    try {
                        videoCacheService.stopCache(mVideo.v());
                        videoCacheService.clearNotification(mVideo.v());
                    } catch (RemoteException e) {
                        Log.e(LOG_TAG, e.toString(), e);
                    }
                }

                mIntentCreator =
                    new NicoroAPIManager.VideoPlayerParameterCreator();
                Intent intent = mIntentCreator.createIntent(
                        mContext,
                        mVideo, mCookieUserSession, mCookieNicoHistory,
                        mUserId, mUserAgent, mForceEco);
                if (intent != null) {
                    intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                }
                return intent;
            } catch (FailPreparePlayVideoException e) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, e.toString(), e);
                }
                if (!neverRetry && shouldRetry()) {
                    if (httpClient != null) {
                        httpClient.getConnectionManager().shutdown();
                        httpClient = null;
                    }
                    return doInBackgroundMain(true, true);
                }
                mException = e;
            } catch (IOException e) {
                Log.d(LOG_TAG, e.toString(), e);
                mException = e;
            } finally {
                if (httpClient != null) {
                    httpClient.getConnectionManager().shutdown();
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Intent result) {
            assert mProgressDialogStartPlay != null;
            if (mProgressDialogStartPlay != null) {
                mProgressDialogStartPlay.dismiss();
            }
            if (mStateManager.wasDestroyed()) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "AsyncStartPlay#onPostExecute: WebBrowserFragment was destroyed");
                }
            } else {
                if (result == null) {
                    String message;
                    if (mException == null) {
                        assert false;
                        // 原因不明で再生失敗
                        message = mContext.getString(R.string.errormessage_play_fail_unknown);
                    } else if (mException instanceof FailPreparePlayVideoException) {
                        message = mException.getMessage();
                        if (message == null) {
                            // 原因不明で再生失敗
                            message = mContext.getString(R.string.errormessage_play_fail_unknown);
                        }
                        if (mForceEco == ECO_TYPE_MID) {
                            // 中画質のない動画かもしれないのでメッセージ追加
                            message += " : " + mContext.getString(R.string.errormessage_mid_may_none);
                        }
                    } else {
                        message = mException.toString();
                    }
                    Util.showErrorDialog(getActivity(),
                            message,
                            false);
                } else {
                    Intent intent = result;
                    String url = intent.getStringExtra(
                            PlayerConstants.INTENT_NAME_VIDEO_URL);
                    if (NicoroAPIManager.isGetflvUrlLow(url)
                            && mForceEco != ECO_TYPE_LOW) {
                        // 低画質指定していないが低画質モードになった
                        Util.showInfoToast(mContext, getString(
                                R.string.toast_transfer_low, mVideo.v()));
                    } else if (mForceEco == ECO_TYPE_MID
                            && !NicoroAPIManager.isGetflvUrlMid(url)) {
                        // 中画質指定したが別のモードになった
                        Util.showInfoToast(mContext, getString(
                                R.string.toast_transfer_not_mid, mVideo.v()));
                    }
                    startActivityForResult(intent, REQUEST_PLAY_VIDEO);
                    pauseWebView();
                }
            }

            mAsyncStartPlay = null;
        }

        public AsyncStartPlay executeWrapper(WatchVideo video, int forceEco) {
            mVideo = video;
            mForceEco = forceEco;
            return (AsyncStartPlay) execute();
        }

        public void stop() {
            Util.abortHttpUriRequest(mHttpRequestNicoHistory);
            Util.abortHttpUriRequest(mHttpRequestUserSession);
            Util.abortHttpUriRequest(mHttpRequestAuthorizeCookie);
            if (mIntentCreator != null) {
                mIntentCreator.abort();
            }
        }

        private void createHttpRequestGetHistory(String videoNumber) {
            mHttpRequestNicoHistory = NicoroAPIManager.createGetCookieNicoHistory(
                    videoNumber, mCookieUserSession,
                    mForceEco, mUserAgent);
        }

        private boolean shouldRetry() {
            mHttpRequestUserSession = NicoroAPIManager.createRequestIsCookieUserSessionValid(
                    mCookieUserSession, mUserAgent);
            if (NicoroAPIManager.checkIsCookieUserSessionValid(
                    mHttpRequestUserSession)) {
                // Cookieは有効→原因不明のエラー
                return false;
            }
            // 何らかの理由でログアウト状態→Cookie再取得を試みる
            String ma = NicoroConfig.getMA(mSharedPreferences);
            String pw = NicoroConfig.getPW(mSharedPreferences);
            if (ma == null || pw == null) {
                return false;
            }
            DefaultHttpClient httpClient = Util.createHttpClient();
            try {
                mHttpRequestAuthorizeCookie =
                    NicoroAPIManager.createRequestGetAuthorizeCookie(
                        ma, pw, mUserAgent);
                NicoroAPIManager.AuthorizeCookie cookie = NicoroAPIManager.getAuthorizeCookie(
                        httpClient, mHttpRequestAuthorizeCookie);
                if (cookie == null) {
                    return false;
                }
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                editor.putString(NicoroConfig.COOKIE_USER_SESSION,
                        cookie.cookie);
                editor.putInt(NicoroConfig.AUTHFLAG, cookie.authflag);
                editor.commit();
                // 念のためメンバ変数更新をUIスレッドで実行
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        updateCookieUserSession();
                    }
                });
            } catch (UnsupportedEncodingException e) {
                Log.e(LOG_TAG, e.toString(), e);
                return false;
            } finally {
                httpClient.getConnectionManager().shutdown();
            }
            createHttpRequestGetHistory(mVideo.v());
            return true;
        }
    }

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

        Activity activity = getActivity();
        mContext = activity.getApplicationContext();
        mAPILevelWrapper = APILevelWrapper.createInstance();

        Resources res = getResources();
        mCurrentLocale = res.getConfiguration().locale;
        cacheResourceString();
        createJavaScriptString();

//        getActivity().getWindow().requestFeature(Window.FEATURE_PROGRESS);

        // 例外対策兼ねて手動で作る
        CookieSyncManager cookieSyncManager =
            CookieSyncManager.createInstance(mContext);
        mCookieSyncManager = cookieSyncManager;

        CookieManager cookieManager = CookieManager.getInstance();
        mCookieManager = cookieManager;
        cookieManager.setAcceptCookie(true);
        cookieManager.removeExpiredCookie();
//      cookieManager.removeAllCookie();

        if (mSharedPreferences == null) {
            mSharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(mContext);
        }
        mWithoutFlash = mSharedPreferences.getBoolean(mResStringPrefKeyWithoutFlash, true);
        mShowActionButton = mSharedPreferences.getBoolean(mResStringPrefKeyShowActionButton,
                res.getBoolean(R.bool.pref_default_show_action_button));

        mThumbInfoCacher = NicoroApplication.getInstance(activity
                ).getThumbInfoCacher();

        mVideoCacheServiceConnection = NicoroApplication.getInstance(activity
                ).getVideoCacheServiceConnection();
        mVideoCacheServiceConnection.addCallback(mVideoCacheServiceCallback);

        // レイアウト崩れ対策
        setCookieNicovideo("nofix=1");
        // 実況アプリインストール済み状態に
        mCookieManager.setCookie("jk.nicovideo.jp", "jikkyo_installed=1");

        cookieSyncManager.sync();

        setHasOptionsMenu(true);

        mVideoCacheServiceConnection.bindVideoCacheService(new Intent(),
                mContext, TAG_VIDEO_CACHE_SERVICE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mLayoutInflater = inflater;
        return initializeView(inflater, container, savedInstanceState);
    }

    @Override
    public void onStart() {
        super.onStart();
        mStateManager.onStart();
    }

    @Override
    public void onResume() {
        super.onResume();
        mStateManager.onResume();

        mCookieSyncManager.startSync();

        updateCookieUserSession();
        mCookieSyncManager.sync();
        // Cookie更新後にURL設定

        WebView webView = mWebView;
        if (webView != null) {
            webView.resumeTimers();

            // タスクの遷移によってはActivityが再生成されて
            // URL読み込み済みでもWebViewのgetUrlがnullになることがある模様

            String urlWebView = webView.getUrl();
            if (urlWebView == null || !urlWebView.equals(mLastUrl)) {
                webView.loadUrl(mLastUrl);
            }
        }

        if (mWithoutFlash && mJsObj.video != null) {
            new EnableMenuJudgeUpdater().execute();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mStateManager.onPause();

        mCookieSyncManager.stopSync();

        WebView webView = mWebView;
        if (webView != null) {
            String urlWebView = webView.getUrl();
            if (urlWebView != null) {
                updateLastUrl(urlWebView);
            }
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        mStateManager.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mStateManager.wasDestroyed();
        mSharedPreferences.unregisterOnSharedPreferenceChangeListener(mSharedPreferencesChangeListener);
        mVideoCacheServiceConnection.unbindVideoCacheService(mContext,
                TAG_VIDEO_CACHE_SERVICE);

//        if (mAsyncCheckCookieUserSession != null) {
//          mAsyncCheckCookieUserSession.cancel(false);
//          mAsyncCheckCookieUserSession.stop();
//        }
        if (mAsyncStartPlay != null) {
            mAsyncStartPlay.cancel(false);
            mAsyncStartPlay.stop();
        }
        if (mButtonAction != null) {
            unregisterForContextMenu(mButtonAction);
        }
        WebView webView = mWebView;
        if (webView != null) {
            unregisterForContextMenu(webView);
            webView.destroy();
        }
        mWebView = null;
        for (WebView wv : mWebViewPool) {
            unregisterForContextMenu(wv);
            wv.destroy();
        }
        mWebViewPool.clear();
        mVideo = null;
        mVideoPool.clear();
        mLiveVideo = null;
        mLiveVideoPool.clear();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if (!newConfig.locale.equals(mCurrentLocale)) {
            cacheResourceString();
        }
    }

//    @Override
//    public boolean onKeyDown(int keyCode, KeyEvent event) {
//        // 1.6向けなのでonKeyDownでback key監視
//        // onBackPressedの活用は、API5からのため駄目
//        if (keyCode == KeyEvent.KEYCODE_BACK) {
//            if (goBackWebView()) {
//                return true;
//            }
//        }
//        return super.onKeyDown(keyCode, event);
//    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mStateManager.onSaveInstanceState();
        WebView webView = mWebView;
        if (webView != null) {
            webView.saveState(outState);
        }
    }

//    @Override
//    protected void onNewIntent(Intent intent) {
//        super.onNewIntent(intent);
//        String url = getUrlFromIntentActionView(intent);
//        if (url != null && !url.equals(mLastUrl)) {
//            updateLastUrl(url);
//            WebView webView = mWebView;
//            if (webView != null) {
//                webView.stopLoading();
//                webView.loadUrl(mLastUrl);
//            } else {
//                assert false;
//            }
//        }
//    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PLAY_VIDEO) {
            if (data == null) {
                return;
            }
            WatchVideo video = data.getParcelableExtra(
                    PlayerConstants.INTENT_NAME_WATCH_VIDEO);
            if (video == null) {
                return;
            }
            String videoNumber = video.v();
            if (videoNumber == null) {
                return;
            }
            WebView webView = mWebView;
            if (webView == null) {
                return;
            }
            String url = webView.getUrl();
            if (url == null) {
                // XXX とりあえずデフォルトのHOSTでジャンプ
                webView.loadUrl("http://" + DOMAIN_NICO_JP + "/watch/" + videoNumber);
            } else {
                Matcher matcher = Pattern.compile(PATTERN_VIDEO_URL_HOST
                        ).matcher(url);
                if (matcher.find()) {
                    webView.loadUrl(matcher.group(1) + videoNumber);
                }
            }
        }
    }

    public void setStartUrl(String url) {
        // TODO 実装これだけで問題ないかどうか？
        // Fragment復帰時は使用しないはずなので、setArgumentsは使わない
        mStartUrl = url;
    }

    public void updateCurrentUrl(String url) {
        if (url != null && !url.equals(mLastUrl)) {
            updateLastUrl(url);
            WebView webView = mWebView;
            if (webView != null) {
                webView.stopLoading();
                webView.loadUrl(mLastUrl);
            } else {
                assert false;
            }
        }
    }

    private View initializeView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.nicoro_webbrowser, container, false);

        mButtonAction = ViewUtil.findViewById(v, R.id.button_action);
        mButtonAction.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//              startAction();
//              openContextMenu(v);
                getActivity().openOptionsMenu();
            }
        });
        registerForContextMenu(mButtonAction);
        updateShowActionButton();

        mProgressLoadPage = ViewUtil.findViewById(v, R.id.progress);
        mProgressLoadPage.setVisibility(View.GONE);
        mProgressLoadPage.setMax(100);

        WebView webView = ViewUtil.findViewById(v, R.id.webview);
        mWebView = webView;

        WebBackForwardList backForwardList = null;
        if (savedInstanceState != null) {
            backForwardList = webView.restoreState(savedInstanceState);
        }

        initializeWebView(webView);

        String url = mStartUrl;
        if (url == null) {
            if (backForwardList != null) {
                url = backForwardList.getCurrentItem().getUrl();
            } else {
                url = "http://www.nicovideo.jp/";
                url = mSharedPreferences.getString(NicoroConfig.LAST_URL, url);
            }
        }
        // 過去バージョンの不具合対策
        if (!isInNicoVideoSite(url)) {
            url = "http://www.nicovideo.jp/";
        }
        updateLastUrl(url);

        mUserAgent = mSharedPreferences.getString(NicoroConfig.USER_AGENT,
                null);
        if (mUserAgent == null) {
            mUserAgent = webView.getSettings().getUserAgentString();
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putString(NicoroConfig.USER_AGENT, mUserAgent);
            editor.commit();
        }

        mSharedPreferencesChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(
                    SharedPreferences sharedPreferences, String key) {
                if (key.equals(mResStringPrefKeyBrowserZoomControls)) {
                    boolean zoomControl = sharedPreferences.getBoolean(key, true);
                    WebView webView = mWebView;
                    if (webView != null) {
                        webView.getSettings().setBuiltInZoomControls(zoomControl);
                    }
                } else if (key.equals(mResStringPrefKeyWithoutFlash)) {
                    boolean last = mWithoutFlash;
                    mWithoutFlash = sharedPreferences.getBoolean(key, true);
                    updateWithoutFlash();
                    if (last != mWithoutFlash && mWebView != null) {
                        mWebView.reload();
                    }
                    if (mOptionsMenuItem != null && mOptionsMenuItem.flashPlayer != null) {
                        setFlashPlayerMenuTitle(mOptionsMenuItem.flashPlayer,
                                mWithoutFlash);
                    }
                } else if (key.equals(mResStringPrefKeyShowActionButton)) {
                    Resources res = mContext.getResources();
                    mShowActionButton = sharedPreferences.getBoolean(key,
                            res.getBoolean(R.bool.pref_default_show_action_button));
                    updateShowActionButton();
                }
            }
        };
        mSharedPreferences.registerOnSharedPreferenceChangeListener(mSharedPreferencesChangeListener);

        mDecimalFormatMB = Util.createRunCachingProgressMessageFormat();

//        webView.loadUrl(mLastUrl);

        return v;
    }

    private void initializeWebView(WebView webView) {
        assert mJsObj != null;
        initializeWebSettings(webView.getSettings());
        webView.setWebChromeClient(new NicoroWebChromeClient());
        webView.setWebViewClient(new NicoroWebViewClient());
        webView.addJavascriptInterface(mJsObj, "nicoro");

        registerForContextMenu(webView);
    }

    private void initializeWebSettings(WebSettings webSettings) {
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
        webSettings.setSavePassword(true);
        webSettings.setUseWideViewPort(true);
//        webSettings.setLightTouchEnabled(true);
        // TODO 完全なマルチウインドウ対応は面倒そう
        webSettings.setSupportMultipleWindows(true);
        boolean zoomControl = mSharedPreferences.getBoolean(
                getString(R.string.pref_key_browser_zoom_controls), true);
        webSettings.setBuiltInZoomControls(zoomControl);

        mAPILevelWrapper.setPluginsEnabled(webSettings, !mWithoutFlash);
    }

//  private void updateCookieUserSession() {
//      String cookieUserSession = Util.getCookieValueFromManager(
//              "nicovideo.jp", "user_session");
//      if (cookieUserSession == null || cookieUserSession.length() == 0) {
//          // 確実にログアウト状態
//          mCookieUserSession = null;
//          mUserId = null;
//      } else if (!cookieUserSession.equals(mCookieUserSession)) {
//          SharedPreferences.Editor editor = mSharedPreferences.edit();
//          editor.putString(NicoroConfig.COOKIE_USER_SESSION, cookieUserSession);
//          editor.commit();
//          mCookieUserSession = cookieUserSession;
//          mMatcherGetUserID = Util.getMatcher(mMatcherGetUserID,
//                  PATTERN_GET_USERID_FROM_USERSESSION,
//                  mCookieUserSession);
//          mUserId = Util.getFirstMatch(mMatcherGetUserID);
//      }
//  }

    private void onChangePage(WebView view, String url) {
        // http/httpsでないなら特別処理省略
        if (!url.startsWith("http://") && !url.startsWith("https://")) {
            return;
        }

        // 外部サイトなら特別処理省略
        if (!isInNicoVideoSite(url)) {
            return;
        }

        // 画像他なら特別処理省略
        if (mMatcherIsNotHtml.get().reset(url).find()) {
            return;
        }

        // ページ移動に備えて基本的にWebViewのcurrent URLで確認
        String urlCurrent = view.getUrl();
        if (urlCurrent == null) {
            urlCurrent = url;
        }

        WatchVideo video = mJsObj.video;
        String videoId = null;
        String videoNumber = getVideoNumberFromUrl(urlCurrent);
        WatchVideo newVideo = filterVideoByVideoNumber(video, videoNumber);
        if (newVideo != null) {
            mVideo = newVideo;
            videoId = newVideo.id();
        }
        if (videoId == null) {
            // XXX ThumbInfoの先読みが必要なのでvideoNumberでとりあえず代行
            videoId = videoNumber;
        }
        WatchVideo newLiveVideo = filterLiveVideoByUrl(video, urlCurrent);
        if (newLiveVideo != null) {
            mLiveVideo = newLiveVideo;
        }
        if (videoId != null) {
            ThumbInfo thumbInfo = mThumbInfoCacher.getThumbInfo(videoId);
            if (thumbInfo == null) {
                mThumbInfoCacher.loadThumbInfo(videoId, null);
            }

            Activity a = getActivity();
            if (a instanceof MainFragmentActivity) {
                RelatedVideoFragment relatedVideo = ((MainFragmentActivity) a).findFragmentInListMenu(
                        RelatedVideoFragment.class);
                if (relatedVideo != null && relatedVideo.isResumed()
                        && !relatedVideo.isHidden()) {
                    relatedVideo.setVideoNumber(videoId);
                }
            }
        }
        mJikkyoNumber = getJikkyoNumberFromUrl(urlCurrent);

//      if (DEBUG_LOG) {
//          CookieManager cookieManager = CookieManager.getInstance();
//          String cookie = cookieManager.getCookie(urlCurrent);
//          if (cookie != null) {
//              Log.d(LOG_TAG, "URL=" + url
//                      + " Cookie=" + cookie);
//          }
//      }

        if (mMatcherIsNicovideo.get().reset(urlCurrent).find()) {
            updateLastUrl(urlCurrent);
        }
    }

    // TODO メソッド名変更すべき
    private void updateStatus1CacheState(StringBuilder builderStatus1,
            String videoV, String videoId) {
        builderStatus1.append(videoV).append(": ");
        boolean isFinishedCache =
            VideoLoader.isFinishedCache(mContext, videoV);
        boolean isSwf = NicoroAPIManager.isVideoNumberSwf(videoId);
        boolean isFinishedCacheLow;
        boolean isFinishedCacheMid;
        if (isSwf) {
            // swfは低画質無し
            isFinishedCacheLow = false;
            isFinishedCacheMid = false;
        } else {
            isFinishedCacheLow =
                VideoLoader.isFinishedCacheLow(mContext, videoV);
            isFinishedCacheMid =
                VideoLoader.isFinishedCacheMid(mContext, videoV);
        }
        if (isFinishedCache && isFinishedCacheLow && isFinishedCacheMid) {
            builderStatus1.append(mResStringStatus1CompletedCache)
                .append(mResStringStatusHighLowShort);
            return;
        }

        int cacheState = VideoCacheService.CACHE_STATE_INVALID;
        int cacheStateLow = VideoCacheService.CACHE_STATE_INVALID;
        int cacheStateMid = VideoCacheService.CACHE_STATE_INVALID;
        IVideoCacheService videoCacheService =
            mVideoCacheServiceConnection.getIVideoCacheService();
        if (videoCacheService != null) {
            try {
                if (!isFinishedCache) {
                    cacheState =
                        videoCacheService.getCacheState(
                                videoV, ECO_TYPE_HIGH);
                }
                if (!isSwf) {
                    if (!isFinishedCacheLow) {
                        cacheStateLow =
                            videoCacheService.getCacheState(
                                    videoV, ECO_TYPE_LOW);
                    }
                    if (!isFinishedCacheMid) {
                        cacheStateMid =
                            videoCacheService.getCacheState(
                                    videoV, ECO_TYPE_MID);
                    }
                }
            } catch (RemoteException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
        }

        if (cacheState == VideoCacheService.CACHE_STATE_RUNNING) {
            builderStatus1.append(mResStringStatus1RunningCache);
            return;
        }
        if (cacheStateMid == VideoCacheService.CACHE_STATE_RUNNING) {
            builderStatus1.append(mResStringStatus1RunningCache)
                .append(mResStringStatusMidShort);
            return;
        }
        if (cacheStateLow == VideoCacheService.CACHE_STATE_RUNNING) {
            builderStatus1.append(mResStringStatus1RunningCache)
                .append(mResStringStatusLowShort);
            return;
        }
        if (cacheState == VideoCacheService.CACHE_STATE_WAIT_START) {
            builderStatus1.append(mResStringStatus1WaitingCache);
            return;
        }
        if (cacheStateMid == VideoCacheService.CACHE_STATE_WAIT_START) {
            builderStatus1.append(mResStringStatus1WaitingCache)
                .append(mResStringStatusMidShort);
            return;
        }
        if (cacheStateLow == VideoCacheService.CACHE_STATE_WAIT_START) {
            builderStatus1.append(mResStringStatus1WaitingCache)
                .append(mResStringStatusLowShort);
            return;
        }

        if (isSwf) {
            if (isFinishedCache) {
                builderStatus1.append(mResStringStatus1CompletedCache);
                return;
            }
        } else {
            // TODO: 低画質・中画質がそもそも存在しない動画のチェックがない
            if (isFinishedCache || isFinishedCacheMid || isFinishedCacheLow) {
                builderStatus1.append(mResStringStatus1CompletedCache);
                if (isFinishedCache) {
                    builderStatus1.append(mResStringStatusHighShort);
                }
                if (isFinishedCacheMid) {
                    builderStatus1.append(mResStringStatusMidShort);
                }
                if (isFinishedCacheLow) {
                    builderStatus1.append(mResStringStatusLowShort);
                }
                return;
            }
        }

        builderStatus1.append(mResStringStatus1NoneCache);
        return;
    }

    StringBuffer getRunCachingProgressMessage(StringBuffer buffer,
            int seekOffsetWrite, int contentLength) {
        return Util.getRunCachingProgressMessage(mDecimalFormatMB, buffer,
                seekOffsetWrite, contentLength);
    }

    private void cacheResourceString() {
        Resources r = getResources();
        mResStringStatusLowShort = r.getString(R.string.status_low_short);
        mResStringStatusMidShort = r.getString(R.string.status_mid_short);
        mResStringStatusHighShort = r.getString(R.string.status_high_short);
        mResStringStatusHighLowShort = r.getString(R.string.status_high_low_short);
//      mResStringStatus1NotLogin = r.getString(R.string.status1_not_login);
        mResStringStatus1CompletedCache = r.getString(R.string.status1_completed_cache);
        mResStringStatus1RunningCache = r.getString(R.string.status1_running_cache);
        mResStringStatus1WaitingCache = r.getString(R.string.status1_waiting_cache);
        mResStringStatus1NoneCache = r.getString(R.string.status1_none_cache);
        mResStringStatus2RunningCache = r.getString(R.string.status2_running_cache);
        mResStringStatus2WaitingCache = r.getString(R.string.status2_waiting_cache);
        mResStringStatus2StartingCache = r.getString(R.string.status2_starting_cache);
        mResStringProgressStartPlay = r.getString(R.string.progress_start_play);
        mResStringPrefKeyBrowserZoomControls = r.getString(R.string.pref_key_browser_zoom_controls);
        mResStringPrefKeyWithoutFlash = r.getString(R.string.pref_key_without_flash);
        mResStringPrefKeyShowActionButton = r.getString(R.string.pref_key_show_action_button);
    }

    private void createJavaScriptString() {
        Resources res = getResources();

        StringBuilder builder = new StringBuilder(256);
        builder.setLength(0);
        mResJsFindRss = Util.loadRawJs(builder, res,
                R.raw.find_rss).toString();

        builder.setLength(0);
        mResJsReplaceFlvplayerContainer = Util.loadRawJs(builder, res,
                R.raw.replace_flvplayer_container).toString();

        builder.setLength(0);
        mResJsReplacePlayerbox = Util.loadRawJs(builder, res,
                R.raw.replace_playerbox).toString();

        builder.setLength(0);
        mResJsGetStreamDescription = Util.loadRawJs(builder, res,
                R.raw.get_stream_description).toString();

        builder.setLength(0);
        mJsMarginBottom = builder.append("javascript: document.body.style.marginBottom = \"")
            .append(res.getDimensionPixelSize(R.dimen.button_video_control_height))
            .append("px\"").toString();

        builder.setLength(0);
        mResJsGetOffsetFlvplayerContainer = Util.loadRawJs(builder, res,
                R.raw.get_offset_flvplayer_container).toString();

        builder.setLength(0);
        mResJsReadVideo = Util.loadRawJs(builder, res,
                R.raw.read_video).toString();

        builder.setLength(0);
        mResJsReadVideoOnPageFinished = Util.loadRawJs(builder, res,
                R.raw.read_video_on_page_finished).toString();
    }

    /**
     * 全てのWebViewが止まるので、Player起動時のみ実行
     */
    void pauseWebView() {
        WebView webView = mWebView;
        if (webView != null) {
//            webView.stopLoading();
            webView.pauseTimers();
        }
    }

    public void onStartPlayerByOther() {
        pauseWebView();
    }

    private void startPlay(WatchVideo video, int forceEco) {
        if (video == null || !video.isValid()) {
            Util.showErrorToast(mContext, R.string.errormessage_play_fail_unknown);
            return;
        }
        mAsyncStartPlay = new AsyncStartPlay();
        mAsyncStartPlay.executeWrapper(video, forceEco);

        if (mProgressDialogStartPlay == null) {
            Activity activity = getActivity();
            if (activity == null) {
                return;
            }
            mProgressDialogStartPlay = Util.createProgressDialogLoading(
                    activity, mResStringProgressStartPlay,
                    new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialog) {
                            if (mAsyncStartPlay != null) {
                                mAsyncStartPlay.cancel(false);
                                mAsyncStartPlay.stop();
                            }
                        }
                    });
        }
        mProgressDialogStartPlay.show();
    }

//  private void loadLoginForm() {
//      // ログイン画面へ
//      // ログイン後に元の動画のURLに移動しない、というかこのメソッドもう使わないのでは？
//        WebView webView = mWebView;
//        if (webView != null) {
//            webView.loadUrl("https://secure.nicovideo.jp/secure/login_form");
//        }
//  }

    private void startCache(int forceEco) {
//      updateCookieUserSession();
//      if (DEBUG_LOGD) {
//          Log.d(LOG_TAG, "mCookieUserSession: " + mCookieUserSession);
//      }

        NicoroConfig.saveCookieUserSession(mSharedPreferences, mCookieUserSession);
        String videoV = getVideoV();
        IVideoCacheService videoCacheService =
            mVideoCacheServiceConnection.getIVideoCacheService();
        if (videoCacheService == null) {
            Intent intentBind = new Intent()
                .putExtra(PlayerConstants.INTENT_NAME_VIDEO_NUMBER, videoV)
                .putExtra(PlayerConstants.INTENT_NAME_FORCE_ECO, forceEco);
            mVideoCacheServiceConnection.bindVideoCacheService(intentBind, mContext,
                    TAG_VIDEO_CACHE_SERVICE);
        } else {
            try {
                videoCacheService.addStartCache(videoV,
                        forceEco);
            } catch (RemoteException e) {
                Log.e(LOG_TAG, e.toString(), e);
                Util.showErrorToast(mContext, R.string.toast_cache_fail_bind);
            }
        }
    }

    private void startJikkyo(String number) {
        try {
            Intent intent = NicoroAPIManager.createJikkyoPlayerIntent(
                    mContext, number)
                .addCategory(Intent.CATEGORY_BROWSABLE);
            startActivity(intent);
            pauseWebView();
        } catch (FailPreparePlayVideoException e) {
        }
    }

    private void startLive(final WatchVideo liveVideo) {
        if (liveVideo == null || !liveVideo.isValid()) {
            Util.showErrorToast(mContext, R.string.errormessage_play_fail_unknown);
            return;
        }
        boolean warning = mSharedPreferences.getBoolean(
                mContext.getString(R.string.pref_key_warning_live_play), true);
        if (warning) {
            DialogInterface.OnClickListener onClickListener =
                new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    switch (which) {
                        case DialogInterface.BUTTON_POSITIVE:
//                            dialog.dismiss();
                            startLiveImpl(liveVideo);
                            break;
                        case DialogInterface.BUTTON_NEGATIVE:
//                            dialog.dismiss();
                            break;
                        default:
                            assert false : which;
                            break;
                    }
                }
            };
            DialogInterface.OnCancelListener onCancelListener =
                new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
//                    dialog.dismiss();
                }
            };
            Activity activity = getActivity();
            LayoutInflater inflater = Util.getInflaterForDialog(activity);
            // LayoutParams生成のためダミーのViewも使用
            View confirmPlayLive = inflater.inflate(
                    R.layout.confirm_play_live, new FrameLayout(activity), false);
            CheckBox dontShow = ViewUtil.findViewById(confirmPlayLive,
                    R.id.dont_show_this_dialog);
            dontShow.setChecked(!warning);
            dontShow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView,
                        boolean isChecked) {
                    SharedPreferences.Editor editor = mSharedPreferences.edit();
                    editor.putBoolean(getString(R.string.pref_key_warning_live_play),
                            !isChecked);
                    editor.commit();
                }
            });
            new AlertDialog.Builder(activity)
                .setTitle(R.string.confirm)
                .setView(confirmPlayLive)
                .setCancelable(true)
                .setPositiveButton(android.R.string.yes, onClickListener)
                .setNegativeButton(android.R.string.cancel, onClickListener)
                .setOnCancelListener(onCancelListener)
                .show();
        } else {
            startLiveImpl(liveVideo);
        }
    }
    private void startLiveImpl(WatchVideo liveVideo) {
        // TODO 生放送情報をAPIで取ると途中までしか取れないため、htmlのソース内から取得
        // →まだ完全に読み込み終わっていないときに実行したら取得できないかも
        String title = liveVideo.title();
        String description = liveVideo.description();;
        if (title == null) {
            WebView webView = mWebView;
            if (webView != null) {
                title = webView.getTitle();
                if (title != null) {
                    Matcher matcher = Pattern.compile(PATTERN_GET_TITLE).matcher(title);
                    if (matcher.find()) {
                        title = matcher.group(1);
                    }
                }
            }
        }
        if (description == null) {
            description = mJsObj.streamDescription;
        } else {
            // HTMLタグはプレイヤー側で変換する
        }
        try {
            Intent intent = NicoroAPIManager.createLivePlayerIntent(
                    mContext,
                    liveVideo, mCookieUserSession,
                    title, description)
                .addCategory(Intent.CATEGORY_BROWSABLE);
            startActivity(intent);
            pauseWebView();
        } catch (FailPreparePlayVideoException e) {
        }
    }

    private void startPlaylist() {
        String mylistNumber = mMylistNumber;
        if (mylistNumber == null) {
            Util.showErrorToast(mContext, R.string.errormessage_play_fail_unknown);
            return;
        }

        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        Fragment prev = fm.findFragmentByTag("playlist_dialog");
        if (prev != null) {
            ft.remove(prev);
        }
        ft.addToBackStack(null);

        StartPlaylistDialogFragment fragment =
            StartPlaylistDialogFragment.createInstance(mylistNumber);
        fragment.show(ft, "playlist_dialog");
    }

//    private void relatedVideo() {
//        String videoNumber = mVideoNumber;
//        Intent intent = new Intent(mContext,
//                RelatedVideoActivity.class);
//        intent.putExtra(RelatedVideoActivity.INTENT_EXTRA_VIDEO_NUMBER,
//                videoNumber);
//        startActivity(intent);
//    }

    private void deleteCache() {
        CacheDeleteTask.createCacheDeleteDialog(getActivity(), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String videoV = getVideoV();
                Activity activity = getActivity();
                if (videoV == null || activity == null) {
                    assert videoV != null;
                    Util.showErrorToast(mContext,
                            R.string.toast_delete_cache_one_fail);
                } else {
                    new CacheDeleteTask(activity, videoV).execute();
                }
            }
        }).show();
    }

    private void stopCache() {
        String videoV = getVideoV();
        if (videoV == null) {
            assert false;
        } else {
            new CacheStopTask(getActivity(), videoV) {
                @Override
                protected void onPostExecute(Void result) {
                    super.onPostExecute(result);
                    // キャッシュ終了と同じ扱いで表示更新
                    mHandler.sendEmptyMessage(MSG_ID_FINISHED_CACHE);
                }
            }.execute();
        }
    }

//    private void accessHistory() {
//        Intent intent = new Intent(getApplicationContext(),
//                AccessHistoryActivity.class);
//        startActivity(intent);
//    }

    private void addMylist() {
        try {
            WebView webView = mWebView;
            if (webView != null) {
                final String videoV = getVideoV();
                if (videoV != null) {
                    URL currentUrl = new URL(webView.getUrl());
                    String host = currentUrl.getHost();
                    if ("sp.nicovideo.jp".equals(host)) {
                        webView.loadUrl("javascript:window.startToAddMylist();");
                    } else {
                        StringBuilder builder = new StringBuilder(
                                "javascript:window.open(\"http://")
                            .append(host).append("/mylist_add/video/")
                            .append(videoV).append("\", \"_blank\");");
                        webView.loadUrl(builder.toString());
                    }
                }
            }
        } catch (MalformedURLException e) {
            Log.e(LOG_TAG, e.toString(), e);
        }
    }

    private void viewRss() {
        URL rss = getRssUrl();
        if (rss != null) {
            try {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setDataAndType(Uri.parse(rss.toExternalForm()),
                        "application/rss+xml");
                startActivity(intent);
                return;
            } catch (ActivityNotFoundException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
        }
        Util.showErrorToast(mContext,
                R.string.toast_rss_fail);
    }

    private void shareRss() {
        URL rss = getRssUrl();
        if (rss != null) {
            try {
                Intent intent = new Intent(Intent.ACTION_SEND);
                intent.putExtra(Intent.EXTRA_TEXT, rss.toExternalForm());
                String title = getRssTitle();
                if (title != null) {
                    intent.putExtra(Intent.EXTRA_SUBJECT, title);
                }
                intent.setType("text/plain");
                intent = Intent.createChooser(intent, null);
                startActivity(intent);
                return;
            } catch (ActivityNotFoundException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
        }
        Util.showErrorToast(mContext,
                R.string.toast_rss_fail);
    }

    private void shareUrl() {
        WebView webView = mWebView;
        if (webView == null) {
            return;
        }
        String url = webView.getUrl();
        if (url != null) {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.putExtra(Intent.EXTRA_TEXT, url);
            String title = webView.getTitle();
            if (title != null) {
                intent.putExtra(Intent.EXTRA_SUBJECT, title);
            }
            intent.setType("text/plain");
            intent = Intent.createChooser(intent, null);
            startActivity(intent);
        }
    }

    private void showListMenu(WebView webView, Integer defaultListRadioId) {
//        // TODO Fragment対応のためIntentとは限らなくなる
//        Intent intent = new Intent(mContext, ListMenuActivity.class)
//            .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
//        if (webView == null) {
//            Util.showErrorToast(mContext, R.string.toast_webview_not_ready);
//        } else {
//            intent.putExtra(ListMenuActivity.INTENT_EXTRA_BOOKMARKS_ADD_TITLE,
//                    webView.getTitle());
//            intent.putExtra(ListMenuActivity.INTENT_EXTRA_BOOKMARKS_ADD_URL,
//                    webView.getUrl());
//        }
//        String videoNumber = mVideoNumber;
//        if (videoNumber != null) {
//            intent.putExtra(ListMenuActivity.INTENT_EXTRA_VIDEO_NUMBER,
//                    videoNumber);
//        }
//        if (defaultListRadioId != null) {
//            intent.putExtra(ListMenuActivity.INTENT_EXTRA_DEFAULT_LIST_RADIO,
//                    defaultListRadioId.intValue());
//        }
//        startActivity(intent);

        Activity a = getActivity();
        if (a instanceof ListMenuStarter) {
            ListMenuStarter starter = (ListMenuStarter) a;
            Bundle bundle = new Bundle();
            if (webView == null) {
                Util.showErrorToast(mContext, R.string.toast_webview_not_ready);
            } else {
                bundle.putString(ListMenuActivity.INTENT_EXTRA_BOOKMARKS_ADD_TITLE,
                        webView.getTitle());
                bundle.putString(ListMenuActivity.INTENT_EXTRA_BOOKMARKS_ADD_URL,
                        webView.getUrl());
            }
            // 現状vとidどちらでも可
            String videoV = getVideoV();
            if (videoV != null) {
                bundle.putString(ListMenuActivity.INTENT_EXTRA_VIDEO_NUMBER,
                        videoV);
            }
            if (defaultListRadioId != null) {
                bundle.putInt(ListMenuActivity.INTENT_EXTRA_DEFAULT_LIST_RADIO,
                        defaultListRadioId.intValue());
            }
            starter.startListMenu(bundle);
        }
    }

    private String getVideoNumberFromUrl(String url) {
        return getVideoNumberFromUrl(mMatcherVideoUrl.get(), url);
    }
    static String getVideoNumberFromUrl(Matcher matcher, String url) {
        matcher.reset(url);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return null;
        }
    }
    static String getVideoNumberFromUrl(ReuseMatcher matcher, String url) {
        return matcher.reset(url).groupIfFind(1);
    }

    private String getJikkyoNumberFromUrl(String url) {
        Matcher matcher = mMatcherJikkyoUrl.get().reset(url);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return null;
        }
    }

    private String getLiveNumberFromUrl(String url) {
        Matcher matcher = mMatcherLiveUrl.get().reset(url);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return null;
        }
    }

    public String getMylistNumber(String url) {
        if (url == null) {
            return null;
        }
        Matcher matcher = mMatcherMylistUrl.get().reset(url);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return null;
        }
    }

//  private void showBookmark() {
//      Intent intent = new Intent(getApplicationContext(), Bookmarks.class);
//      WebView webView = mWebView;
//      if (webView != null) {
//          intent.putExtra(Bookmarks.INTENT_EXTRA_TITLE, webView.getTitle());
//          intent.putExtra(Bookmarks.INTENT_EXTRA_URL, webView.getUrl());
//      } else {
//          Util.showErrorToast(getApplicationContext(), R.string.toast_webview_not_ready);
//      }
//      startActivityIfNeeded(intent, 0);
//  }

//  private void reflectIsCookieUserSessionValid(boolean result) {
//        if (result) {
//          // 前回保存時のuser session Cookieを有効化
//          mMatcherGetUserID = Util.getMatcher(mMatcherGetUserID,
//                  PATTERN_GET_USERID_FROM_USERSESSION,
//                  mCookieUserSession);
//          mUserId = Util.getFirstMatch(mMatcherGetUserID);
////            mCookieManager.setCookie("nicovideo.jp", mCookieUserSession);
//        } else {
//          mCookieUserSession = null;
//          mUserId = null;
//          // FIXME CookieManagerでは特定のCookieを削除することはできない
//          // →WebViewDatabaseの実装を参考にdbファイル直接編集するしかない？
////            cookieManager.setCookie("nicovideo.jp", "user_session=");
//        }
//  }

    private void updateCookieUserSession() {
        mCookieUserSession = NicoroConfig.getCookieUserSession(mSharedPreferences);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("updateCookieUserSession: mCookieUserSession=")
                    .append(mCookieUserSession)
                    .toString());
        }
        setCookieNicovideo(mCookieUserSession);
        setCookieNicovideoSp(mCookieUserSession);
        if (mCookieUserSession != null) {
            mUserId = Util.getFirstMatch(
                    mMatcherGetUserID.get().reset(mCookieUserSession));
        }
    }

    private void setCookieNicovideo(String value) {
        // Android3.0は先頭に.が無いと認識せず、Android2.1は先頭に.があると認識しない
        mCookieManager.setCookie("nicovideo.jp", value);
        mCookieManager.setCookie(".nicovideo.jp", value);
        // 公式短縮URLにも設定しないとCookieがうまく引き継がれない模様
        mCookieManager.setCookie("nico.ms", value);
    }

    private void setCookieNicovideoSp(String value) {
        mCookieManager.setCookie("sp.live.nicovideo.jp", value);
        mCookieManager.setCookie(".sp.live.nicovideo.jp", value);
    }

    public boolean goBackWebView() {
        WebView webView = mWebView;
        if (webView == null) {
            return false;
        }
        if (webView.canGoBack()) {
            synchronized (mEnableMenuJudgeAsync) {
                mEnableMenuJudgeAsync.clearLastParameter();
            }
            webView.goBack();
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_OPTIONS_MENU);
            return true;
        }
        if (mWebViewPool.isEmpty()) {
            return false;
        }
        WebView lastWebView = mWebViewPool.removeLast();
        WatchVideo lastVideo = mVideoPool.removeLast();
        WatchVideo lastLiveVideo = mLiveVideoPool.removeLast();
        ViewUtil.replaceView(webView, lastWebView);
        mWebView = lastWebView;
        mVideo = lastVideo;
        mLiveVideo = lastLiveVideo;
        String url = lastWebView.getUrl();
        updateLastUrl(url);
        webView.destroy();
        // XXX onChangePage前に復帰値で上書き
        synchronized (mEnableMenuJudgeAsync) {
            mEnableMenuJudgeAsync.clearLastParameter();
        }
        if (lastVideo != null) {
            mJsObj.updateVideo(lastVideo);
        } else if (lastLiveVideo != null) {
            mJsObj.updateVideo(lastLiveVideo);
        }
        onChangePage(lastWebView, url);
        mHandler.sendEmptyMessage(MSG_ID_UPDATE_OPTIONS_MENU);
        return true;
    }

    private void updateLastUrl(String url) {
        if (!TextUtils.equals(mLastUrl, url)) {
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putString(NicoroConfig.LAST_URL, url);
            editor.commit();
            mLastUrl = url;
        }
    }

    public static boolean isInNicoVideoSite(String url) {
        try {
            URL urlParse = new URL(url);
            String host = urlParse.getHost();
            String path = urlParse.getPath();
            if (host.equals("nico.ms")) {
                return true;
//            } else if (host.indexOf("nicoseiga.jp") >= 0) {
//                return true;
            } else if (host.indexOf("nicovideo.jp") < 0
                    || host.equals("rd.nicovideo.jp")
                    || host.equals("ads.nicovideo.jp")) {
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, Log.buf().append("Outside url=")
                            .append(url).toString());
                }
                return false;
            } else if (host.equals("seiga.nicovideo.jp")
                    && path.startsWith("/image/source")) {
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, Log.buf().append("Outside url=")
                            .append(url).toString());
                }
                return false;
            } else if (host.equals(DOMAIN_NICO_ES) || host.equals(DOMAIN_NICO_DE)) {
                // XXX ドイツ版とスペイン版のサイトはniconico.comにリダイレクト
                // されるようになったのでひとまずfalse扱い
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, Log.buf().append("Outside url=")
                            .append(url).toString());
                }
                return false;
            } else {
                return true;
            }
        } catch (MalformedURLException e) {
            Log.w(LOG_TAG, "Parse failed: url=" + url, e);
            // XXX とりあえずtrue扱い
            return true;
        }
    }

    private void updateWithoutFlash() {
        boolean flag = !mWithoutFlash;
        if (mWebView != null) {
            mAPILevelWrapper.setPluginsEnabled(mWebView.getSettings(), flag);
        }
        for (WebView wv : mWebViewPool) {
            mAPILevelWrapper.setPluginsEnabled(wv.getSettings(), flag);
        }
    }

    private void updateShowActionButton() {
        int visibility;
        if (mShowActionButton) {
            visibility = View.VISIBLE;
        } else {
            visibility = View.GONE;
        }
        mButtonAction.setVisibility(visibility);
    }

    private URL getRssUrl() {
        WebView webView = mWebView;
        if (webView == null) {
            Log.e(LOG_TAG, "getRssUrl: webView is null");
            return null;
        }
        String url = webView.getUrl();
        if (url == null) {
            Log.e(LOG_TAG, "getRssUrl: mWebView.getUrl() return null");
            return null;
        }
        String urlRss = mJsObj.urlRss;
        if (TextUtils.isEmpty(urlRss)) {
            Log.e(LOG_TAG, "getRssUrl: mJsObj.urlRss is empty");
            assert false;
            return null;
        }
        try {
            URL forParse = new URL(url);
            return new URL(forParse.getProtocol(),
                    forParse.getHost(), urlRss);
        } catch (MalformedURLException e) {
            Log.e(LOG_TAG, e.toString(), e);
            return null;
        }
    }

    private String getRssTitle() {
        if (TextUtils.isEmpty(mJsObj.titleRss)) {
            return null;
        }
        return mJsObj.titleRss;
    }

    private boolean isCacheNeeded(String videoV) {
        if (VideoLoader.isFinishedCache(mContext, videoV)) {
            return false;
        } else {
            return isCacheNeededOnService(videoV, ECO_TYPE_HIGH);
        }
    }

    private boolean isCacheNeededMid(String videoV) {
        if (VideoLoader.isFinishedCacheMid(mContext, videoV)) {
            return false;
        } else {
            return isCacheNeededOnService(videoV, ECO_TYPE_MID);
        }
    }

    private boolean isCacheNeededLow(String videoV) {
        if (VideoLoader.isFinishedCacheLow(mContext, videoV)) {
            return false;
        } else {
            return isCacheNeededOnService(videoV, ECO_TYPE_LOW);
        }
    }

    private boolean isCacheNeededOnService(String videoV, int ecoType) {
        IVideoCacheService videoCacheService =
            mVideoCacheServiceConnection.getIVideoCacheService();
        if (videoCacheService == null) {
            // サービスが（まだ）見つからないなら、キャッシュ起動のアイテムは表示する
            return true;
        } else {
            try {
                final int cacheState = videoCacheService.getCacheState(
                        videoV, ecoType);
                if (cacheState == VideoCacheService.CACHE_STATE_NOT_RUN) {
                    return true;
                } else {
                    return false;
                }
            } catch (RemoteException e) {
                Log.e(LOG_TAG, e.toString(), e);
                return true;
            }
        }
    }

    public String getWebTitle() {
        WebView webView = mWebView;
        if (webView == null) {
            return null;
        } else {
            return mWebView.getTitle();
        }
    }
    public String getWebUrl() {
        WebView webView = mWebView;
        if (webView == null) {
            return null;
        } else {
            return mWebView.getUrl();
        }
    }

    public String getVideoV() {
        WatchVideo video = mVideo;
        if (video == null) {
            String url = getWebUrl();
            if (url == null) {
                return null;
            } else {
                // ひとまずURLからの値をそのまま使用して返す
                return getVideoNumberFromUrl(url);
            }
        } else {
            return video.v();
        }
    }

    public String getVideoId() {
        WatchVideo video = mVideo;
        if (video == null) {
            String url = getWebUrl();
            if (url == null) {
                return null;
            } else {
                // ひとまずURLからの値をそのまま使用して返す
                return getVideoNumberFromUrl(url);
            }
        } else {
            return video.id();
        }
    }

    WatchVideo filterVideoByUrl(WatchVideo video, String url) {
        if (url != null) {
            String videoNumber = getVideoNumberFromUrl(url);
            return filterVideoByVideoNumber(video, videoNumber);
        }
        return null;
    }

    WatchVideo filterVideoByVideoNumber(WatchVideo video, String videoNumber) {
        if (video != null && video.isValid() && videoNumber != null) {
            if (video.hasVideoNumber(videoNumber)) {
                return video;
            }
            // URLベースで動画番号が一致したらマッチとみなす
            // XXX →URLから取れる番号とは必ずしも一致しないので動画なら可にする
            String videoUrl = video.getUrl();
            if (videoUrl != null) {
                if (getVideoNumberFromUrl(videoUrl) != null) {
                    return video;
                }
            }
        }
        return null;
    }

    WatchVideo filterLiveVideoByUrl(WatchVideo liveVideo, String url) {
        if (url != null) {
            String liveNumber = getLiveNumberFromUrl(url);
            if (liveVideo != null && liveVideo.isValid() && liveNumber != null) {
                if (liveVideo.hasVideoNumber(liveNumber)) {
                    return liveVideo;
                }
                // URLベースで番号が一致したらマッチとみなす
                // XXX →URLから取れる番号とは必ずしも一致しないので生放送なら可にする
                String liveVideoUrl = liveVideo.getUrl();
                if (liveVideoUrl != null) {
                    if (getLiveNumberFromUrl(liveVideoUrl) != null) {
                        return liveVideo;
                    }
                }
            }
        }
        return null;
    }


    // メニュー関連

    private static class OptionsMenuItem {
        MenuItem flashSetPos;
        MenuItem flashMatchFull;
        MenuItem viewRss;
        MenuItem shareRss;
        MenuItem video;
        MenuItem playNormal;
        MenuItem playLow;
        MenuItem playMid;
        MenuItem cacheNormal;
        MenuItem cacheLow;
        MenuItem cacheMid;
        MenuItem stopCache;
        MenuItem deleteCache;
        MenuItem relatedVideo;
        MenuItem addMylist;
        MenuItem jikkyo;
        MenuItem live;
        MenuItem flashPlayer;
        MenuItem pcSp;
        MenuItem playlist;
    }
    private OptionsMenuItem mOptionsMenuItem;

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // TODO マルチペインなら他のFragmentのMenuと合わせて表示
        inflater.inflate(R.menu.nicoro_webbrowser_menu, menu);
        if (mOptionsMenuItem == null) {
            mOptionsMenuItem = new OptionsMenuItem();
        }
        mOptionsMenuItem.flashSetPos = menu.findItem(R.id.menu_flash_set_pos);
        mOptionsMenuItem.flashMatchFull = menu.findItem(R.id.menu_flash_match_full);
        mOptionsMenuItem.viewRss = menu.findItem(R.id.menu_view_rss);
        mOptionsMenuItem.shareRss = menu.findItem(R.id.menu_share_rss);
        mOptionsMenuItem.video = menu.findItem(R.id.menu_video);
        mOptionsMenuItem.playNormal = menu.findItem(R.id.menu_play_normal);
        mOptionsMenuItem.playLow = menu.findItem(R.id.menu_play_low);
        mOptionsMenuItem.playMid = menu.findItem(R.id.menu_play_mid);
        mOptionsMenuItem.cacheNormal = menu.findItem(R.id.menu_cache_normal);
        mOptionsMenuItem.cacheLow = menu.findItem(R.id.menu_cache_low);
        mOptionsMenuItem.cacheMid = menu.findItem(R.id.menu_cache_mid);
        mOptionsMenuItem.stopCache = menu.findItem(R.id.menu_stop_cache);
        mOptionsMenuItem.deleteCache = menu.findItem(R.id.menu_delete_cache);
        mOptionsMenuItem.relatedVideo = menu.findItem(R.id.menu_related_video);
        mOptionsMenuItem.addMylist = menu.findItem(R.id.menu_add_mylist);
        mOptionsMenuItem.jikkyo = menu.findItem(R.id.menu_jikkyo);
        mOptionsMenuItem.live = menu.findItem(R.id.menu_live);
        mOptionsMenuItem.flashPlayer = menu.findItem(R.id.menu_flash_player);
        mOptionsMenuItem.pcSp = menu.findItem(R.id.menu_pc_sp);
        mOptionsMenuItem.playlist = menu.findItem(R.id.menu_playlist);

        PackageInfo flashPlayerInfo = Util.getFlashPlayerInfo(mContext);
        if (flashPlayerInfo == null) {
            mOptionsMenuItem.flashPlayer.setVisible(false);
        } else {
            mOptionsMenuItem.flashPlayer.setVisible(true);
            if (flashPlayerInfo.applicationInfo != null) {
                if (flashPlayerInfo.applicationInfo.icon != 0) {
                    Drawable icon = flashPlayerInfo.applicationInfo.loadIcon(
                            mContext.getPackageManager());
                    mOptionsMenuItem.flashPlayer.setIcon(icon);
                }
            }
            setFlashPlayerMenuTitle(mOptionsMenuItem.flashPlayer, mWithoutFlash);
        }
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        updateOptionsMenu();
    }

    private void updateOptionsMenu() {
        if (mOptionsMenuItem == null) {
            return;
        }
        // TODO マルチペインなら他のFragmentのMenuと合わせて表示
        boolean enableFlashMenu = !mWithoutFlash;
        mOptionsMenuItem.flashSetPos.setEnabled(enableFlashMenu);
        mOptionsMenuItem.flashMatchFull.setEnabled(enableFlashMenu);

        boolean enableRssMenu = !TextUtils.isEmpty(mJsObj.urlRss);
        mOptionsMenuItem.viewRss.setEnabled(enableRssMenu);
        mOptionsMenuItem.shareRss.setEnabled(enableRssMenu);

        String newVideoV = null;
        String newVideoId = null;
        String newJikkyoNumber = null;
        String newLiveVideoV = null;
        WatchVideo newVideo = null;
        WatchVideo newLiveVideo = null;

        String url = null;
        WebView webView = mWebView;
        if (webView != null) {
            url = webView.getUrl();
        }
        WatchVideo video = mJsObj.video;
        if (url != null) {
            newVideo = filterVideoByUrl(video, url);
            if (newVideo != null) {
                newVideoV = newVideo.v();
                newVideoId = newVideo.id();
            }
            newLiveVideo = filterLiveVideoByUrl(video, url);
            if (newLiveVideo != null) {
                newLiveVideoV = newLiveVideo.v();
            }

            newJikkyoNumber = getJikkyoNumberFromUrl(url);
        }

        mOptionsMenuItem.pcSp.setEnabled(canSwitchPcSp(url));

        EnableMenuJudge judge = mEnableMenuJudgeOptionsMenu;
        synchronized (mEnableMenuJudgeAsync) {
            judge.copyFrom(mEnableMenuJudgeAsync);
        }
        if (!judge.isLatestVideoJudge(mWithoutFlash, newVideoV, newVideoId,
                newLiveVideoV)) {
            judge.judge(mWithoutFlash, newVideoV, newVideoId,
                    newJikkyoNumber, newLiveVideoV, true);
        }
        boolean enablePlayNormal = judge.enablePlayNormal;
        boolean enablePlayLow = judge.enablePlayLow;
        boolean enablePlayMid = judge.enablePlayMid;
        boolean enableCacheNormal = judge.enableCacheNormal;
        boolean enableCacheLow = judge.enableCacheLow;
        boolean enableCacheMid = judge.enableCacheMid;
        boolean enableStopCache = judge.enableStopCache;
        boolean enableDeleteCache = judge.enableDeleteCache;
        boolean enableRelatedVideo = judge.enableRelatedVideo;
        boolean enableAddMylist = judge.enableAddMylist;
        boolean enableJikkyo = judge.enableJikkyo;
        boolean enableLive = judge.enableLive;

        String mylistNumber = getMylistNumber(url);
        boolean enablePlaylist = (mylistNumber != null);

        boolean enableVideoMenu = (enablePlayNormal || enablePlayLow || enablePlayMid
                || enableCacheNormal || enableCacheLow || enableCacheMid
                || enableStopCache || enableDeleteCache || enableRelatedVideo
                || enableAddMylist || enableJikkyo || enableLive);
        mOptionsMenuItem.video.setEnabled(enableVideoMenu);

        boolean visibleNormalVideo = (enablePlayNormal || enablePlayLow || enablePlayMid
                || enableCacheNormal || enableCacheLow || enableCacheMid
                || enableStopCache || enableDeleteCache || enableRelatedVideo
                || enableAddMylist);

        mOptionsMenuItem.playNormal.setEnabled(enablePlayNormal)
            .setVisible(visibleNormalVideo);
        mOptionsMenuItem.playLow.setEnabled(enablePlayLow)
            .setVisible(enablePlayLow);
        mOptionsMenuItem.playMid.setEnabled(enablePlayMid)
            .setVisible(enablePlayMid);
        mOptionsMenuItem.cacheNormal.setEnabled(enableCacheNormal)
            .setVisible(visibleNormalVideo);
        mOptionsMenuItem.cacheLow.setEnabled(enableCacheLow)
            .setVisible(enableCacheLow);
        mOptionsMenuItem.cacheMid.setEnabled(enableCacheMid)
            .setVisible(enableCacheMid);
        mOptionsMenuItem.stopCache.setEnabled(enableStopCache)
            .setVisible(visibleNormalVideo);
        mOptionsMenuItem.deleteCache.setEnabled(enableDeleteCache)
            .setVisible(visibleNormalVideo);
        mOptionsMenuItem.relatedVideo.setEnabled(enableRelatedVideo)
            .setVisible(visibleNormalVideo);
        mOptionsMenuItem.addMylist.setEnabled(enableAddMylist)
            .setVisible(visibleNormalVideo);

        mOptionsMenuItem.jikkyo.setEnabled(enableJikkyo)
            .setVisible(enableJikkyo);
        mOptionsMenuItem.live.setEnabled(enableLive)
            .setVisible(enableLive);

        mOptionsMenuItem.playlist.setEnabled(enablePlaylist)
            .setVisible(enablePlaylist);

        mVideo = newVideo;
        mJikkyoNumber = newJikkyoNumber;
        mLiveVideo = newLiveVideo;
        mMylistNumber = mylistNumber;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Intent intent;
        WebView webView = mWebView;
        switch (item.getItemId()) {
            case R.id.menu_top:
                if (webView != null) {
                    webView.loadUrl("http://www.nicovideo.jp/");
                }
                return true;
            case R.id.menu_reload:
                if (webView != null) {
                    webView.reload();
                }
                return true;
            case R.id.menu_config:
                intent = new Intent(mContext, NicoroConfig.class);
                startActivity(intent);
                return true;
            case R.id.menu_help:
                intent = new Intent(mContext, NicoroHelp.class);
                startActivity(intent);
                return true;
            case R.id.menu_exit:
                NavUtils.navigateUpFromSameTask(getActivity());
                return true;
            case R.id.menu_view:
                // none
                return false;
            case R.id.menu_video:
                // none
                return false;
//          case R.id.menu_more:
//              // none
//              return false;
            case R.id.menu_clear_cache:
                new AlertDialog.Builder(getActivity())
                    .setTitle(R.string.menu_clear_cache)
                    .setMessage(R.string.confirm_clear_cache)
                    .setCancelable(true)
                    .setPositiveButton(android.R.string.yes,
                            new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            new AsyncTask<Void, Void, Void>() {
                                private ProgressDialog progressDialog;

                                @Override
                                protected void onPreExecute() {
                                    progressDialog = Util.createProgressDialogLoading(
                                            getActivity(),
                                            R.string.progress_clear_cache, null);
                                    progressDialog.show();
                                }
                                @Override
                                protected Void doInBackground(Void... params) {
                                    IVideoCacheService videoCacheService =
                                        mVideoCacheServiceConnection.getIVideoCacheService();
                                    // XXX エラーチェック
                                    if (videoCacheService != null) {
                                        try {
                                            videoCacheService.deleteAllCache();
                                        } catch (RemoteException e) {
                                            Log.e(LOG_TAG, e.toString(), e);
                                        }
                                    }
                                    return null;
                                }
                                @Override
                                protected void onPostExecute(Void result) {
                                    progressDialog.dismiss();
                                }
                            }.execute();
                        }
                    })
                    .setNegativeButton(android.R.string.no, null)
                    .show();
                return true;
            case R.id.menu_bookmarks:
//                showBookmark();
                showListMenu(webView, R.id.radio_bookmarks);
                return true;
            case R.id.menu_zoom_in:
                if (webView != null) {
                    webView.zoomIn();
                }
                break;
            case R.id.menu_zoom_out:
                if (webView != null) {
                    webView.zoomOut();
                }
                break;
            case R.id.menu_flash_set_pos:
                if (webView != null) {
                    mJsObj.msgIdGetOffsetFlvplayerContainer = MSG_ID_FLASH_SET_POS;
                    webView.loadUrl(mResJsGetOffsetFlvplayerContainer);
                }
                return true;
            case R.id.menu_flash_match_full:
                if (webView != null) {
                    mJsObj.msgIdGetOffsetFlvplayerContainer = MSG_ID_FLASH_MATCH_FULL;
                    webView.loadUrl(mResJsGetOffsetFlvplayerContainer);
                }
                return true;
//            case R.id.menu_access_history:
//                accessHistory();
//                return true;
            case R.id.menu_play_normal:
                startPlay(mVideo, ECO_TYPE_HIGH);
                return true;
            case R.id.menu_play_mid:
                startPlay(mVideo, ECO_TYPE_MID);
                return true;
            case R.id.menu_play_low:
                startPlay(mVideo, ECO_TYPE_LOW);
                return true;
            case R.id.menu_cache_normal:
                startCache(ECO_TYPE_HIGH);
                return true;
            case R.id.menu_cache_mid:
                startCache(ECO_TYPE_MID);
                return true;
            case R.id.menu_cache_low:
                startCache(ECO_TYPE_LOW);
                return true;
            case R.id.menu_delete_cache:
                deleteCache();
                return true;
            case R.id.menu_related_video:
//                relatedVideo();
                showListMenu(webView, R.id.radio_related_video);
                return true;
            case R.id.menu_add_mylist:
                addMylist();
                return true;
            case R.id.menu_live:
                startLive(mLiveVideo);
                return true;
            case R.id.menu_jikkyo:
                startJikkyo(mJikkyoNumber);
                return true;
            case R.id.menu_view_rss:
                viewRss();
                return true;
            case R.id.menu_share_rss:
                shareRss();
                return true;
            case R.id.menu_share_url:
                shareUrl();
                return true;
            case R.id.menu_go_back:
                if (webView != null) {
                    webView.goBack();
                }
                return true;
            case R.id.menu_go_forward:
                if (webView != null) {
                    webView.goForward();
                }
                return true;
            case R.id.menu_search:
                getActivity().onSearchRequested();
                return true;
            case R.id.menu_list_menu:
                showListMenu(webView, null);
                return true;
            case R.id.menu_page_info:
                if (webView != null) {
                    FragmentManager manager = getFragmentManager();
                    FragmentTransaction transaction = manager.beginTransaction();
                    Fragment prev = manager.findFragmentByTag("dialog");
                    if (prev != null) {
                        transaction.remove(prev);
                    }
                    transaction.addToBackStack(null);

                    String cacheInfo;
                    String videoV = getVideoV();
                    String videoId = getVideoId();
                    if (mWithoutFlash) {
                        if (videoV == null && videoId == null) {
                            cacheInfo = null;
                        } else {
                            assert videoV != null && videoId != null;
                            StringBuilder builder = new StringBuilder();
                            updateStatus1CacheState(builder, videoV, videoId);
                            cacheInfo = builder.toString();
                        }
                    } else {
                        cacheInfo = null;
                    }
                    PageInfoDialogFragment dialogFragment = PageInfoDialogFragment.newInstance(
                            webView.getTitle(), webView.getUrl(), videoId, cacheInfo);
                    dialogFragment.show(transaction, "dialog");
                }
                break;
            case R.id.menu_flash_player:
                boolean newWithoutFlash = !mWithoutFlash;
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                editor.putBoolean(mResStringPrefKeyWithoutFlash, newWithoutFlash);
                editor.commit();
                return true;
            case R.id.menu_pc_sp:
                if (!switchPcSp(webView)) {
                    Util.showErrorToast(mContext, R.string.toast_pc_sp);
                }
                return true;
            case R.id.menu_copy_text:
                APILevelWrapper api = APILevelWrapper.createInstance();
                if (webView != null) {
                    api.emulateShiftHeld(webView);
                }
                return true;
            case R.id.menu_stop_cache:
                stopCache();
                return true;
            case R.id.menu_playlist:
                startPlaylist();
                return true;
            default:
                assert false : item.getItemId();
                break;
        }
        return false;
    }

    private void setFlashPlayerMenuTitle(MenuItem item, boolean withoutFlash) {
        StringBuilder builder = new StringBuilder(10);
//        if (item.getIcon() == null) {
            builder.append("Flash ");
//        }
        String titleCondensed;
        if (withoutFlash) {
            builder.append("ON");
            titleCondensed = "ON";
        } else {
            builder.append("OFF");
            titleCondensed = "OFF";
        }
        item.setTitle(builder.toString());
        item.setTitleCondensed(titleCondensed);
    }

    private boolean switchPcSp(WebView webView) {
        if (webView == null) {
            return false;
        }
        String url = webView.getUrl();
        try {
            URL forParse = new URL(url);
            String host = forParse.getHost();
            if ("sp.nicovideo.jp".equals(host)) {
                String file = URLEncoder.encode(forParse.getFile(), HTTP.UTF_8);
                webView.loadUrl("http://sp.nicovideo.jp/change_mode?mode=pc&uri=" + file);
                setCookieNicovideo("usepc=1");
                return true;
            } else if ("sp.live.nicovideo.jp".equals(host)) {
                // TODO PCへの生放送切り替えはトップページ固定
                webView.loadUrl("http://sp.live.nicovideo.jp/gotopc");
                setCookieNicovideo("usepc=1");
                return true;
            } else if ("sp.ch.nicovideo.jp".equals(host)) {
                String file = forParse.getFile().replace("/info/", "/channel/")
                    .replace("/live_top", "");
                URL newUrl = new URL(forParse.getProtocol(),
                        "ch.nicovideo.jp", file);
                webView.loadUrl(newUrl.toExternalForm());
                setCookieNicovideo("usepc=1");
                return true;
            } else if ("www.nicovideo.jp".equals(host)) {
                String file = URLEncoder.encode(forParse.getFile(), HTTP.UTF_8);
                webView.loadUrl("http://sp.nicovideo.jp/change_mode?mode=sp&uri=" + file);
                setCookieNicovideo("usepc=0");
                return true;
            } else if ("live.nicovideo.jp".equals(host)) {
                String lv = (mLiveVideo == null ? null : mLiveVideo.v());
                if (lv == null) {
                    webView.loadUrl("http://live.nicovideo.jp/gotosp");
                } else {
                    webView.loadUrl("http://live.nicovideo.jp/gotosp?v=" + lv + "&a=watch");
                }
                setCookieNicovideo("usepc=0");
                return true;
            } else if ("ch.nicovideo.jp".equals(host)) {
                String file = forParse.getFile().replace("/channel/", "/info/");
                URL newUrl = new URL(forParse.getProtocol(),
                        "sp.ch.nicovideo.jp", file);
                webView.loadUrl(newUrl.toExternalForm());
                setCookieNicovideo("usepc=0");
                return true;
            }
        } catch (MalformedURLException e) {
            Log.w(LOG_TAG, e.toString(), e);
        } catch (UnsupportedEncodingException e) {
            Log.w(LOG_TAG, e.toString(), e);
        }
        return false;
    }

    private boolean canSwitchPcSp(String url) {
        if (url == null) {
            return false;
        }
        Matcher matcher = mMatcherCanSwitchPcSp.get().reset(url);
        return matcher.find();
    }

    @SuppressWarnings("deprecation")
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        Activity activity = getActivity();
        if (activity == null) {
            Log.w(LOG_TAG, "Fragment has been detached from Activitiy");
            return;
        }
        if (v instanceof WebView) {
            MenuInflater inflater = activity.getMenuInflater();
            WebView webView = (WebView) v;
            HitTestResult result = webView.getHitTestResult();
            mHitTestResultExtra = result.getExtra();
            switch (result.getType()) {
                case HitTestResult.SRC_ANCHOR_TYPE:
                    menu.setHeaderTitle(mHitTestResultExtra);
                    inflater.inflate(R.menu.webbrowser_contextmenu_src, menu);
                    break;
                case HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
                    menu.setHeaderTitle(mHitTestResultExtra);
                    inflater.inflate(R.menu.webbrowser_contextmenu_src, menu);
                    inflater.inflate(R.menu.webbrowser_contextmenu_img, menu);
                    break;
                case HitTestResult.IMAGE_TYPE:
                case HitTestResult.IMAGE_ANCHOR_TYPE:
                    menu.setHeaderTitle(mHitTestResultExtra);
                    inflater.inflate(R.menu.webbrowser_contextmenu_img, menu);
                    break;
                default:
                    break;
            }
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        WebView webView = mWebView;
        switch (item.getItemId()) {
            case R.id.menu_open:
                assert mHitTestResultExtra != null;
                if (webView != null) {
                    webView.loadUrl(mHitTestResultExtra);
                }
                return true;
            case R.id.menu_open_other_app:
                assert mHitTestResultExtra != null;
                startActivityViewUrl(mHitTestResultExtra);
                return true;
            case R.id.menu_copy_url:
                assert mHitTestResultExtra != null;
                ClipboardManager clipboardManager =
                    (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
                clipboardManager.setText(mHitTestResultExtra);
                return true;
//            case R.id.menu_save_img:
//                assert mHitTestResultExtra != null;
//                Intent intent = new Intent(Intent.ACTION_VIEW)
//                    .setData(Uri.parse(mHitTestResultExtra))
//                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//                try {
//                    startActivity(intent);
//                } catch (ActivityNotFoundException e) {
//
//                }
//                return true;
            case R.id.menu_view_img:
                assert mHitTestResultExtra != null;
                if (webView != null) {
                    webView.loadUrl(mHitTestResultExtra);
                }
                return true;
            case R.id.menu_view_img_other_app:
                assert mHitTestResultExtra != null;
                startActivityViewUrl(mHitTestResultExtra);
                return true;
//            case R.id.menu_wallpaper_img:
//                assert mHitTestResultExtra != null;
//                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    private boolean isAnyCacheRunning(String videoNumber) {
        IVideoCacheService videoCacheService = mVideoCacheServiceConnection.getIVideoCacheService();
        if (videoCacheService == null) {
            return false;
        }
        return VideoCacheService.isAnyCacheRunning(videoNumber, videoCacheService);
    }

    private class EnableMenuJudgeUpdater extends AsyncTask<Void, Void, Void> {
        private EnableMenuJudge mJudge = new EnableMenuJudge();

        private String newVideoV = null;
        private String newVideoId = null;
        private String newJikkyoNumber = null;
        private String newLiveVideoV = null;

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

            WatchVideo newVideo;
            WatchVideo newLiveVideo;

            String url = null;
            WebView webView = mWebView;
            if (webView != null) {
                url = webView.getUrl();
            }
            WatchVideo video = mJsObj.video;
            if (url != null) {
                newVideo = filterVideoByUrl(video, url);
                if (newVideo != null) {
                    newVideoV = newVideo.v();
                    newVideoId = newVideo.id();
                }
                newLiveVideo = filterLiveVideoByUrl(video, url);
                if (newLiveVideo != null) {
                    newLiveVideoV = newLiveVideo.v();
                }

                newJikkyoNumber = getJikkyoNumberFromUrl(url);
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            mJudge.judge(mWithoutFlash, newVideoV, newVideoId,
                    newJikkyoNumber, newLiveVideoV, false);

            synchronized (mEnableMenuJudgeAsync) {
                mEnableMenuJudgeAsync.copyFrom(mJudge);
            }

            mHandler.sendEmptyMessage(MSG_ID_UPDATE_OPTIONS_MENU);
            mHandler.sendEmptyMessage(MSG_ID_FINISHED_READ_VIDEO);
            return null;
        }
    }

    private class EnableMenuJudge {
        boolean enablePlayNormal;
        boolean enablePlayLow;
        boolean enablePlayMid;
        boolean enableCacheNormal;
        boolean enableCacheLow;
        boolean enableCacheMid;
        boolean enableStopCache;
        boolean enableDeleteCache;
        boolean enableRelatedVideo;
        boolean enableAddMylist;
        boolean enableJikkyo;
        boolean enableLive;
        boolean isSwf;

        // 前回判定に使用したパラメータ
        private boolean mLastWithoutFlash;
        private String mLastVideoV;
        private String mLastVideoId;
        private String mLastJikkyoNumber;
        private String mLastLiveNumber;
        private boolean mHasLastParameter;

        void judge(boolean withoutFlash, String newVideoV, String newVideoId,
                String newJikkyoNumber, String newLiveNumber, boolean inMainThread) {
            if (!inMainThread) {
                mLastWithoutFlash = withoutFlash;
                mLastVideoV = newVideoV;
                mLastVideoId = newVideoId;
                mLastJikkyoNumber = newJikkyoNumber;
                mLastLiveNumber = newLiveNumber;
                mHasLastParameter = true;
            }

            enablePlayNormal = false;
            enablePlayLow = false;
            enablePlayMid = false;
            enableCacheNormal = false;
            enableCacheLow = false;
            enableCacheMid = false;
            enableStopCache = false;
            enableDeleteCache = false;
            enableRelatedVideo = false;
            enableAddMylist = false;
            enableJikkyo = false;
            enableLive = false;
            isSwf = false;

            if (newVideoV != null) {
                if (withoutFlash) {
                    // XXX この取り方だとタイミング的にちょっと甘い
                    ThumbInfo thumbInfo = mThumbInfoCacher.getThumbInfo(newVideoId);
                    final boolean hasLow = ((thumbInfo == null)
                            ? true : thumbInfo.getSizeLow() > 0);
                    // 動画
                    isSwf = NicoroAPIManager.isVideoNumberSwf(newVideoId);
                    if (isSwf) {
                        // swf動画
                        enablePlayNormal = true;
                    } else {
                        // flvまたはmp4動画
                        enablePlayNormal = true;
                        // XXX 低画質無し・中画質有りのパターンもあるのでとりあえず有りと見なす
                        enablePlayMid = true;
                        if (hasLow) {
                            enablePlayLow = true;
                        }
                    }

                    // UIスレッドでのファイル読み込みの回避
                    if (!inMainThread) {
                        if (isCacheNeeded(newVideoV)) {
                            enableCacheNormal = true;
                        }
                        if (!isSwf) {
                            // XXX 低画質無し・中画質有りのパターンもあるのでとりあえず有りと見なす
                            if (isCacheNeededMid(newVideoV)) {
                                enableCacheMid = true;
                            }

                            if (hasLow) {
                                if (isCacheNeededLow(newVideoV)) {
                                    enableCacheLow = true;
                                }
                            }
                        }
                    }
                }

                enableRelatedVideo = true;
                enableAddMylist = true;

                if (isAnyCacheRunning(newVideoV)) {
                    enableStopCache = true;
                }

                // UIスレッドでのファイル読み込みの回避
                if (!inMainThread) {
                    if (VideoLoader.hasAnyCacheFile(mContext, newVideoV)) {
                        enableDeleteCache = true;
                    }
                }
            }

            if (newJikkyoNumber != null) {
                enableJikkyo = true;
            }

            if (withoutFlash) {
                if (newLiveNumber != null) {
                    enableLive = true;
                }
            }
        }

        boolean isLatestVideoJudge(boolean withoutFlash, String newVideoV, String newVideoId,
                String newLiveNumber) {
            return (mHasLastParameter
                    && withoutFlash == mLastWithoutFlash
                    && TextUtils.equals(newVideoV, mLastVideoV)
                    && TextUtils.equals(newVideoId, mLastVideoId)
                    && TextUtils.equals(newLiveNumber, mLastLiveNumber));
        }

        void clearLastParameter() {
            mLastWithoutFlash = true;
            mLastVideoV = null;
            mLastVideoId = null;
            mLastJikkyoNumber = null;
            mLastLiveNumber = null;
            mHasLastParameter = false;
        }

        void copyFrom(EnableMenuJudge src) {
            enablePlayNormal = src.enablePlayNormal;
            enablePlayLow = src.enablePlayLow;
            enablePlayMid = src.enablePlayMid;
            enableCacheNormal = src.enableCacheNormal;
            enableCacheLow = src.enableCacheLow;
            enableCacheMid = src.enableCacheMid;
            enableStopCache = src.enableStopCache;
            enableDeleteCache = src.enableDeleteCache;
            enableRelatedVideo = src.enableRelatedVideo;
            enableAddMylist = src.enableAddMylist;
            enableJikkyo = src.enableJikkyo;
            enableLive = src.enableLive;
            isSwf = src.isSwf;

            mLastWithoutFlash = src.mLastWithoutFlash;
            mLastVideoV = src.mLastVideoV;
            mLastVideoId = src.mLastVideoId;
            mLastJikkyoNumber = src.mLastJikkyoNumber;
            mLastLiveNumber = src.mLastLiveNumber;
            mHasLastParameter = src.mHasLastParameter;
        }
    }

    private class JsObj {
        private static final String DIV_STYLE = "text-align: left; "
            + "border: dotted medium #000000; "
            + "margin: 5px 8px; padding: 5px 7px;"
            + "color: #000000; background-color: #f0f8ff; "
            + "font-size: 14pt; ";
        private static final String STRONG_STYLE = "font-weight: bold; "
            + "font-size: 16pt; ";

        public String urlRss;
        public String titleRss;
        public String streamDescription;
        public int msgIdGetOffsetFlvplayerContainer = -1;
        public WatchVideo video;
        private HashMap<String, String> mVideoMap = new HashMap<String, String>();
        private String mVideoUrl;
        private EnableMenuJudge mEnableMenuJudgeFlvplayerContainer = new EnableMenuJudge();

        @SuppressWarnings("unused")
        public void getRss(String url, String title) {
            urlRss = url;
            titleRss = title;
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("RSS: url=").append(url)
                        .append(" title=").append(title).toString());
            }
            mHandler.sendEmptyMessage(MSG_ID_UPDATE_OPTIONS_MENU);
        }

        @SuppressWarnings("unused")
        public String getFlvplayerContainerHtml(String url) {
            StringBuilder builder = new StringBuilder();
            builder.append("<div style=\"").append(DIV_STYLE).append("\">");
            getFlvplayerContainerMain(builder, url);
            builder.append("</div>");

            return builder.toString();
        }

        @SuppressWarnings("unused")
        public String getNicoplayerContainerInnerHtml(String url) {
            StringBuilder builder = new StringBuilder();
            builder.append("<div style=\"").append(DIV_STYLE).append("\">");
            getFlvplayerContainerMain(builder, url);

            WatchVideo wv = mVideo;
            if (wv == null) {
                wv = mLiveVideo;
            }

            if (wv != null) {
                Resources res = mContext.getResources();
                String description = wv.description();
                if (description != null) {
                    builder.append("<br /><p><strong style=\"")
                        .append(STRONG_STYLE).append("\">")
                        .append(res.getString(R.string.video_description))
                        .append(":</strong><br />")
                        .append(description).append("</p>");
                }
                ArrayList<WatchVideo.TagList> tagList = wv.getTagList();
                if (tagList != null) {
                    builder.append("<br /><p><strong style=\"")
                        .append(STRONG_STYLE).append("\">")
                        .append(res.getString(R.string.tag)).append(":</strong><br />");
                    for (WatchVideo.TagList tag : tagList) {
                        if (!TextUtils.isEmpty(tag.tag)) {
                            builder.append("<a href=\"/tag/")
                                .append(tag.tag).append("\">")
                                .append(tag.tag).append("</a>");
                            if (tag.dic) {
                                builder.append("<a href=\"").append(URL_DIC_A)
                                .append(tag.tag).append("\"><img src=\"")
                                .append(URL_ICON_DIC_ON).append("\" alt=\"大百科\" /></a>");
                            }
                            builder.append("&nbsp;&nbsp;");
                        }
                    }
                    builder.append("</p>");
                }
            }

            builder.append("</div>");

            return builder.toString();
        }

        @SuppressWarnings("unused")
        public void startPlay() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_PLAY,
                    ECO_TYPE_HIGH, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void startPlayMid() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_PLAY,
                    ECO_TYPE_MID, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void startPlayLow() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_PLAY,
                    ECO_TYPE_LOW, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void startCache() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_CACHE,
                    ECO_TYPE_HIGH, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void startCacheMid() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_CACHE,
                    ECO_TYPE_MID, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void startCacheLow() {
            mHandler.obtainMessage(MSG_ID_ACTION_START_CACHE,
                    ECO_TYPE_LOW, 0)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void relatedVideo() {
            mHandler.obtainMessage(MSG_ID_ACTION_RELATED_VIDEO)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void stopCache() {
            mHandler.obtainMessage(MSG_ID_ACTION_STOP_CACHE)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void deleteCache() {
            mHandler.obtainMessage(MSG_ID_ACTION_DELETE_CACHE)
                .sendToTarget();
        }
        @SuppressWarnings("unused")
        public void addMylist() {
            mHandler.obtainMessage(MSG_ID_ACTION_ADD_MYLIST)
                .sendToTarget();
        }

        @SuppressWarnings("unused")
        public String getPlayerboxHtml(String url) {
            String newJikkyoNumber = null;

            StringBuilder builder = new StringBuilder();
            Resources res = mContext.getResources();
            builder.append("<div style=\"").append(DIV_STYLE).append("\"><strong style=\"")
                .append(STRONG_STYLE).append("\">")
                .append(res.getString(R.string.js_flvplayer_container_menu_title))
                .append("</strong><form action=\"javascript:void(0)\"><ul>");

            if (mCookieUserSession != null && url != null) {
                newJikkyoNumber = getJikkyoNumberFromUrl(url);
                if (newJikkyoNumber != null) {
                    appendLiButton(builder, "nicoro.startJikkyo()",
                            res.getString(R.string.action_item_jikkyo));
                }
            }

            builder.append("</ul></form></div>");

            mJikkyoNumber = newJikkyoNumber;

            return builder.toString();
        }

        @SuppressWarnings("unused")
        public void startJikkyo() {
            mHandler.sendEmptyMessage(MSG_ID_ACTION_START_JIKKYO);
        }

        @SuppressWarnings("unused")
        public void startLive() {
            mHandler.sendEmptyMessage(MSG_ID_ACTION_START_LIVE);
        }

        @SuppressWarnings("unused")
        public void goBack() {
            mHandler.sendEmptyMessage(MSG_ID_GO_BACK_WEBVIEW);
        }

        @SuppressWarnings("unused")
        public void getStreamDescription(String strDes) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("stream_description=")
                        .append(strDes).toString());
            }
            streamDescription = strDes;
        }

        @SuppressWarnings("unused")
        public void log(String text) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, text);
            }
        }

        @SuppressWarnings("unused")
        public void getOffsetFlvplayerContainer(int offsetLeft, int offsetTop,
                int offsetWidth, int offsetHeight) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("getOffsetFlvplayerContainer: ")
                        .append(offsetLeft).append(",").append(offsetTop).append(",")
                        .append(offsetWidth).append(",").append(offsetHeight)
                        .append(" msgIdGetOffsetFlvplayerContainer=").append(msgIdGetOffsetFlvplayerContainer)
                        .toString());
            }
//            mHandler.obtainMessage(MSG_ID_FLASH_SET_POS, offsetLeft, offsetTop).sendToTarget();
            mHandler.obtainMessage(msgIdGetOffsetFlvplayerContainer,
                    new Rect(offsetLeft, offsetTop,
                            offsetLeft + offsetWidth, offsetTop + offsetHeight))
                            .sendToTarget();
        }

        @SuppressWarnings("unused")
        public void startReadVideo(String url) {
            mVideoMap.clear();
            mVideoUrl = url;
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Video URL=").append(url)
                        .toString());
            }
        }

        @SuppressWarnings("unused")
        public void readVideo(String prop, String value) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("Video.").append(prop)
                        .append('=').append(value).toString());
            }
            mVideoMap.put(prop, value);
        }

        @SuppressWarnings("unused")
        public void endReadVideo() {
            WatchVideo watchVideo = WatchVideo.createFromVideoMap(mVideoMap);
            watchVideo.setUrl(mVideoUrl);
            updateVideo(watchVideo);
        }

        @SuppressWarnings("unused")
        public void readWatchAPIDataContainer(String url,
                String watchAPIDataContainer) {
            try {
                JSONObject json = new JSONObject(watchAPIDataContainer);
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("URL=").append(url)
                            .append(" watchAPIDataContainer=").toString());
                    Log.d(LOG_TAG, json.toString(2));
                }

                updateVideo(WatchVideo.createFromWatchAPIDataContainer(json));
            } catch (JSONException e) {
                Log.w(LOG_TAG, e.toString(), e);
                if (DEBUG_LOGD) {
                    Log.dLong(LOG_TAG, watchAPIDataContainer);
                }
            }
        }

        @SuppressWarnings("unused")
        public void failReadVideo(String url) {
            if (url != null) {
                // XXX Videoが含まれていない場合はURLから仮生成
                // （spの生放送、ページ情報一部破損？等）
                String number = getVideoNumberFromUrl(url);
                if (number == null) {
                    number = getLiveNumberFromUrl(url);
                }
                if (number != null) {
                    WatchVideo watchVideo = WatchVideo.createTemp(number);
                    watchVideo.setUrl(url);
                    updateVideo(watchVideo);
                }
            }
        }

        private StringBuilder getFlvplayerContainerMain(StringBuilder builder,
                String url) {
            Resources res = mContext.getResources();
            builder.append("<strong>")
                .append(res.getString(R.string.js_flvplayer_container_menu_title))
                .append("</strong><form action=\"javascript:void(0)\"><ul>");

            String newVideoV = null;
            String newVideoId = null;
            String newLiveVideoV = null;
            WatchVideo newVideo = null;
            WatchVideo newLiveVideo = null;

            if (mCookieUserSession != null && url != null) {
                newVideo = filterVideoByUrl(video, url);
                if (newVideo != null) {
                    newVideoV = newVideo.v();
                    newVideoId = newVideo.id();
                }
                newLiveVideo = filterLiveVideoByUrl(video, url);
                if (newLiveVideo != null) {
                    newLiveVideoV = newLiveVideo.v();
                }
            }

            mVideo = newVideo;
            mLiveVideo = newLiveVideo;

            EnableMenuJudge judge = mEnableMenuJudgeFlvplayerContainer;
            synchronized (mEnableMenuJudgeAsync) {
                judge.copyFrom(mEnableMenuJudgeAsync);
            }
            if (!judge.isLatestVideoJudge(true, newVideoV, newVideoId,
                    newLiveVideoV)) {
                // メインスレッド扱いで読み込み
                judge.judge(true, newVideoV, newVideoId,
                        null, newLiveVideoV, true);
            }
            if (judge.enablePlayNormal) {
                int playStringId;
                if (judge.isSwf) {
                    // swf動画
                    playStringId = R.string.action_item_play_swf;
                } else {
                    // flvまたはmp4動画
                    playStringId = R.string.action_item_play_normal;
                }
                appendLiButton(builder, "nicoro.startPlay()",
                        res.getString(playStringId));
            }
            if (judge.enablePlayMid) {
                appendLiButton(builder, "nicoro.startPlayMid()",
                        res.getString(R.string.action_item_play_normal_mid));
            }
            if (judge.enablePlayLow) {
                appendLiButton(builder, "nicoro.startPlayLow()",
                        res.getString(R.string.action_item_play_normal_low));
            }
            if (judge.enableCacheNormal) {
                appendLiButton(builder, "nicoro.startCache()",
                        res.getString(R.string.action_item_cache));
            }
            if (judge.enableCacheMid) {
                appendLiButton(builder, "nicoro.startCacheMid()",
                        res.getString(R.string.action_item_cache_mid));
            }
            if (judge.enableCacheLow) {
                appendLiButton(builder, "nicoro.startCacheLow()",
                        res.getString(R.string.action_item_cache_low));
            }
            if (judge.enableRelatedVideo) {
                appendLiButton(builder, "nicoro.relatedVideo()",
                        res.getString(R.string.action_item_related_video));
            }
            if (judge.enableStopCache) {
                appendLiButton(builder, "nicoro.stopCache()",
                        res.getString(R.string.action_item_stop_cache_one));
            }
            if (judge.enableDeleteCache) {
                appendLiButton(builder, "nicoro.deleteCache()",
                        res.getString(R.string.action_item_delete_cache_one));
            }
            if (judge.enableAddMylist) {
                appendLiButton(builder, "nicoro.addMylist()",
                        res.getString(R.string.action_item_add_mylist));
            }
            if (judge.enableLive) {
                appendLiButton(builder, "nicoro.startLive()",
                        res.getString(R.string.action_item_live));
            }

            builder.append("</ul></form>");

            return builder;
        }

        private void updateVideo(WatchVideo v) {
            video = v;
            mHandler.sendEmptyMessage(MSG_ID_ON_UPDATE_VIDEO);
        }
    }

    static StringBuilder appendLiButton(StringBuilder builder,
            String function, String text) {
        return builder.append("<li><button onclick=\"")
            .append(function)
            .append("; return false;\">")
            .append(text)
            .append("</button>");
    }

    private void startActivityViewUrl(String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW,
                Uri.parse(url))
            .addCategory(Intent.CATEGORY_BROWSABLE)
            .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivity(intent);
    }
}
