1 2 /* 3 * Copyright (C) 2011 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package com.android.browser.homepages; 18 19 import android.content.Context; 20 import android.content.UriMatcher; 21 import android.content.res.Resources; 22 import android.database.Cursor; 23 import android.database.MergeCursor; 24 import android.net.Uri; 25 import android.provider.BrowserContract.Bookmarks; 26 import android.provider.BrowserContract.History; 27 import android.text.TextUtils; 28 import android.util.Base64; 29 import android.util.Log; 30 31 import com.android.browser.R; 32 import com.android.browser.homepages.Template.ListEntityIterator; 33 34 import java.io.File; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.text.DateFormat; 39 import java.text.DecimalFormat; 40 import java.util.Arrays; 41 import java.util.Comparator; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 public class RequestHandler extends Thread { 46 47 private static final String TAG = "RequestHandler"; 48 private static final int INDEX = 1; 49 private static final int RESOURCE = 2; 50 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 51 52 Uri mUri; 53 Context mContext; 54 OutputStream mOutput; 55 56 static { 57 sUriMatcher.addURI(HomeProvider.AUTHORITY, "/", INDEX); 58 sUriMatcher.addURI(HomeProvider.AUTHORITY, "res/*/*", RESOURCE); 59 } 60 61 public RequestHandler(Context context, Uri uri, OutputStream out) { 62 mUri = uri; 63 mContext = context.getApplicationContext(); 64 mOutput = out; 65 } 66 67 @Override 68 public void run() { 69 super.run(); 70 try { 71 doHandleRequest(); 72 } catch (Exception e) { 73 Log.e(TAG, "Failed to handle request: " + mUri, e); 74 } finally { 75 cleanup(); 76 } 77 } 78 79 void doHandleRequest() throws IOException { 80 if ("file".equals(mUri.getScheme())) { 81 writeFolderIndex(); 82 return; 83 } 84 int match = sUriMatcher.match(mUri); 85 switch (match) { 86 case INDEX: 87 writeTemplatedIndex(); 88 break; 89 case RESOURCE: 90 writeResource(getUriResourcePath()); 91 break; 92 } 93 } 94 95 byte[] htmlEncode(String s) { 96 return TextUtils.htmlEncode(s).getBytes(); 97 } 98 99 // We can reuse this for both History and Bookmarks queries because the 100 // columns defined actually belong to the CommonColumn and ImageColumn 101 // interfaces that both History and Bookmarks implement 102 private static final String[] PROJECTION = new String[] { 103 History.URL, 104 History.TITLE, 105 History.THUMBNAIL 106 }; 107 private static final String SELECTION = History.URL 108 + " NOT LIKE 'content:%' AND " + History.THUMBNAIL + " IS NOT NULL"; 109 void writeTemplatedIndex() throws IOException { 110 Template t = Template.getCachedTemplate(mContext, R.raw.most_visited); 111 Cursor historyResults = mContext.getContentResolver().query( 112 History.CONTENT_URI, PROJECTION, SELECTION, 113 null, History.VISITS + " DESC LIMIT 12"); 114 Cursor cursor = historyResults; 115 try { 116 if (cursor.getCount() < 12) { 117 Cursor bookmarkResults = mContext.getContentResolver().query( 118 Bookmarks.CONTENT_URI, PROJECTION, SELECTION, 119 null, Bookmarks.DATE_CREATED + " DESC LIMIT 12"); 120 cursor = new MergeCursor(new Cursor[] { historyResults, bookmarkResults }) { 121 @Override 122 public int getCount() { 123 return Math.min(12, super.getCount()); 124 } 125 }; 126 } 127 t.assignLoop("most_visited", new Template.CursorListEntityWrapper(cursor) { 128 @Override 129 public void writeValue(OutputStream stream, String key) throws IOException { 130 Cursor cursor = getCursor(); 131 if (key.equals("url")) { 132 stream.write(htmlEncode(cursor.getString(0))); 133 } else if (key.equals("title")) { 134 stream.write(htmlEncode(cursor.getString(1))); 135 } else if (key.equals("thumbnail")) { 136 stream.write("data:image/png;base64,".getBytes()); 137 byte[] thumb = cursor.getBlob(2); 138 stream.write(Base64.encode(thumb, Base64.DEFAULT)); 139 } 140 } 141 }); 142 t.write(mOutput); 143 } finally { 144 cursor.close(); 145 } 146 } 147 148 private static final Comparator<File> sFileComparator = new Comparator<File>() { 149 @Override 150 public int compare(File lhs, File rhs) { 151 if (lhs.isDirectory() != rhs.isDirectory()) { 152 return lhs.isDirectory() ? -1 : 1; 153 } 154 return lhs.getName().compareTo(rhs.getName()); 155 } 156 }; 157 158 void writeFolderIndex() throws IOException { 159 File f = new File(mUri.getPath()); 160 final File[] files = f.listFiles(); 161 Arrays.sort(files, sFileComparator); 162 Template t = Template.getCachedTemplate(mContext, R.raw.folder_view); 163 t.assign("path", mUri.getPath()); 164 t.assign("parent_url", f.getParent() != null ? f.getParent() : f.getPath()); 165 t.assignLoop("files", new ListEntityIterator() { 166 int index = -1; 167 168 @Override 169 public void writeValue(OutputStream stream, String key) throws IOException { 170 File f = files[index]; 171 if ("name".equals(key)) { 172 stream.write(f.getName().getBytes()); 173 } 174 if ("url".equals(key)) { 175 stream.write(("file://" + f.getAbsolutePath()).getBytes()); 176 } 177 if ("type".equals(key)) { 178 stream.write((f.isDirectory() ? "dir" : "file").getBytes()); 179 } 180 if ("size".equals(key)) { 181 if (f.isFile()) { 182 stream.write(readableFileSize(f.length()).getBytes()); 183 } 184 } 185 if ("last_modified".equals(key)) { 186 String date = DateFormat.getDateTimeInstance( 187 DateFormat.SHORT, DateFormat.SHORT) 188 .format(f.lastModified()); 189 stream.write(date.getBytes()); 190 } 191 if ("alt".equals(key)) { 192 if (index % 2 == 0) { 193 stream.write("alt".getBytes()); 194 } 195 } 196 } 197 198 @Override 199 public ListEntityIterator getListIterator(String key) { 200 return null; 201 } 202 203 @Override 204 public void reset() { 205 index = -1; 206 } 207 208 @Override 209 public boolean moveToNext() { 210 return (++index) < files.length; 211 } 212 }); 213 t.write(mOutput); 214 } 215 216 static String readableFileSize(long size) { 217 if(size <= 0) return "0"; 218 final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; 219 int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); 220 return new DecimalFormat("#,##0.#").format( 221 size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 222 } 223 224 String getUriResourcePath() { 225 final Pattern pattern = Pattern.compile("/?res/([\\w/]+)"); 226 Matcher m = pattern.matcher(mUri.getPath()); 227 if (m.matches()) { 228 return m.group(1); 229 } else { 230 return mUri.getPath(); 231 } 232 } 233 234 void writeResource(String fileName) throws IOException { 235 Resources res = mContext.getResources(); 236 String packageName = R.class.getPackage().getName(); 237 int id = res.getIdentifier(fileName, null, packageName); 238 if (id != 0) { 239 InputStream in = res.openRawResource(id); 240 byte[] buf = new byte[4096]; 241 int read; 242 while ((read = in.read(buf)) > 0) { 243 mOutput.write(buf, 0, read); 244 } 245 } 246 } 247 248 void writeString(String str) throws IOException { 249 mOutput.write(str.getBytes()); 250 } 251 252 void writeString(String str, int offset, int count) throws IOException { 253 mOutput.write(str.getBytes(), offset, count); 254 } 255 256 void cleanup() { 257 try { 258 mOutput.close(); 259 } catch (Exception e) { 260 Log.e(TAG, "Failed to close pipe!", e); 261 } 262 } 263 264 } 265