org.deviceconnect.android.manager.accesslog.AccessLogActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.deviceconnect.android.manager.accesslog.AccessLogActivity.java

Source

/*
 AccessLogActivity.java
 Copyright (c) 2014 NTT DOCOMO,INC.
 Released under the MIT license
 http://opensource.org/licenses/mit-license.php
 */
package org.deviceconnect.android.manager.accesslog;

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;

import org.deviceconnect.android.manager.R;
import org.deviceconnect.server.nanohttpd.accesslog.AccessLog;
import org.deviceconnect.server.nanohttpd.accesslog.AccessLogProvider;
import org.deviceconnect.android.manager.setting.BaseSettingActivity;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * ???? Activity.
 *
 * @author NTT DOCOMO, INC.
 */
public class AccessLogActivity extends BaseSettingActivity {
    /**
     * ?.
     */
    private AccessLogProvider mAccessLogProvider;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAccessLogProvider = new AccessLogProvider(getApplicationContext());

        setTitle(R.string.activity_settings_accesslog);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        if (savedInstanceState == null) {
            addFragment(DateListFragment.create(), false);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            callFragment();
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            callFragment();
            FragmentManager manager = getSupportFragmentManager();
            if (manager.getBackStackEntryCount() > 0) {
                manager.popBackStack();
            } else {
                finish();
            }
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Fragment ? {@link BaseFragment#onReturn()} ????.
     */
    private void callFragment() {
        FragmentManager manager = getSupportFragmentManager();
        List<Fragment> fragments = manager.getFragments();
        for (Fragment fragment : fragments) {
            if (fragment instanceof BaseFragment) {
                ((BaseFragment) fragment).onReturn();
            }
        }
    }

    /**
     * ???.
     *
     * @param fragment ?
     * @param isBackStack ?????true????false
     */
    private void addFragment(Fragment fragment, boolean isBackStack) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(android.R.id.content, fragment, "container");
        if (isBackStack) {
            transaction.addToBackStack(null);
        }
        transaction.commit();
    }

    /**
     * ???.
     *
     * @param fragment ?
     */
    private void addFragment(Fragment fragment) {
        addFragment(fragment, true);
    }

    /**
     * AccessLogProvider ?????.
     *
     * @return AccessLogProvider ?
     */
    private AccessLogProvider getAccessLogProvider() {
        return mAccessLogProvider;
    }

    /**
     * ?.
     */
    public static abstract class BaseFragment extends Fragment {
        /**
         * ???.
         */
        private Handler mHandler = new Handler(Looper.getMainLooper());

        /**
         * ?.
         */
        private AccessLogProvider mAccessLogProvider;

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

            AccessLogActivity activity = (AccessLogActivity) getActivity();
            if (activity != null) {
                mAccessLogProvider = activity.getAccessLogProvider();
            }
        }

        /**
         * ?????????.
         */
        void onReturn() {
        }

        /**
         * ?? Fragment ???.
         * <p>
         * ?? Fragment ?????? Activity ???
         * </p>
         */
        void popFragment() {
            FragmentManager manager = getFragmentManager();
            if (manager.getBackStackEntryCount() > 0) {
                manager.popBackStack();
            } else {
                getActivity().finish();
            }
        }

        /**
         * AccessLogProvider ?????.
         * <p>
         * Activity????????? null ????
         * </p>
         * @return AccessLogProvider ?
         */
        AccessLogProvider getAccessLogProvider() {
            return mAccessLogProvider;
        }

        /**
         * ?? Runnable ????.
         *
         * @param runnable ??Runnable
         */
        void runOnUiThread(Runnable runnable) {
            mHandler.post(runnable);
        }

        /**
         * ???.
         *
         * @param fragment t
         */
        void gotoFragment(Fragment fragment) {
            AccessLogActivity activity = (AccessLogActivity) getActivity();
            if (activity != null) {
                activity.addFragment(fragment);
            }
        }
    }

