1 /* 2 * Copyright (C) 2007 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 android.webkit; 18 19 import android.content.Context; 20 import android.net.http.EventHandler; 21 import android.net.http.Headers; 22 import android.os.Handler; 23 import android.os.Message; 24 25 import java.io.IOException; 26 import java.io.InputStream; 27 28 /** 29 * This abstract class is used for all content loaders that rely on streaming 30 * content into the rendering engine loading framework. 31 * 32 * The class implements a state machine to load the content into the frame in 33 * a similar manor to the way content arrives from the network. The class uses 34 * messages to move from one state to the next, which enables async. loading of 35 * the streamed content. 36 * 37 * Classes that inherit from this class must implement two methods, the first 38 * method is used to setup the InputStream and notify the loading framework if 39 * it can load it's content. The other method allows the derived class to add 40 * additional HTTP headers to the response. 41 * 42 * By default, content loaded with a StreamLoader is marked with a HTTP header 43 * that indicates the content should not be cached. 44 * 45 */ 46 abstract class StreamLoader implements Handler.Callback { 47 48 private static final int MSG_STATUS = 100; // Send status to loader 49 private static final int MSG_HEADERS = 101; // Send headers to loader 50 private static final int MSG_DATA = 102; // Send data to loader 51 private static final int MSG_END = 103; // Send endData to loader 52 53 protected final Context mContext; 54 protected final LoadListener mLoadListener; // loader class 55 protected InputStream mDataStream; // stream to read data from 56 protected long mContentLength; // content length of data 57 private byte [] mData; // buffer to pass data to loader with. 58 59 // Handler which will be initialized in the thread where load() is called. 60 private Handler mHandler; 61 62 /** 63 * Constructor. Although this class calls the LoadListener, it only calls 64 * the EventHandler Interface methods. LoadListener concrete class is used 65 * to avoid the penality of calling an interface. 66 * 67 * @param loadlistener The LoadListener to call with the data. 68 */ StreamLoader(LoadListener loadlistener)69 StreamLoader(LoadListener loadlistener) { 70 mLoadListener = loadlistener; 71 mContext = loadlistener.getContext(); 72 } 73 74 /** 75 * This method is called when the derived class should setup mDataStream, 76 * and call mLoadListener.status() to indicate that the load can occur. If it 77 * fails to setup, it should still call status() with the error code. 78 * 79 * @return true if stream was successfully setup 80 */ setupStreamAndSendStatus()81 protected abstract boolean setupStreamAndSendStatus(); 82 83 /** 84 * This method is called when the headers are about to be sent to the 85 * load framework. The derived class has the opportunity to add addition 86 * headers. 87 * 88 * @param headers Map of HTTP headers that will be sent to the loader. 89 */ buildHeaders(Headers headers)90 abstract protected void buildHeaders(Headers headers); 91 92 /** 93 * Calling this method starts the load of the content for this StreamLoader. 94 * This method simply creates a Handler in the current thread and posts a 95 * message to send the status and returns immediately. 96 */ load()97 final void load() { 98 synchronized (this) { 99 if (mHandler == null) { 100 mHandler = new Handler(this); 101 } 102 } 103 104 if (!mLoadListener.isSynchronous()) { 105 mHandler.sendEmptyMessage(MSG_STATUS); 106 } else { 107 // Load the stream synchronously. 108 if (setupStreamAndSendStatus()) { 109 // We were able to open the stream, create the array 110 // to pass data to the loader 111 mData = new byte[8192]; 112 sendHeaders(); 113 while (!sendData() && !mLoadListener.cancelled()); 114 closeStreamAndSendEndData(); 115 mLoadListener.loadSynchronousMessages(); 116 } 117 } 118 } 119 handleMessage(Message msg)120 public boolean handleMessage(Message msg) { 121 if (mLoadListener.isSynchronous()) { 122 throw new AssertionError(); 123 } 124 if (mLoadListener.cancelled()) { 125 closeStreamAndSendEndData(); 126 return true; 127 } 128 switch(msg.what) { 129 case MSG_STATUS: 130 if (setupStreamAndSendStatus()) { 131 // We were able to open the stream, create the array 132 // to pass data to the loader 133 mData = new byte[8192]; 134 mHandler.sendEmptyMessage(MSG_HEADERS); 135 } 136 break; 137 case MSG_HEADERS: 138 sendHeaders(); 139 mHandler.sendEmptyMessage(MSG_DATA); 140 break; 141 case MSG_DATA: 142 if (sendData()) { 143 mHandler.sendEmptyMessage(MSG_END); 144 } else { 145 mHandler.sendEmptyMessage(MSG_DATA); 146 } 147 break; 148 case MSG_END: 149 closeStreamAndSendEndData(); 150 break; 151 default: 152 return false; 153 } 154 return true; 155 } 156 157 /** 158 * Construct the headers and pass them to the EventHandler. 159 */ sendHeaders()160 private void sendHeaders() { 161 Headers headers = new Headers(); 162 if (mContentLength > 0) { 163 headers.setContentLength(mContentLength); 164 } 165 buildHeaders(headers); 166 mLoadListener.headers(headers); 167 } 168 169 /** 170 * Read data from the stream and pass it to the EventHandler. 171 * If an error occurs reading the stream, then an error is sent to the 172 * EventHandler, and moves onto the next state - end of data. 173 * @return True if all the data has been read. False if sendData should be 174 * called again. 175 */ sendData()176 private boolean sendData() { 177 if (mDataStream != null) { 178 try { 179 int amount = mDataStream.read(mData); 180 if (amount > 0) { 181 mLoadListener.data(mData, amount); 182 return false; 183 } 184 } catch (IOException ex) { 185 mLoadListener.error(EventHandler.FILE_ERROR, ex.getMessage()); 186 } 187 } 188 return true; 189 } 190 191 /** 192 * Close the stream and inform the EventHandler that load is complete. 193 */ closeStreamAndSendEndData()194 private void closeStreamAndSendEndData() { 195 if (mDataStream != null) { 196 try { 197 mDataStream.close(); 198 } catch (IOException ex) { 199 // ignore. 200 } 201 } 202 mLoadListener.endData(); 203 } 204 } 205