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 {
sUriMatcher.addURI(HomeProvider.AUTHORITY, "/", INDEX)57         sUriMatcher.addURI(HomeProvider.AUTHORITY, "/", INDEX);
sUriMatcher.addURI(HomeProvider.AUTHORITY, "res/*/*", RESOURCE)58         sUriMatcher.addURI(HomeProvider.AUTHORITY, "res/*/*", RESOURCE);
59     }
60 
RequestHandler(Context context, Uri uri, OutputStream out)61     public RequestHandler(Context context, Uri uri, OutputStream out) {
62         mUri = uri;
63         mContext = context.getApplicationContext();
64         mOutput = out;
65     }
66 
67     @Override
run()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 
doHandleRequest()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 
htmlEncode(String s)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";
writeTemplatedIndex()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 
writeFolderIndex()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 
readableFileSize(long size)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 
getUriResourcePath()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 
writeResource(String fileName)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 
writeString(String str)248     void writeString(String str) throws IOException {
249         mOutput.write(str.getBytes());
250     }
251 
writeString(String str, int offset, int count)252     void writeString(String str, int offset, int count) throws IOException {
253         mOutput.write(str.getBytes(), offset, count);
254     }
255 
cleanup()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