/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.browser.homepages; import android.content.Context; import android.content.UriMatcher; import android.content.res.Resources; import android.database.Cursor; import android.database.MergeCursor; import android.net.Uri; import com.android.browser.provider.BrowserContract.Bookmarks; import com.android.browser.provider.BrowserContract.History; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.android.browser.R; import com.android.browser.homepages.Template.ListEntityIterator; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RequestHandler extends Thread { private static final String TAG = "RequestHandler"; private static final int INDEX = 1; private static final int RESOURCE = 2; private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); Uri mUri; Context mContext; OutputStream mOutput; static { sUriMatcher.addURI(HomeProvider.AUTHORITY, "/", INDEX); sUriMatcher.addURI(HomeProvider.AUTHORITY, "res/*/*", RESOURCE); } public RequestHandler(Context context, Uri uri, OutputStream out) { mUri = uri; mContext = context.getApplicationContext(); mOutput = out; } @Override public void run() { super.run(); try { doHandleRequest(); } catch (Exception e) { Log.e(TAG, "Failed to handle request: " + mUri, e); } finally { cleanup(); } } void doHandleRequest() throws IOException { if ("file".equals(mUri.getScheme())) { writeFolderIndex(); return; } int match = sUriMatcher.match(mUri); switch (match) { case INDEX: writeTemplatedIndex(); break; case RESOURCE: writeResource(getUriResourcePath()); break; } } byte[] htmlEncode(String s) { return TextUtils.htmlEncode(s).getBytes(); } // We can reuse this for both History and Bookmarks queries because the // columns defined actually belong to the CommonColumn and ImageColumn // interfaces that both History and Bookmarks implement private static final String[] PROJECTION = new String[] { History.URL, History.TITLE, History.THUMBNAIL }; private static final String SELECTION = History.URL + " NOT LIKE 'content:%' AND " + History.THUMBNAIL + " IS NOT NULL"; void writeTemplatedIndex() throws IOException { Template t = Template.getCachedTemplate(mContext, R.raw.most_visited); Cursor historyResults = mContext.getContentResolver().query( History.CONTENT_URI, PROJECTION, SELECTION, null, History.VISITS + " DESC LIMIT 12"); Cursor cursor = historyResults; try { if (cursor.getCount() < 12) { Cursor bookmarkResults = mContext.getContentResolver().query( Bookmarks.CONTENT_URI, PROJECTION, SELECTION, null, Bookmarks.DATE_CREATED + " DESC LIMIT 12"); cursor = new MergeCursor(new Cursor[] { historyResults, bookmarkResults }) { @Override public int getCount() { return Math.min(12, super.getCount()); } }; } t.assignLoop("most_visited", new Template.CursorListEntityWrapper(cursor) { @Override public void writeValue(OutputStream stream, String key) throws IOException { Cursor cursor = getCursor(); if (key.equals("url")) { stream.write(htmlEncode(cursor.getString(0))); } else if (key.equals("title")) { stream.write(htmlEncode(cursor.getString(1))); } else if (key.equals("thumbnail")) { stream.write("data:image/png;base64,".getBytes()); byte[] thumb = cursor.getBlob(2); stream.write(Base64.encode(thumb, Base64.DEFAULT)); } } }); t.write(mOutput); } finally { cursor.close(); } } private static final Comparator<File> sFileComparator = new Comparator<File>() { @Override public int compare(File lhs, File rhs) { if (lhs.isDirectory() != rhs.isDirectory()) { return lhs.isDirectory() ? -1 : 1; } return lhs.getName().compareTo(rhs.getName()); } }; void writeFolderIndex() throws IOException { File f = new File(mUri.getPath()); final File[] files = f.listFiles(); Arrays.sort(files, sFileComparator); Template t = Template.getCachedTemplate(mContext, R.raw.folder_view); t.assign("path", mUri.getPath()); t.assign("parent_url", f.getParent() != null ? f.getParent() : f.getPath()); t.assignLoop("files", new ListEntityIterator() { int index = -1; @Override public void writeValue(OutputStream stream, String key) throws IOException { File f = files[index]; if ("name".equals(key)) { stream.write(f.getName().getBytes()); } if ("url".equals(key)) { stream.write(("file://" + f.getAbsolutePath()).getBytes()); } if ("type".equals(key)) { stream.write((f.isDirectory() ? "dir" : "file").getBytes()); } if ("size".equals(key)) { if (f.isFile()) { stream.write(readableFileSize(f.length()).getBytes()); } } if ("last_modified".equals(key)) { String date = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT) .format(f.lastModified()); stream.write(date.getBytes()); } if ("alt".equals(key)) { if (index % 2 == 0) { stream.write("alt".getBytes()); } } } @Override public ListEntityIterator getListIterator(String key) { return null; } @Override public void reset() { index = -1; } @Override public boolean moveToNext() { return (++index) < files.length; } }); t.write(mOutput); } static String readableFileSize(long size) { if(size <= 0) return "0"; final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); return new DecimalFormat("#,##0.#").format( size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } String getUriResourcePath() { final Pattern pattern = Pattern.compile("/?res/([\\w/]+)"); Matcher m = pattern.matcher(mUri.getPath()); if (m.matches()) { return m.group(1); } else { return mUri.getPath(); } } void writeResource(String fileName) throws IOException { Resources res = mContext.getResources(); String packageName = R.class.getPackage().getName(); int id = res.getIdentifier(fileName, null, packageName); if (id != 0) { InputStream in = res.openRawResource(id); byte[] buf = new byte[4096]; int read; while ((read = in.read(buf)) > 0) { mOutput.write(buf, 0, read); } } } void writeString(String str) throws IOException { mOutput.write(str.getBytes()); } void writeString(String str, int offset, int count) throws IOException { mOutput.write(str.getBytes(), offset, count); } void cleanup() { try { mOutput.close(); } catch (Exception e) { Log.e(TAG, "Failed to close pipe!", e); } } }