1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.browser;
18 
19 import android.content.Context;
20 import android.database.DataSetObserver;
21 import android.graphics.Color;
22 import android.util.AttributeSet;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.View.OnClickListener;
27 import android.webkit.ConsoleMessage;
28 import android.webkit.WebView;
29 import android.widget.Button;
30 import android.widget.EditText;
31 import android.widget.LinearLayout;
32 import android.widget.ListView;
33 import android.widget.TextView;
34 import android.widget.TwoLineListItem;
35 
36 import java.util.Vector;
37 
38 /* package */ class ErrorConsoleView extends LinearLayout {
39 
40     /**
41      * Define some constants to describe the visibility of the error console.
42      */
43     public static final int SHOW_MINIMIZED = 0;
44     public static final int SHOW_MAXIMIZED = 1;
45     public static final int SHOW_NONE      = 2;
46 
47     private TextView mConsoleHeader;
48     private ErrorConsoleListView mErrorList;
49     private LinearLayout mEvalJsViewGroup;
50     private EditText mEvalEditText;
51     private Button mEvalButton;
52     private WebView mWebView;
53     private int mCurrentShowState = SHOW_NONE;
54 
55     private boolean mSetupComplete = false;
56 
57     // Before we've been asked to display the console, cache any messages that should
58     // be added to the console. Then when we do display the console, add them to the view
59     // then.
60     private Vector<ConsoleMessage> mErrorMessageCache;
61 
ErrorConsoleView(Context context)62     public ErrorConsoleView(Context context) {
63         super(context);
64     }
65 
ErrorConsoleView(Context context, AttributeSet attributes)66     public ErrorConsoleView(Context context, AttributeSet attributes) {
67         super(context, attributes);
68     }
69 
commonSetupIfNeeded()70     private void commonSetupIfNeeded() {
71         if (mSetupComplete) {
72             return;
73         }
74 
75         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
76                 Context.LAYOUT_INFLATER_SERVICE);
77         inflater.inflate(R.layout.error_console, this);
78 
79         // Get references to each ui element.
80         mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
81         mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
82         mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
83         mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
84         mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);
85 
86         mEvalButton.setOnClickListener(new OnClickListener() {
87             public void onClick(View v) {
88                 // Send the javascript to be evaluated to webkit as a javascript: url
89                 // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
90                 // way? Note that this is called on the UI thread so we will need to post a message
91                 // to the WebCore thread to implement this.
92                 if (mWebView != null) {
93                     mWebView.loadUrl("javascript:" + mEvalEditText.getText());
94                 }
95 
96                 mEvalEditText.setText("");
97             }
98         });
99 
100         // Make clicking on the console title bar min/maximse it.
101         mConsoleHeader.setOnClickListener(new OnClickListener() {
102             public void onClick(View v) {
103                 if (mCurrentShowState == SHOW_MINIMIZED) {
104                     showConsole(SHOW_MAXIMIZED);
105                 } else {
106                     showConsole(SHOW_MINIMIZED);
107                 }
108             }
109         });
110 
111         // Add any cached messages to the list now that we've assembled the view.
112         if (mErrorMessageCache != null) {
113             for (ConsoleMessage msg : mErrorMessageCache) {
114                 mErrorList.addErrorMessage(msg);
115             }
116             mErrorMessageCache.clear();
117         }
118 
119         mSetupComplete = true;
120     }
121 
122     /**
123      * Adds a message to the set of messages the console uses.
124      */
addErrorMessage(ConsoleMessage consoleMessage)125     public void addErrorMessage(ConsoleMessage consoleMessage) {
126         if (mSetupComplete) {
127             mErrorList.addErrorMessage(consoleMessage);
128         } else {
129             if (mErrorMessageCache == null) {
130                 mErrorMessageCache = new Vector<ConsoleMessage>();
131             }
132             mErrorMessageCache.add(consoleMessage);
133         }
134     }
135 
136     /**
137      * Removes all error messages from the console.
138      */
clearErrorMessages()139     public void clearErrorMessages() {
140         if (mSetupComplete) {
141             mErrorList.clearErrorMessages();
142         } else if (mErrorMessageCache != null) {
143             mErrorMessageCache.clear();
144         }
145     }
146 
147     /**
148      * Returns the current number of errors displayed in the console.
149      */
numberOfErrors()150     public int numberOfErrors() {
151         if (mSetupComplete) {
152             return mErrorList.getCount();
153         } else {
154             return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
155         }
156     }
157 
158     /**
159      * Sets the webview that this console is associated with. Currently this is used so
160      * we can call into webkit to evaluate JS expressions in the console.
161      */
setWebView(WebView webview)162     public void setWebView(WebView webview) {
163         mWebView = webview;
164     }
165 
166     /**
167      * Sets the visibility state of the console.
168      */
showConsole(int show_state)169     public void showConsole(int show_state) {
170         commonSetupIfNeeded();
171         switch (show_state) {
172             case SHOW_MINIMIZED:
173                 mConsoleHeader.setVisibility(View.VISIBLE);
174                 mConsoleHeader.setText(R.string.error_console_header_text_minimized);
175                 mErrorList.setVisibility(View.GONE);
176                 mEvalJsViewGroup.setVisibility(View.GONE);
177                 break;
178 
179             case SHOW_MAXIMIZED:
180                 mConsoleHeader.setVisibility(View.VISIBLE);
181                 mConsoleHeader.setText(R.string.error_console_header_text_maximized);
182                 mErrorList.setVisibility(View.VISIBLE);
183                 mEvalJsViewGroup.setVisibility(View.VISIBLE);
184                 break;
185 
186             case SHOW_NONE:
187                 mConsoleHeader.setVisibility(View.GONE);
188                 mErrorList.setVisibility(View.GONE);
189                 mEvalJsViewGroup.setVisibility(View.GONE);
190                 break;
191         }
192         mCurrentShowState = show_state;
193     }
194 
195     /**
196      * Returns the current visibility state of the console.
197      */
getShowState()198     public int getShowState() {
199         if (mSetupComplete) {
200             return mCurrentShowState;
201         } else {
202             return SHOW_NONE;
203         }
204     }
205 
206     /**
207      * This class extends ListView to implement the View that will actually display the set of
208      * errors encountered on the current page.
209      */
210     private static class ErrorConsoleListView extends ListView {
211         // An adapter for this View that contains a list of error messages.
212         private ErrorConsoleMessageList mConsoleMessages;
213 
ErrorConsoleListView(Context context, AttributeSet attributes)214         public ErrorConsoleListView(Context context, AttributeSet attributes) {
215             super(context, attributes);
216             mConsoleMessages = new ErrorConsoleMessageList(context);
217             setAdapter(mConsoleMessages);
218         }
219 
addErrorMessage(ConsoleMessage consoleMessage)220         public void addErrorMessage(ConsoleMessage consoleMessage) {
221             mConsoleMessages.add(consoleMessage);
222             setSelection(mConsoleMessages.getCount());
223         }
224 
clearErrorMessages()225         public void clearErrorMessages() {
226             mConsoleMessages.clear();
227         }
228 
229         /**
230          * This class is an adapter for ErrorConsoleListView that contains the error console
231          * message data.
232          */
233         private static class ErrorConsoleMessageList extends android.widget.BaseAdapter
234                 implements android.widget.ListAdapter {
235 
236             private Vector<ConsoleMessage> mMessages;
237             private LayoutInflater mInflater;
238 
ErrorConsoleMessageList(Context context)239             public ErrorConsoleMessageList(Context context) {
240                 mMessages = new Vector<ConsoleMessage>();
241                 mInflater = (LayoutInflater)context.getSystemService(
242                         Context.LAYOUT_INFLATER_SERVICE);
243             }
244 
245             /**
246              * Add a new message to the list and update the View.
247              */
add(ConsoleMessage consoleMessage)248             public void add(ConsoleMessage consoleMessage) {
249                 mMessages.add(consoleMessage);
250                 notifyDataSetChanged();
251             }
252 
253             /**
254              * Remove all messages from the list and update the view.
255              */
clear()256             public void clear() {
257                 mMessages.clear();
258                 notifyDataSetChanged();
259             }
260 
261             @Override
areAllItemsEnabled()262             public boolean areAllItemsEnabled() {
263                 return false;
264             }
265 
266             @Override
isEnabled(int position)267             public boolean isEnabled(int position) {
268                 return false;
269             }
270 
getItemId(int position)271             public long getItemId(int position) {
272                 return position;
273             }
274 
getItem(int position)275             public Object getItem(int position) {
276                 return mMessages.get(position);
277             }
278 
getCount()279             public int getCount() {
280                 return mMessages.size();
281             }
282 
283             @Override
hasStableIds()284             public boolean hasStableIds() {
285                 return true;
286             }
287 
288             /**
289              * Constructs a TwoLineListItem for the error at position.
290              */
getView(int position, View convertView, ViewGroup parent)291             public View getView(int position, View convertView, ViewGroup parent) {
292                 View view;
293                 ConsoleMessage error = mMessages.get(position);
294 
295                 if (error == null) {
296                     return null;
297                 }
298 
299                 if (convertView == null) {
300                     view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
301                 } else {
302                     view = convertView;
303                 }
304 
305                 TextView headline = (TextView) view.findViewById(android.R.id.text1);
306                 TextView subText = (TextView) view.findViewById(android.R.id.text2);
307                 headline.setText(error.sourceId() + ":" + error.lineNumber());
308                 subText.setText(error.message());
309                 switch (error.messageLevel()) {
310                     case ERROR:
311                         subText.setTextColor(Color.RED);
312                         break;
313                     case WARNING:
314                         // Orange
315                         subText.setTextColor(Color.rgb(255,192,0));
316                         break;
317                     case TIP:
318                         subText.setTextColor(Color.BLUE);
319                         break;
320                     default:
321                         subText.setTextColor(Color.LTGRAY);
322                         break;
323                 }
324                 return view;
325             }
326 
327         }
328     }
329 }
330