/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.devsupport;
import android.app.Dialog;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.facebook.common.logging.FLog;
import com.facebook.react.R;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import org.json.JSONObject;
/**
* Dialog for displaying JS errors in an eye-catching form (red box).
*/
/* package */ class RedBoxDialog extends Dialog implements AdapterView.OnItemClickListener {
private final DevSupportManager mDevSupportManager;
private ListView mStackView;
private Button mReloadJs;
private int mCookie = 0;
private static class StackAdapter extends BaseAdapter {
private static final int VIEW_TYPE_COUNT = 2;
private static final int VIEW_TYPE_TITLE = 0;
private static final int VIEW_TYPE_STACKFRAME = 1;
private final String mTitle;
private final StackFrame[] mStack;
private static class FrameViewHolder {
private final TextView mMethodView;
private final TextView mFileView;
private FrameViewHolder(View v) {
mMethodView = (TextView) v.findViewById(R.id.rn_frame_method);
mFileView = (TextView) v.findViewById(R.id.rn_frame_file);
}
}
public StackAdapter(String title, StackFrame[] stack) {
mTitle = title;
mStack = stack;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return position > 0;
}
@Override
public int getCount() {
return mStack.length + 1;
}
@Override
public Object getItem(int position) {
return position == 0 ? mTitle : mStack[position - 1];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
@Override
public int getItemViewType(int position) {
return position == 0 ? VIEW_TYPE_TITLE : VIEW_TYPE_STACKFRAME;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
TextView title = convertView != null
? (TextView) convertView
: (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.redbox_item_title, parent, false);
title.setText(mTitle);
return title;
} else {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.redbox_item_frame, parent, false);
convertView.setTag(new FrameViewHolder(convertView));
}
StackFrame frame = mStack[position - 1];
FrameViewHolder holder = (FrameViewHolder) convertView.getTag();
holder.mMethodView.setText(frame.getMethod());
holder.mFileView.setText(frame.getFileName() + ":" + frame.getLine());
return convertView;
}
}
}
private static class OpenStackFrameTask extends AsyncTask<StackFrame, Void, Void> {
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final DevSupportManager mDevSupportManager;
private OpenStackFrameTask(DevSupportManager devSupportManager) {
mDevSupportManager = devSupportManager;
}
@Override
protected Void doInBackground(StackFrame... stackFrames) {
try {
String openStackFrameUrl =
Uri.parse(mDevSupportManager.getSourceUrl()).buildUpon()
.path("/open-stack-frame")
.query(null)
.build()
.toString();
OkHttpClient client = new OkHttpClient();
for (StackFrame frame: stackFrames) {
String payload = stackFrameToJson(frame).toString();
RequestBody body = RequestBody.create(JSON, payload);
Request request = new Request.Builder().url(openStackFrameUrl).post(body).build();
client.newCall(request).execute();
}
} catch (Exception e) {
FLog.e(ReactConstants.TAG, "Could not open stack frame", e);
}
return null;
}
private static JSONObject stackFrameToJson(StackFrame frame) {
return new JSONObject(
MapBuilder.of(
"file", frame.getFile(),
"methodName", frame.getMethod(),
"lineNumber", frame.getLine(),
"column", frame.getColumn()
));
}
}
protected RedBoxDialog(Context context, DevSupportManager devSupportManager) {
super(context, R.style.Theme_Catalyst_RedBox);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.redbox_view);
mDevSupportManager = devSupportManager;
mStackView = (ListView) findViewById(R.id.rn_redbox_stack);
mStackView.setOnItemClickListener(this);
mReloadJs = (Button) findViewById(R.id.rn_redbox_reloadjs);
mReloadJs.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDevSupportManager.handleReloadJS();
}
});
}
public void setExceptionDetails(String title, StackFrame[] stack) {
mStackView.setAdapter(new StackAdapter(title, stack));
}
public void setErrorCookie(int cookie) {
mCookie = cookie;
}
public int getErrorCookie() {
return mCookie;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
new OpenStackFrameTask(mDevSupportManager).executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR,
(StackFrame) mStackView.getAdapter().getItem(position));
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
mDevSupportManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
}