/WebVox/src/com/marvin/webvox/WebsiteSettingsActivity.java

http://eyes-free.googlecode.com/ · Java · 589 lines · 371 code · 52 blank · 166 comment · 59 complexity · ae896dc134b0bb7eb0aee9f36440450a MD5 · raw file

  1. /*
  2. * Copyright (C) 2009 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. package com.marvin.webvox;
  17. import com.marvin.webvox.R;
  18. import android.app.AlertDialog;
  19. import android.app.ListActivity;
  20. import android.content.Context;
  21. import android.content.DialogInterface;
  22. import android.database.Cursor;
  23. import android.graphics.Bitmap;
  24. import android.graphics.BitmapFactory;
  25. import android.net.Uri;
  26. import android.os.Bundle;
  27. import android.provider.Browser;
  28. import android.util.Log;
  29. import android.view.KeyEvent;
  30. import android.view.LayoutInflater;
  31. import android.view.Menu;
  32. import android.view.MenuInflater;
  33. import android.view.MenuItem;
  34. import android.view.View;
  35. import android.view.ViewGroup;
  36. import android.webkit.GeolocationPermissions;
  37. //import android.webkit.ValueCallback;
  38. import android.webkit.WebIconDatabase;
  39. import android.webkit.WebStorage;
  40. import android.widget.ArrayAdapter;
  41. import android.widget.AdapterView;
  42. import android.widget.AdapterView.OnItemClickListener;
  43. import android.widget.ImageView;
  44. import android.widget.TextView;
  45. import java.util.HashMap;
  46. import java.util.HashSet;
  47. import java.util.Iterator;
  48. import java.util.Map;
  49. import java.util.Set;
  50. import java.util.Vector;
  51. /**
  52. * Manage the settings for an origin.
  53. * We use it to keep track of the 'HTML5' settings, i.e. database (webstorage)
  54. * and Geolocation.
  55. */
  56. public class WebsiteSettingsActivity extends ListActivity {
  57. private String LOGTAG = "WebsiteSettingsActivity";
  58. private static String sMBStored = null;
  59. private SiteAdapter mAdapter = null;
  60. class Site {
  61. private String mOrigin;
  62. private String mTitle;
  63. private Bitmap mIcon;
  64. private int mFeatures;
  65. // These constants provide the set of features that a site may support
  66. // They must be consecutive. To add a new feature, add a new FEATURE_XXX
  67. // variable with value equal to the current value of FEATURE_COUNT, then
  68. // increment FEATURE_COUNT.
  69. private final static int FEATURE_WEB_STORAGE = 0;
  70. private final static int FEATURE_GEOLOCATION = 1;
  71. // The number of features available.
  72. private final static int FEATURE_COUNT = 2;
  73. public Site(String origin) {
  74. mOrigin = origin;
  75. mTitle = null;
  76. mIcon = null;
  77. mFeatures = 0;
  78. }
  79. public void addFeature(int feature) {
  80. mFeatures |= (1 << feature);
  81. }
  82. public boolean hasFeature(int feature) {
  83. return (mFeatures & (1 << feature)) != 0;
  84. }
  85. /**
  86. * Gets the number of features supported by this site.
  87. */
  88. public int getFeatureCount() {
  89. int count = 0;
  90. for (int i = 0; i < FEATURE_COUNT; ++i) {
  91. count += hasFeature(i) ? 1 : 0;
  92. }
  93. return count;
  94. }
  95. /**
  96. * Gets the ID of the nth (zero-based) feature supported by this site.
  97. * The return value is a feature ID - one of the FEATURE_XXX values.
  98. * This is required to determine which feature is displayed at a given
  99. * position in the list of features for this site. This is used both
  100. * when populating the view and when responding to clicks on the list.
  101. */
  102. public int getFeatureByIndex(int n) {
  103. int j = -1;
  104. for (int i = 0; i < FEATURE_COUNT; ++i) {
  105. j += hasFeature(i) ? 1 : 0;
  106. if (j == n) {
  107. return i;
  108. }
  109. }
  110. return -1;
  111. }
  112. public String getOrigin() {
  113. return mOrigin;
  114. }
  115. public void setTitle(String title) {
  116. mTitle = title;
  117. }
  118. public void setIcon(Bitmap icon) {
  119. mIcon = icon;
  120. }
  121. public Bitmap getIcon() {
  122. return mIcon;
  123. }
  124. public String getPrettyOrigin() {
  125. return mTitle == null ? null : hideHttp(mOrigin);
  126. }
  127. public String getPrettyTitle() {
  128. return mTitle == null ? hideHttp(mOrigin) : mTitle;
  129. }
  130. private String hideHttp(String str) {
  131. Uri uri = Uri.parse(str);
  132. return "http".equals(uri.getScheme()) ? str.substring(7) : str;
  133. }
  134. }
  135. class SiteAdapter extends ArrayAdapter<Site>
  136. implements AdapterView.OnItemClickListener {
  137. private int mResource;
  138. private LayoutInflater mInflater;
  139. private Bitmap mDefaultIcon;
  140. private Bitmap mUsageEmptyIcon;
  141. private Bitmap mUsageLowIcon;
  142. private Bitmap mUsageHighIcon;
  143. private Bitmap mLocationAllowedIcon;
  144. private Bitmap mLocationDisallowedIcon;
  145. private Site mCurrentSite;
  146. public SiteAdapter(Context context, int rsc) {
  147. super(context, rsc);
  148. mResource = rsc;
  149. mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  150. mDefaultIcon = BitmapFactory.decodeResource(getResources(),
  151. R.drawable.ic_launcher_shortcut_browser_bookmark);
  152. mUsageEmptyIcon = BitmapFactory.decodeResource(getResources(),
  153. R.drawable.ic_list_data_off);
  154. mUsageLowIcon = BitmapFactory.decodeResource(getResources(),
  155. R.drawable.ic_list_data_small);
  156. mUsageHighIcon = BitmapFactory.decodeResource(getResources(),
  157. R.drawable.ic_list_data_large);
  158. mLocationAllowedIcon = BitmapFactory.decodeResource(getResources(),
  159. R.drawable.ic_list_gps_on);
  160. mLocationDisallowedIcon = BitmapFactory.decodeResource(getResources(),
  161. R.drawable.ic_list_gps_denied);
  162. askForOrigins();
  163. }
  164. /**
  165. * Adds the specified feature to the site corresponding to supplied
  166. * origin in the map. Creates the site if it does not already exist.
  167. */
  168. private void addFeatureToSite(Map sites, String origin, int feature) {
  169. Site site = null;
  170. if (sites.containsKey(origin)) {
  171. site = (Site) sites.get(origin);
  172. } else {
  173. site = new Site(origin);
  174. sites.put(origin, site);
  175. }
  176. site.addFeature(feature);
  177. }
  178. public void askForOrigins() {
  179. // Get the list of origins we want to display.
  180. // All 'HTML 5 modules' (Database, Geolocation etc) form these
  181. // origin strings using WebCore::SecurityOrigin::toString(), so it's
  182. // safe to group origins here. Note that WebCore::SecurityOrigin
  183. // uses 0 (which is not printed) for the port if the port is the
  184. // default for the protocol. Eg http://www.google.com and
  185. // http://www.google.com:80 both record a port of 0 and hence
  186. // toString() == 'http://www.google.com' for both.
  187. /*
  188. WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
  189. public void onReceiveValue(Map origins) {
  190. Map sites = new HashMap<String, Site>();
  191. if (origins != null) {
  192. Iterator<String> iter = origins.keySet().iterator();
  193. while (iter.hasNext()) {
  194. addFeatureToSite(sites, iter.next(), Site.FEATURE_WEB_STORAGE);
  195. }
  196. }
  197. askForGeolocation(sites);
  198. }
  199. });
  200. */
  201. }
  202. /*
  203. public void askForGeolocation(final Map sites) {
  204. GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set>() {
  205. public void onReceiveValue(Set origins) {
  206. if (origins != null) {
  207. Iterator<String> iter = origins.iterator();
  208. while (iter.hasNext()) {
  209. addFeatureToSite(sites, iter.next(), Site.FEATURE_GEOLOCATION);
  210. }
  211. }
  212. populateIcons(sites);
  213. populateOrigins(sites);
  214. }
  215. });
  216. }
  217. */
  218. public void populateIcons(Map sites) {
  219. // Create a map from host to origin. This is used to add metadata
  220. // (title, icon) for this origin from the bookmarks DB.
  221. HashMap hosts = new HashMap<String, Set<Site> >();
  222. Set keys = sites.keySet();
  223. Iterator<String> originIter = keys.iterator();
  224. while (originIter.hasNext()) {
  225. String origin = originIter.next();
  226. Site site = (Site) sites.get(origin);
  227. String host = Uri.parse(origin).getHost();
  228. Set hostSites = null;
  229. if (hosts.containsKey(host)) {
  230. hostSites = (Set) hosts.get(host);
  231. } else {
  232. hostSites = new HashSet<Site>();
  233. hosts.put(host, hostSites);
  234. }
  235. hostSites.add(site);
  236. }
  237. // Check the bookmark DB. If we have data for a host used by any of
  238. // our origins, use it to set their title and favicon
  239. Cursor c = getContext().getContentResolver().query(Browser.BOOKMARKS_URI,
  240. new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE,
  241. Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null);
  242. if ((c != null) && c.moveToFirst()) {
  243. int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
  244. int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
  245. int faviconIndex = c.getColumnIndex(Browser.BookmarkColumns.FAVICON);
  246. do {
  247. String url = c.getString(urlIndex);
  248. String host = Uri.parse(url).getHost();
  249. if (hosts.containsKey(host)) {
  250. String title = c.getString(titleIndex);
  251. Bitmap bmp = null;
  252. byte[] data = c.getBlob(faviconIndex);
  253. if (data != null) {
  254. bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
  255. }
  256. Set matchingSites = (Set) hosts.get(host);
  257. Iterator<Site> sitesIter = matchingSites.iterator();
  258. while (sitesIter.hasNext()) {
  259. Site site = sitesIter.next();
  260. site.setTitle(title);
  261. if (bmp != null) {
  262. site.setIcon(bmp);
  263. }
  264. }
  265. }
  266. } while (c.moveToNext());
  267. }
  268. c.close();
  269. }
  270. public void populateOrigins(Map sites) {
  271. clear();
  272. // We can now simply populate our array with Site instances
  273. Set keys = sites.keySet();
  274. Iterator<String> originIter = keys.iterator();
  275. while (originIter.hasNext()) {
  276. String origin = originIter.next();
  277. Site site = (Site) sites.get(origin);
  278. add(site);
  279. }
  280. notifyDataSetChanged();
  281. if (getCount() == 0) {
  282. finish(); // we close the screen
  283. }
  284. }
  285. public int getCount() {
  286. if (mCurrentSite == null) {
  287. return super.getCount();
  288. }
  289. return mCurrentSite.getFeatureCount();
  290. }
  291. public String sizeValueToString(long bytes) {
  292. // We display the size in MB, to 1dp, rounding up to the next 0.1MB.
  293. // bytes should always be greater than zero.
  294. if (bytes <= 0) {
  295. Log.e(LOGTAG, "sizeValueToString called with non-positive value");
  296. return "0";
  297. }
  298. float megabytes = (float) bytes / (1024.0F * 1024.0F);
  299. int truncated = (int) Math.ceil(megabytes * 10.0F);
  300. float result = (float) (truncated / 10.0F);
  301. return String.valueOf(result);
  302. }
  303. /*
  304. * If we receive the back event and are displaying
  305. * site's settings, we want to go back to the main
  306. * list view. If not, we just do nothing (see
  307. * dispatchKeyEvent() below).
  308. */
  309. public boolean backKeyPressed() {
  310. if (mCurrentSite != null) {
  311. mCurrentSite = null;
  312. askForOrigins();
  313. return true;
  314. }
  315. return false;
  316. }
  317. /**
  318. * @hide
  319. * Utility function
  320. * Set the icon according to the usage
  321. */
  322. public void setIconForUsage(ImageView usageIcon, long usageInBytes) {
  323. float usageInMegabytes = (float) usageInBytes / (1024.0F * 1024.0F);
  324. usageIcon.setVisibility(View.VISIBLE);
  325. // We set the correct icon:
  326. // 0 < empty < 0.1MB
  327. // 0.1MB < low < 5MB
  328. // 5MB < high
  329. if (usageInMegabytes <= 0.1) {
  330. usageIcon.setImageBitmap(mUsageEmptyIcon);
  331. } else if (usageInMegabytes > 0.1 && usageInMegabytes <= 5) {
  332. usageIcon.setImageBitmap(mUsageLowIcon);
  333. } else if (usageInMegabytes > 5) {
  334. usageIcon.setImageBitmap(mUsageHighIcon);
  335. }
  336. }
  337. public View getView(int position, View convertView, ViewGroup parent) {
  338. View view;
  339. final TextView title;
  340. final TextView subtitle;
  341. ImageView icon;
  342. final ImageView usageIcon;
  343. final ImageView locationIcon;
  344. if (convertView == null) {
  345. view = mInflater.inflate(mResource, parent, false);
  346. } else {
  347. view = convertView;
  348. }
  349. title = (TextView) view.findViewById(R.id.title);
  350. subtitle = (TextView) view.findViewById(R.id.subtitle);
  351. icon = (ImageView) view.findViewById(R.id.icon);
  352. usageIcon = (ImageView) view.findViewById(R.id.usage_icon);
  353. locationIcon = (ImageView) view.findViewById(R.id.location_icon);
  354. usageIcon.setVisibility(View.GONE);
  355. locationIcon.setVisibility(View.GONE);
  356. if (mCurrentSite == null) {
  357. setTitle(getString(R.string.pref_extras_website_settings));
  358. Site site = getItem(position);
  359. title.setText(site.getPrettyTitle());
  360. subtitle.setText(site.getPrettyOrigin());
  361. icon.setVisibility(View.VISIBLE);
  362. usageIcon.setVisibility(View.INVISIBLE);
  363. locationIcon.setVisibility(View.INVISIBLE);
  364. Bitmap bmp = site.getIcon();
  365. if (bmp == null) {
  366. bmp = mDefaultIcon;
  367. }
  368. icon.setImageBitmap(bmp);
  369. // We set the site as the view's tag,
  370. // so that we can get it in onItemClick()
  371. view.setTag(site);
  372. String origin = site.getOrigin();
  373. /*
  374. if (site.hasFeature(Site.FEATURE_WEB_STORAGE)) {
  375. WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
  376. public void onReceiveValue(Long value) {
  377. if (value != null) {
  378. setIconForUsage(usageIcon, value.longValue());
  379. }
  380. }
  381. });
  382. }
  383. */
  384. /*
  385. if (site.hasFeature(Site.FEATURE_GEOLOCATION)) {
  386. locationIcon.setVisibility(View.VISIBLE);
  387. GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
  388. public void onReceiveValue(Boolean allowed) {
  389. if (allowed != null) {
  390. if (allowed.booleanValue()) {
  391. locationIcon.setImageBitmap(mLocationAllowedIcon);
  392. } else {
  393. locationIcon.setImageBitmap(mLocationDisallowedIcon);
  394. }
  395. }
  396. }
  397. });
  398. }
  399. */
  400. } else {
  401. setTitle(mCurrentSite.getPrettyTitle());
  402. icon.setVisibility(View.GONE);
  403. String origin = mCurrentSite.getOrigin();
  404. switch (mCurrentSite.getFeatureByIndex(position)) {
  405. /*
  406. case Site.FEATURE_WEB_STORAGE:
  407. WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
  408. public void onReceiveValue(Long value) {
  409. if (value != null) {
  410. String usage = sizeValueToString(value.longValue()) + " " + sMBStored;
  411. title.setText(R.string.webstorage_clear_data_title);
  412. subtitle.setText(usage);
  413. }
  414. }
  415. });
  416. break;
  417. case Site.FEATURE_GEOLOCATION:
  418. title.setText(R.string.geolocation_settings_page_title);
  419. GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
  420. public void onReceiveValue(Boolean allowed) {
  421. if (allowed != null) {
  422. if (allowed.booleanValue()) {
  423. subtitle.setText(R.string.geolocation_settings_page_summary_allowed);
  424. } else {
  425. subtitle.setText(R.string.geolocation_settings_page_summary_not_allowed);
  426. }
  427. }
  428. }
  429. });
  430. break;
  431. */
  432. }
  433. }
  434. return view;
  435. }
  436. public void onItemClick(AdapterView<?> parent,
  437. View view,
  438. int position,
  439. long id) {
  440. if (mCurrentSite != null) {
  441. switch (mCurrentSite.getFeatureByIndex(position)) {
  442. case Site.FEATURE_WEB_STORAGE:
  443. new AlertDialog.Builder(getContext())
  444. .setTitle(R.string.webstorage_clear_data_dialog_title)
  445. .setMessage(R.string.webstorage_clear_data_dialog_message)
  446. .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button,
  447. new AlertDialog.OnClickListener() {
  448. public void onClick(DialogInterface dlg, int which) {
  449. // WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin());
  450. mCurrentSite = null;
  451. askForOrigins();
  452. }})
  453. .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null)
  454. .setIcon(android.R.drawable.ic_dialog_alert)
  455. .show();
  456. break;
  457. case Site.FEATURE_GEOLOCATION:
  458. new AlertDialog.Builder(getContext())
  459. .setTitle(R.string.geolocation_settings_page_dialog_title)
  460. .setMessage(R.string.geolocation_settings_page_dialog_message)
  461. .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button,
  462. new AlertDialog.OnClickListener() {
  463. public void onClick(DialogInterface dlg, int which) {
  464. // GeolocationPermissions.getInstance().clear(mCurrentSite.getOrigin());
  465. mCurrentSite = null;
  466. askForOrigins();
  467. }})
  468. .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null)
  469. .setIcon(android.R.drawable.ic_dialog_alert)
  470. .show();
  471. break;
  472. }
  473. } else {
  474. mCurrentSite = (Site) view.getTag();
  475. notifyDataSetChanged();
  476. }
  477. }
  478. }
  479. /**
  480. * Intercepts the back key to immediately notify
  481. * NativeDialog that we are done.
  482. */
  483. public boolean dispatchKeyEvent(KeyEvent event) {
  484. if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
  485. && (event.getAction() == KeyEvent.ACTION_DOWN)) {
  486. if ((mAdapter != null) && (mAdapter.backKeyPressed())){
  487. return true; // event consumed
  488. }
  489. }
  490. return super.dispatchKeyEvent(event);
  491. }
  492. @Override
  493. protected void onCreate(Bundle icicle) {
  494. super.onCreate(icicle);
  495. if (sMBStored == null) {
  496. sMBStored = getString(R.string.webstorage_origin_summary_mb_stored);
  497. }
  498. mAdapter = new SiteAdapter(this, R.layout.website_settings_row);
  499. setListAdapter(mAdapter);
  500. getListView().setOnItemClickListener(mAdapter);
  501. }
  502. @Override
  503. public boolean onCreateOptionsMenu(Menu menu) {
  504. MenuInflater inflater = getMenuInflater();
  505. inflater.inflate(R.menu.websitesettings, menu);
  506. return true;
  507. }
  508. @Override
  509. public boolean onPrepareOptionsMenu(Menu menu) {
  510. // If we aren't listing any sites hide the clear all button (and hence the menu).
  511. return mAdapter.getCount() > 0;
  512. }
  513. @Override
  514. public boolean onOptionsItemSelected(MenuItem item) {
  515. switch (item.getItemId()) {
  516. case R.id.website_settings_menu_clear_all:
  517. // Show the prompt to clear all origins of their data and geolocation permissions.
  518. new AlertDialog.Builder(this)
  519. .setTitle(R.string.website_settings_clear_all_dialog_title)
  520. .setMessage(R.string.website_settings_clear_all_dialog_message)
  521. .setPositiveButton(R.string.website_settings_clear_all_dialog_ok_button,
  522. new AlertDialog.OnClickListener() {
  523. public void onClick(DialogInterface dlg, int which) {
  524. // WebStorage.getInstance().deleteAllData();
  525. // GeolocationPermissions.getInstance().clearAll();
  526. WebStorageSizeManager.resetLastOutOfSpaceNotificationTime();
  527. mAdapter.askForOrigins();
  528. finish();
  529. }})
  530. .setNegativeButton(R.string.website_settings_clear_all_dialog_cancel_button, null)
  531. .setIcon(android.R.drawable.ic_dialog_alert)
  532. .show();
  533. return true;
  534. }
  535. return false;
  536. }
  537. }