    /**
     * RecyclerView?? RecyclerView.Adapter ???.
     *
     * @param <A> ?
     * @param <T> RecyclerView.ViewHolder??
     */
    private static abstract class BaseAdapter<A, T extends RecyclerView.ViewHolder>
            extends RecyclerView.Adapter<T> {
        /**
         * ?.
         */
        List<A> mDataList = new ArrayList<>();

        /**
         * .
         */
        LayoutInflater mInflater;

        /**
         * ??????.
         */
        OnItemClickListener mOnItemClickListener;

        /**
         * ???.
         */
        OnItemRemoveListener<A> mOnItemRemoveListener;

        /**
         * Undo??Snackbar.
         */
        Snackbar mSnackbar;

        /**
         * .
         * @param inflater 
         */
        BaseAdapter(LayoutInflater inflater) {
            mInflater = inflater;
        }

        /**
         * Undo?? Snackbar ?????.
         */
        void dismissSnackbar() {
            if (mSnackbar != null) {
                mSnackbar.dismiss();
            }
        }

        /**
         * ???.
         *
         * @param dataList ??
         */
        void updateDataList(List<A> dataList) {
            if (dataList == null || mDataList.equals(dataList)) {
                return;
            }
            mDataList = dataList;
            notifyDataSetChanged();
        }

        /**
         * ????????.
         *
         * @param position ?
         * @return 
         */
        A getItem(int position) {
            return mDataList.get(position);
        }

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

        /**
         * ???????????.
         *
         * @param viewHolder view
         * @param recyclerView View
         */
        void onItemRemove(final RecyclerView.ViewHolder viewHolder, final RecyclerView recyclerView) {
            final int adapterPosition = viewHolder.getAdapterPosition();
            final A removeData = mDataList.get(adapterPosition);
            mSnackbar = Snackbar
                    .make(recyclerView, R.string.activity_accesslog_remove_date, Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.activity_accesslog_undo_remove_date, (View view) -> {
                        mDataList.add(adapterPosition, removeData);
                        notifyItemInserted(adapterPosition);
                    }).addCallback(new Snackbar.Callback() {
                        @Override
                        public void onDismissed(Snackbar snackbar, int event) {
                            // ??? Action ??????????
                            if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
                                if (mOnItemRemoveListener != null) {
                                    mOnItemRemoveListener.onItemDelete(removeData);
                                }
                            }
                        }
                    });
            mSnackbar.show();
            mDataList.remove(adapterPosition);
            notifyItemRemoved(adapterPosition);
        }

        /**
         * ?????????.
         *
         * @param listener 
         */
        void setOnItemClickListener(OnItemClickListener listener) {
            mOnItemClickListener = listener;
        }

        /**
         * ?????????.
         *
         * @param listener 
         */
        void setOnItemRemoveListener(OnItemRemoveListener<A> listener) {
            mOnItemRemoveListener = listener;
        }

        /**
         * .
         */
        interface OnItemClickListener {
            void onItemClick(View view, int position);
        }

