/*
* 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);
}
}
}