        /**
         * .
         */
        interface OnItemRemoveListener<A> {
            void onItemDelete(A data);
        }
    }

    /**
     * ???.
     *
     */
    public static class DateListFragment extends BaseFragment {
        /**
         * .
         */
        private static final int REQUEST_CODE = 100;

        /**
         * ???.
         */
        private DateListAdapter mListAdapter;

        /**
         * DateListFragment ?????.
         *
         * @return DateListFragment?
         */
        static DateListFragment create() {
            return new DateListFragment();
        }

        @SuppressLint("ClickableViewAccessibility")
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View root = inflater.inflate(R.layout.fragment_accesslog_date_list, container, false);

            mListAdapter = new DateListAdapter(inflater);
            mListAdapter.setOnItemClickListener(
                    (v, position) -> gotoAccessLogListFragment(mListAdapter.getItem(position)));
            mListAdapter.setOnItemRemoveListener((data) -> {
                AccessLogProvider provider = getAccessLogProvider();
                if (provider != null) {
                    provider.remove(data);
                }
            });

            RecyclerView recyclerView = root.findViewById(R.id.recycler_view_accesslog_date_list);
            recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            recyclerView.setAdapter(mListAdapter);
            recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
            recyclerView.setOnTouchListener((v, event) -> {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mListAdapter.dismissSnackbar();
                    break;
                }
                return false;
            });

            ItemTouchHelper helper = new ItemTouchHelper(new SwipeToDeleteCallback(getContext()) {
                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    mListAdapter.onItemRemove(viewHolder, recyclerView);
                }
            });
            helper.attachToRecyclerView(recyclerView);

            setHasOptionsMenu(true);

            return root;
        }

        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            super.onCreateOptionsMenu(menu, inflater);
            inflater.inflate(R.menu.fragment_date_list, menu);
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
            case R.id.fragment_accesslog_all_delete:
                new DeleteDialogFragment.Builder().requestCode(REQUEST_CODE)
                        .title(getString(R.string.fragment_accesslog_delete_all_title))
                        .message(getString(R.string.fragment_accesslog_delete_all_message))
                        .positive(getString(R.string.fragment_accesslog_delete_all_positive))
                        .negative(getString(R.string.fragment_accesslog_delete_all_nagetive))
                        .show(getFragmentManager(), this);
                break;
            }
            return true;
        }

        @Override
        public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
            if (requestCode == REQUEST_CODE) {
                if (resultCode == DialogInterface.BUTTON_POSITIVE) {
                    deleteAll();
                }
            }
        }

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

            AccessLogProvider provider = getAccessLogProvider();
            if (provider != null) {
                provider.getDateList(this::updateDateList);
            }
        }

        @Override
        void onReturn() {
            mListAdapter.dismissSnackbar();
        }

        /**
         * ???.
         *
         * @param dateList ?
         */
        private void updateDateList(List<String> dateList) {
            runOnUiThread(() -> {
                if (dateList.isEmpty()) {
                    setNoDateView(View.VISIBLE);
                } else {
                    setNoDateView(View.GONE);
                    mListAdapter.updateDataList(dateList);
                }
            });
        }

        /**
         * ??????View????.
         *
         * @param visibility 
         */
        private void setNoDateView(int visibility) {
            View root = getView();
            if (root != null) {
                View v = root.findViewById(R.id.fragment_accesslog_no_data);
                if (v != null) {
                    v.setVisibility(visibility);
                }
            }
        }

        /**
         * AccessLogListFragment ???.
         *
         * @param date ?
         */
        private void gotoAccessLogListFragment(String date) {
            gotoFragment(AccessLogListFragment.create(date));
        }

        /**
         * ???.
         */
        void deleteAll() {
            AccessLogProvider provider = getAccessLogProvider();
            if (provider != null) {
                provider.removeAll((Boolean value) -> getActivity().finish());
            }
        }
    }

    /**
     * ???.
     */
    private static class DateListAdapter extends BaseAdapter<String, DateListAdapter.ViewHolder> {
        /**
         * .
         * @param inflater 
         */
        DateListAdapter(LayoutInflater inflater) {
            super(inflater);
        }

        @Override
        public DateListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(mInflater.inflate(R.layout.item_accesslog_date_list, parent, false));
        }

        @Override
        public void onBindViewHolder(DateListAdapter.ViewHolder holder, int position) {
            holder.mTextView.setText(mDataList.get(position));
        }

        /**
         * View ???.
         */
        class ViewHolder extends RecyclerView.ViewHolder {
            /**
             * ??View.
             */
            TextView mTextView;

            /**
             * .
             * @param itemView RecyclerView?View
             */
            ViewHolder(View itemView) {
                super(itemView);
                mTextView = itemView.findViewById(R.id.accesslog_date_name);
                itemView.setOnClickListener((v) -> {
                    if (mOnItemClickListener != null) {
                        v.postDelayed(() -> mOnItemClickListener.onItemClick(itemView, getAdapterPosition()), 300);
                    }
                });

                // ?????? Snackbar ???
                itemView.setOnTouchListener((v, event) -> {
                    switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        dismissSnackbar();
                        break;
                    }
                    return false;
                });
            }
        }
    }

    /**
     * ?????.
     */
    public static class AccessLogListFragment extends BaseFragment {
        /**
         * .
         */
        private static final int REQUEST_CODE = 101;

        /**
         * ???.
         */
        private static final String ARGS_DATE = "date";

        /**
         * ?????.
         */
        private AccessLogListAdapter mListAdapter;

        /**
         * .
         */
        private EditText mCondition;

        /**
         * AccessLogListFragment ????.
         *
         * @param date ??
         * @return AccessLogListFragment?
         */
        static AccessLogListFragment create(String date) {
            Bundle args = new Bundle();
            args.putString(ARGS_DATE, date);

            AccessLogListFragment fragment = new AccessLogListFragment();
            fragment.setArguments(args);
            return fragment;
        }

        @SuppressLint("ClickableViewAccessibility")
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View root = inflater.inflate(R.layout.fragment_accesslog_list, container, false);

            mListAdapter = new AccessLogListAdapter(inflater);
            mListAdapter.setOnItemClickListener(
                    (view, position) -> gotoAccessLogFragment(mListAdapter.getItem(position)));
            mListAdapter.setOnItemRemoveListener((data) -> {
                AccessLogProvider provider = getAccessLogProvider();
                if (provider != null) {
                    provider.remove(data);
                }
            });

            mCondition = root.findViewById(R.id.fragment_search_edit_text);

            RecyclerView recyclerView = root.findViewById(R.id.list_view_accesslog_list);
            recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            recyclerView.setAdapter(mListAdapter);
            recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
            recyclerView.setOnTouchListener((v, event) -> {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mListAdapter.dismissSnackbar();
                    break;
                }
                return false;
            });

            ItemTouchHelper helper = new ItemTouchHelper(new SwipeToDeleteCallback(getContext()) {
                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    mListAdapter.onItemRemove(viewHolder, recyclerView);
                }
            });
            helper.attachToRecyclerView(recyclerView);

            root.findViewById(R.id.fragment_search_btn).setOnClickListener((v) -> {
                searchAccessLogs(mCondition.getText().toString());
                InputMethodManager imm = (InputMethodManager) getContext()
                        .getSystemService(Context.INPUT_METHOD_SERVICE);
                if (imm != null) {
                    imm.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
                }
            });

            setHasOptionsMenu(true);

            return root;
        }

        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            super.onCreateOptionsMenu(menu, inflater);
            inflater.inflate(R.menu.fragment_date_list, menu);
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
            case R.id.fragment_accesslog_all_delete:
                new DeleteDialogFragment.Builder().requestCode(REQUEST_CODE)
                        .title(getString(R.string.fragment_accesslog_delete_accesslog_title))
                        .message(getString(R.string.fragment_accesslog_delete_accesslog_message, getDateString()))
                        .positive(getString(R.string.fragment_accesslog_delete_accesslog_positive))
                        .negative(getString(R.string.fragment_accesslog_delete_accesslog_negative))
                        .show(getFragmentManager(), this);
                break;
            }
            return true;
        }

        @Override
        public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
            if (requestCode == REQUEST_CODE) {
                if (resultCode == DialogInterface.BUTTON_POSITIVE) {
                    deleteAccessLogs();
                }
            }
        }

        @Override
        public void onResume() {
            super.onResume();
            searchAccessLogs(mCondition.getText().toString());
        }

        @Override
        void onReturn() {
            mListAdapter.dismissSnackbar();
        }

        /**
         * ???????.
         * <p>
         * ?????????? null ???
         * </p>
         * @return 
         */
        private String getDateString() {
            Bundle args = getArguments();
            if (args != null) {
                return args.getString(ARGS_DATE);
            }
            return null;
        }

        /**
         * ????????.
         */
        private void deleteAccessLogs() {
            String date = getDateString();
            AccessLogProvider provider = getAccessLogProvider();
            if (date != null && provider != null) {
                provider.remove(date, (value) -> popFragment());
            }
        }

        /**
         * ????.
         *
         * @param accessLogList ?
         */
        private void updateAccessLogList(List<AccessLog> accessLogList) {
            runOnUiThread(() -> {
                if (accessLogList.isEmpty()) {
                    setNoDateView(View.VISIBLE);
                } else {
                    setNoDateView(View.GONE);
                    mListAdapter.updateDataList(accessLogList);
                }
            });
        }

        /**
         * ????????.
         *
         * @param condition ?
         */
        private void searchAccessLogs(String condition) {
            mListAdapter.dismissSnackbar();
            String date = getDateString();
            AccessLogProvider provider = getAccessLogProvider();
            if (provider != null && date != null) {
                if (condition == null || condition.isEmpty()) {
                    provider.getAccessLogsOfDate(date, this::updateAccessLogList);
                } else {
                    provider.getAccessLogsFromCondition(date, condition, this::updateAccessLogList);
                }
            }
        }

        /**
         * ??????View????.
         *
         * @param visibility 
         */
        private void setNoDateView(int visibility) {
            View root = getView();
            if (root != null) {
                View v = root.findViewById(R.id.fragment_accesslog_no_data);
                if (v != null) {
                    v.setVisibility(visibility);
                }
            }
        }

        /**
         * ??????.
         *
         * @param accessLog 
         */
        private void gotoAccessLogFragment(AccessLog accessLog) {
            gotoFragment(AccessLogFragment.create(accessLog.getId()));
        }
    }

    /**
     * ???.
     */
    private static class AccessLogListAdapter extends BaseAdapter<AccessLog, AccessLogListAdapter.ViewHolder> {
        AccessLogListAdapter(LayoutInflater inflater) {
            super(inflater);
        }

        private static final Object[][] METHOD_COLORS = { { "get", R.drawable.access_log_method_get },
                { "put", R.drawable.access_log_method_put }, { "post", R.drawable.access_log_method_post },
                { "delete", R.drawable.access_log_method_delete }, };

        @Override
        public AccessLogListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(mInflater.inflate(R.layout.item_accesslog_list, parent, false));
        }

        @Override
        public void onBindViewHolder(AccessLogListAdapter.ViewHolder holder, int position) {
            AccessLog accessLog = mDataList.get(position);
            holder.mIpAddress.setText(getIpAddress(accessLog));
            holder.mPath.setText(getPath(accessLog));
            holder.mDate.setText(getDate(accessLog));

            String method = accessLog.getRequestMethod();
            for (Object[] v : METHOD_COLORS) {
                String m = (String) v[0];
                if (m.equalsIgnoreCase(method)) {
                    holder.mMethod.setBackgroundResource((int) v[1]);
                }
            }
            holder.mMethod.setText(method);
        }

        /**
         * View ???.
         */
        class ViewHolder extends RecyclerView.ViewHolder {
            /**
             * IP?TextView.
             */
            TextView mIpAddress;

            /**
             * ?TextView.
             */
            TextView mMethod;

            /**
             * ?TextView.
             */
            TextView mPath;

            /**
             * ?TextView.
             */
            TextView mDate;

            /**
             * .
             * @param itemView RecyclerView?View
             */
            ViewHolder(View itemView) {
                super(itemView);
                mIpAddress = itemView.findViewById(R.id.item_access_log_ip_address);
                mMethod = itemView.findViewById(R.id.item_access_log_method);
                mPath = itemView.findViewById(R.id.item_access_log_path);
                mDate = itemView.findViewById(R.id.item_access_log_date);
                itemView.setOnClickListener((v) -> {
                    if (mOnItemClickListener != null) {
                        v.postDelayed(() -> mOnItemClickListener.onItemClick(itemView, getAdapterPosition()), 300);
                    }
                });

                // ?????? Snackbar ???
                itemView.setOnTouchListener((v, event) -> {
                    switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        dismissSnackbar();
                        break;
                    }
                    return false;
                });
            }
        }

        /**
         * ? IP ????.
         *
         * @param accessLog 
         * @return IP
         */
        private String getIpAddress(AccessLog accessLog) {
            String ipAddress = accessLog.getRemoteIpAddress();
            if (ipAddress == null) {
                ipAddress = "none";
            }
            return ipAddress;
        }

        /**
         * ?HTTP?????.
         *
         * @param accessLog 
         * @return HTTP?
         */
        private String getPath(AccessLog accessLog) {
            String path = accessLog.getRequestPath();
            if (path == null) {
                path = "/";
            }
            return path;
        }

        /**
         * ??????.
         *
         * @param accessLog 
         * @return ?
         */
        private String getDate(AccessLog accessLog) {
            return AccessLogProvider.dateToString(accessLog.getRequestReceivedTime());
        }
    }

    /**
     * ??.
     */
    public static class AccessLogFragment extends BaseFragment {
        /**
         * ?ID??.
         */
        private static final String ARGS_ID = "id";

        /**
         * ??TextView.
         */
        private TextView mRequestView;

        /**
         * ???TextView.
         */
        private TextView mResponseView;

        /**
         * ????TextView.
         */
        private TextView mRequestTimeView;

        /**
         * ??IP?TextView.
         */
        private TextView mIpAddressView;

        /**
         * ?????TextView.
         */
        private TextView mHostNameView;

        /**
         * ?????TextView.
         */
        private TextView mSendTimeView;

        /**
         * AccessLogFragment ????.
         *
         * @param id ?ID
         * @return AccessLogFragment?
         */
        static AccessLogFragment create(long id) {
            Bundle args = new Bundle();
            args.putLong(ARGS_ID, id);

            AccessLogFragment fragment = new AccessLogFragment();
            fragment.setArguments(args);
            return fragment;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View root = inflater.inflate(R.layout.fragment_accesslog_detail, container, false);
            mRequestView = root.findViewById(R.id.fragment_accesslog_detail_request);
            mRequestTimeView = root.findViewById(R.id.fragment_accesslog_detail_request_time);
            mIpAddressView = root.findViewById(R.id.fragment_accesslog_detail_request_ip_address);
            mHostNameView = root.findViewById(R.id.fragment_accesslog_detail_request_host_name);
            mResponseView = root.findViewById(R.id.fragment_accesslog_detail_response);
            mSendTimeView = root.findViewById(R.id.fragment_accesslog_detail_response_time);
            return root;
        }

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

            long id = getAccessLogId();
            AccessLogProvider provider = getAccessLogProvider();
            if (provider != null && id != -1) {
                provider.getAccessLog(id, this::updateAccessLog);
            }
        }

        /**
         * ????.
         *
         * @param accessLog 
         */
        private void updateAccessLog(AccessLog accessLog) {
            if (accessLog == null) {
                return;
            }

            updateAccessLog(AccessLogProvider.dateToString(accessLog.getRequestReceivedTime()),
                    AccessLogProvider.dateToString(accessLog.getResponseSendTime()), accessLog.getRemoteIpAddress(),
                    accessLog.getRemoteHostName(), createRequestString(accessLog), createResponseString(accessLog));
        }

        /**
         * ??????.
         *
         * @param accessLog 
         * @return ?
         */
        private String createRequestString(AccessLog accessLog) {
            StringBuilder request = new StringBuilder();
            request.append(accessLog.getRequestMethod()).append(" ").append(accessLog.getRequestPath())
                    .append("\r\n");
            Map<String, String> headers = accessLog.getRequestHeader();
            if (headers != null) {
                for (String key : headers.keySet()) {
                    request.append(key).append(": ").append(headers.get(key)).append("\r\n");
                }
            }
            if (accessLog.getRequestBody() != null) {
                request.append("\r\n");
                request.append(accessLog.getRequestBody()).append("\r\n");
            }
            return request.toString();
        }

        /**
         * ???????.
         *
         * @param accessLog 
         * @return ?
         */
        private String createResponseString(AccessLog accessLog) {
            StringBuilder response = new StringBuilder();
            response.append("HTTP/1.1 ").append(accessLog.getResponseStatusCode()).append("\r\n");
            if (accessLog.getResponseContentType() != null) {
                response.append("Content-Type: ").append(accessLog.getResponseContentType()).append("\r\n");
            }
            response.append("\r\n");
            if (accessLog.getResponseBody() != null) {
                if (isContentType(accessLog.getResponseContentType())) {
                    response.append(reshapeJson(accessLog.getResponseBody())).append("\r\n");
                } else {
                    response.append(accessLog.getResponseBody()).append("\r\n");
                }
            }
            return response.toString();
        }

        /**
         * ? TextView ??????.
         *
         * @param receivedTime ?
         * @param sendTime ?
         * @param ipAddress IP
         * @param hostName ??
         * @param request 
         * @param response ?
         */
        private void updateAccessLog(String receivedTime, String sendTime, String ipAddress, String hostName,
                String request, String response) {
            runOnUiThread(() -> {
                mRequestTimeView.setText(receivedTime);
                mSendTimeView.setText(sendTime);
                mIpAddressView.setText(ipAddress);
                mHostNameView.setText(hostName);
                mRequestView.setText(request);
                mResponseView.setText(response);
            });
        }

        /**
         * ?JSON?????.
         *
         * @param contentType 
         * @return JSON????true????false
         */
        private boolean isContentType(String contentType) {
            return contentType != null && contentType.startsWith("application/json");
        }

        /**
         * JSON???????.
         *
         * @param body JSON?
         * @return ??JSON?
         */
        private String reshapeJson(String body) {
            try {
                JSONObject object = new JSONObject(body);
                return object.toString(2);
            } catch (JSONException e) {
                return body;
            }
        }

        /**
         * ????ID????.
         *
         * @return ?ID
         */
        private long getAccessLogId() {
            Bundle args = getArguments();
            if (args != null) {
                return args.getLong(ARGS_ID);
            }
            return -1;
        }
    }

    /**
     * ??.
     */
    public static class DeleteDialogFragment extends DialogFragment {
        /**
         * ?.
         */
        private final DialogInterface.OnClickListener mOnClickListener = (dialog, id) -> {
            dismiss();

            Fragment targetFragment = getTargetFragment();
            if (targetFragment != null) {
                targetFragment.onActivityResult(getTargetRequestCode(), id, null);
            }
        };

        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            String title = getArguments().getString("title");
            String message = getArguments().getString("message");
            String positive = getArguments().getString("positive");
            String negative = getArguments().getString("negative");

            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            if (title != null) {
                builder.setTitle(title);
            }
            if (message != null) {
                builder.setMessage(message);
            }
            builder.setPositiveButton(positive, mOnClickListener);
            if (negative != null) {
                builder.setNegativeButton(negative, mOnClickListener);
            }
            return builder.create();
        }

        /**
         * ?.
         */
        public static class Builder {
            /**
             * ?.
             */
            private String mTitle;

            /**
             * ?.
             */
            private String mMessage;

            /**
             * ? Positive ?.
             */
            private String mPositive;

            /**
             * ? Negative ?.
             */
            private String mNegative;

            /**
             * .
             */
            private int mRequestCode;

            /**
             * ???.
             *
             * @param requestCode 
             * @return Builder
             */
            Builder requestCode(int requestCode) {
                mRequestCode = requestCode;
                return this;
            }

            /**
             * ???.
             *
             * @param title 
             * @return Builder
             */
            Builder title(String title) {
                mTitle = title;
                return this;
            }

            /**
             * ???.
             *
             * @param message 
             * @return Builder
             */
            Builder message(String message) {
                mMessage = message;
                return this;
            }

            /**
             * Positive ????.
             *
             * @param positive Positive ?
             * @return Builder
             */
            Builder positive(String positive) {
                mPositive = positive;
                return this;
            }

            /**
             * Negative ????.
             *
             * @param negative negative ?
             * @return Builder
             */
            Builder negative(String negative) {
                mNegative = negative;
                return this;
            }

            /**
             * ???.
             * <p>
             * targetFragment ??? Fragment ? {@link Fragment#onActivityResult(int, int, Intent)}
             * ??????
             * </p>
             * @param fragmentManager ?FragmentManager
             * @param targetFragment ??Fragment
             */
            void show(FragmentManager fragmentManager, Fragment targetFragment) {
                Bundle bundle = new Bundle();
                if (mTitle != null) {
                    bundle.putString("title", mTitle);
                }
                if (mMessage != null) {
                    bundle.putString("message", mMessage);
                }
                if (mPositive != null) {
                    bundle.putString("positive", mPositive);
                }
                if (mNegative != null) {
                    bundle.putString("negative", mNegative);
                }
                DialogFragment dialog = new DeleteDialogFragment();
                dialog.setArguments(bundle);
                dialog.setTargetFragment(targetFragment, mRequestCode);
                dialog.show(fragmentManager, "test");
            }
        }
    }

    /**
     * RecyclerView ?.
     */
    private static abstract class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
        /**
         * ?.
         */
        private Drawable mDeleteIcon;

        /**
         * .
         */
        private ColorDrawable mBackground;

        /**
         * .
         * @param context 
         */
        SwipeToDeleteCallback(Context context) {
            super(0, (ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT));
            mDeleteIcon = ContextCompat.getDrawable(context, android.R.drawable.ic_menu_delete);
            mBackground = new ColorDrawable(Color.rgb(235, 55, 35));
        }

        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                RecyclerView.ViewHolder target) {
            return false;
        }

        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dx,
                float dy, int actionState, boolean isCurrentlyActive) {
            View itemView = viewHolder.itemView;
            int backgroundCornerOffset = 20;
            int iconMargin = (itemView.getHeight() - mDeleteIcon.getIntrinsicHeight()) / 2;
            int iconTop = itemView.getTop() + (itemView.getHeight() - mDeleteIcon.getIntrinsicHeight()) / 2;
            int iconBottom = iconTop + mDeleteIcon.getIntrinsicHeight();

            if (dx > 0) { // Swiping to the right
                int iconLeft = itemView.getLeft() + iconMargin + mDeleteIcon.getIntrinsicWidth();
                int iconRight = itemView.getLeft() + iconMargin;
                mDeleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
                mBackground.setBounds(itemView.getLeft(), itemView.getTop(),
                        itemView.getLeft() + ((int) dx) + backgroundCornerOffset, itemView.getBottom());
            } else if (dx < 0) { // Swiping to the left
                int iconLeft = itemView.getRight() - iconMargin - mDeleteIcon.getIntrinsicWidth();
                int iconRight = itemView.getRight() - iconMargin;
                mDeleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
                mBackground.setBounds(itemView.getRight() + ((int) dx) - backgroundCornerOffset, itemView.getTop(),
                        itemView.getRight(), itemView.getBottom());
            } else { // view is unSwiped
                mBackground.setBounds(0, 0, 0, 0);
            }

            mBackground.draw(c);
            mDeleteIcon.draw(c);

            super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive);
        }
    }
}