Merge "Browser: add homepage and bookmark feature"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 13f13b3..8c88f43 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -36,6 +36,7 @@
<uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
@@ -198,9 +199,30 @@
</intent-filter>
</activity>
+ <activity android:name="DownloadSettings" android:label="@string/download_settings_title"
+ android:theme="@style/DialogWhenLarge"
+ android:launchMode="singleTask"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:windowSoftInputMode="adjustResize">
+ <intent-filter>
+ <action android:name="android.intent.action.BROWSERDOWNLOAD" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name="ComboViewActivity">
</activity>
+ <!-- used to new folder in bookmark page for carrier -->
+ <activity android:name="AddBookmarkFolder" android:label="@string/save_bookmark_folder"
+ android:theme="@style/DialogWhenLarge"
+ android:configChanges="orientation|keyboardHidden|screenSize">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/bookmark"/>
+ </intent-filter>
+ </activity>
+
<!-- Bookmark thumbnail homescreen widget -->
<receiver
android:name=".widget.BookmarkThumbnailWidgetProvider"
diff --git a/res/layout/download_settings.xml b/res/layout/download_settings.xml
new file mode 100644
index 0000000..ad47363
--- /dev/null
+++ b/res/layout/download_settings.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2013, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="?android:attr/dividerHorizontal"
+ android:orientation="vertical"
+ android:showDividers="middle" >
+ <LinearLayout android:id="@+id/title_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ >
+ <TextView android:id="@+id/download_settings_title"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:gravity="center_vertical"
+ android:drawableLeft="@drawable/ic_bookmark_on_holo_dark"
+ android:text="@string/download_settings_title"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" >
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TableLayout
+ android:id="@+id/download_table_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dip"
+ android:paddingRight="20dip"
+ android:paddingTop="10dip"
+ android:shrinkColumns="1"
+ android:stretchColumns="1" >
+
+ <TableRow android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:text="@string/download_edit_filename_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+
+ <EditText
+ android:id="@+id/download_filename_edit"
+ android:layout_width="260dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:inputType="textCapSentences"
+ android:selectAllOnFocus="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </EditText>
+ </TableRow>
+
+ <TableRow
+ android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:text="@string/download_filepath_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+
+ <EditText
+ android:id="@+id/download_filepath_selected"
+ android:layout_width="260dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:editable="false"
+ android:ellipsize="end"
+ android:focusableInTouchMode="false"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="18sp" >
+ </EditText>
+ </TableRow>
+
+ <TableRow
+ android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/download_filesize"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+ <TextView
+ android:id="@+id/download_estimate_size_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/download_filesize"
+ android:paddingLeft="10dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+ </TableRow>
+
+ <TableRow
+ android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/download_timeneeded"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+ <TextView
+ android:id="@+id/download_estimate_time_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="10dip"
+ android:text="@string/download_timeneeded"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="18sp" >
+ </TextView>
+ </TableRow>
+ </TableLayout>
+ </ScrollView>
+ </FrameLayout>
+
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/download_cancle"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:text="@string/download_cancel" />
+
+ <Button
+ android:id="@+id/download_start"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:text="@string/download_start" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/menu/bookmark.xml b/res/menu/bookmark.xml
new file mode 100644
index 0000000..b43d7ce
--- /dev/null
+++ b/res/menu/bookmark.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/add_bookmark_menu_id"
+ android:title="@string/add_new_bookmark"
+ android:icon="@drawable/ic_add_string"/>
+ <item android:id="@+id/new_bmfolder_menu_id"
+ android:title="@string/new_folder"
+ android:icon="@drawable/ic_add_string"/>
+</menu>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d0b8452..e3df6d7 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -424,4 +424,39 @@
<string name="exit_browser_msg">请选择退出浏览器的方式?</string>
<string name="exit_minimize">最小化</string>
<string name="exit_quit">退出</string>
+
+ <!-- Add for new bookmark/folder Feature -->
+ <string name="save_bookmark_folder">新建书签文件夹</string>
+ <string name="browser_max_input_title">已达输入上限</string>
+ <string name="browser_max_input">您输入的字符数已经达到最大长度<xliff:g id="MAXLIMIT">%s</xliff:g>个字符。</string>
+ <string name="save_to_bookmarks_title">保存书签</string>
+ <string name="duplicated_folder_warning">该文件夹已经存在</string>
+ <string name="overwrite_bookmark_msg">该书签的标签或者地址已经存在,是否覆盖?</string>
+
+ <!-- Add for download save path setting Feature -->
+ <string name="invalid_path">"无效路径!"</string>
+ <string name="path_wrong">"路径错误"</string>
+ <string name="pref_download_title">下载路径设置</string>
+ <string name="pref_download_path_setting_screen_title">自定义下载路径</string>
+ <string name="download_start">确定</string>
+ <string name="download_cancel">取消</string>
+ <string name="download_timeneeded">时间</string>
+ <string name="download_filesize">大小</string>
+ <string name="download_filepath_label">路径</string>
+ <string name="download_edit_filename_label">名称</string>
+ <string name="download_default_path">/Download</string>
+ <string name="download_no_enough_memory">存储空间不足</string>
+ <string name="download_settings_title">下载设置</string>
+ <string name="save_page_needs_title">文件名称不能为空!</string>
+ <string name="filename_empty_title">文件名为空</string>
+ <string name="filename_empty_msg">文件名不能为空,请重新输入!</string>
+ <string name="unknow_length">未知</string>
+ <string name="download_file_exist_msg">文件已经存在,请重新输入文件名!</string>
+ <string name="download_file_exist">文件已经存在</string>
+ <string name ="time_min">分钟</string>
+ <string name="download_path_phone_stroage_label">/内置存储器</string>
+ <string name="download_path_sd_card_label">/SD卡</string>
+ <string name="download_path_unavailable_dlg_title">浏览器的下载路径不可达</string>
+ <string name="download_path_unavailable_dlg_msg">请重新设置浏览器的下载路径</string>
+ <string name="activity_not_found">没有找到处理 Intent <xliff:g id="NOACTIVITY">%s</xliff:g> 的Activity.</string>
</resources>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index 5f75723..9fd97b2 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -30,4 +30,5 @@
<integer name="max_bookmark_columns">5</integer>
<!-- The duration of the titlebar animation in millisecs -->
<integer name="titlebar_animation_duration">200</integer>
+ <integer name="netswitch_type_remind">1</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5167822..1b7fe73 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1038,4 +1038,42 @@
<string name="exit_browser_msg">Please Select How to Exit Browser?</string>
<string name="exit_minimize">Minimize</string>
<string name="exit_quit">Quit</string>
+
+ <!-- Add for new bookmark/folder Feature -->
+ <string name="save_bookmark_folder">New folder</string>
+ <string name="browser_max_input_title">Reached Input Limit</string>
+ <string name="browser_max_input">Your input has reached the maximum limit of <xliff:g id="MAXLIMIT">%s</xliff:g> characters.</string>
+ <string name="save_to_bookmarks_title">Save to Bookmarks</string>
+ <string name="duplicated_folder_warning">The folder has already exists</string>
+ <string name="overwrite_bookmark_msg">This bookmark\'s name or address already exists, overwrite it?</string>
+
+ <!-- Add for download save path setting Feature -->
+ <string name="invalid_path">"Path is invalid!"</string>
+ <string name="path_wrong">"Wrong Path"</string>
+ <string name="pref_download_title">Download Path Settings</string>
+ <string name="pref_download_path_setting_screen_title">Custom Download Path</string>
+ <string name="download_start">OK</string>
+ <string name="download_cancel">Cancel</string>
+ <string name="download_timeneeded">Time</string>
+ <string name="download_filesize">Size</string>
+ <string name="download_filepath_label">Path</string>
+ <string name="download_edit_filename_label">Name</string>
+ <string name="download_default_path">default</string>
+ <string name="default_savepath_name">/Download</string>
+ <string name="download_no_enough_memory">Download no enough memory</string>
+ <string name="download_settings_title">Download Settings</string>
+ <string name="save_page_needs_title">Download File Name is Empty!</string>
+ <string name="filename_empty_title">Download File Name is Null</string>
+ <string name="filename_empty_msg">Download File Name Can not Be Null!Please Enter a Valid File Name!</string>
+ <string name="download_file_setting">Download File Settings</string>
+ <string name="unknow_length">Unknow</string>
+ <string name="download_file_exist_msg">This File is Already Exist,Please Enter a New File Name!</string>
+ <string name="download_file_exist">File Exist</string>
+ <string name ="time_min">min</string>
+ <string name="download_path_phone_stroage_label">/Phone Stroage</string>
+ <string name="download_path_sd_card_label">/SD card</string>
+ <string name="download_path_unavailable_dlg_title">Download Directory Unavailable</string>
+ <string name="download_path_unavailable_dlg_msg">Please modify the Download Directory of Browser</string>
+ <string name="activity_not_found">Activity Not Found to Handle Intent <xliff:g id="NOACTIVITY">%s</xliff:g>.</string>
+ <string name="network_switch_remind_type">wifi_browser_interaction_remind</string>
</resources>
diff --git a/res/xml/download_settings_preferences.xml b/res/xml/download_settings_preferences.xml
new file mode 100644
index 0000000..0cd99a9
--- /dev/null
+++ b/res/xml/download_settings_preferences.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2013, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <PreferenceCategory
+ android:title="@string/pref_download_title"
+ android:key="download_path_setting_category">
+ <PreferenceScreen
+ android:key="download_path_setting_screen"
+ android:title="@string/pref_download_path_setting_screen_title"/>
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/com/android/browser/AddBookmarkFolder.java b/src/com/android/browser/AddBookmarkFolder.java
new file mode 100644
index 0000000..7fae373
--- /dev/null
+++ b/src/com/android/browser/AddBookmarkFolder.java
@@ -0,0 +1,951 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.Loader;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.browser.addbookmark.FolderSpinner;
+import com.android.browser.addbookmark.FolderSpinnerAdapter;
+import com.android.browser.provider.BrowserProvider2;
+
+public class AddBookmarkFolder extends Activity implements View.OnClickListener,
+ TextView.OnEditorActionListener, AdapterView.OnItemClickListener,
+ LoaderManager.LoaderCallbacks<Cursor>, BreadCrumbView.Controller,
+ FolderSpinner.OnSetSelectionListener, OnItemSelectedListener {
+
+ public static final long DEFAULT_FOLDER_ID = -1;
+
+ // Place on an edited bookmark to remove the saved thumbnail
+ public static final String CHECK_FOR_DUPE = "check_for_dupe";
+
+ public static final String BOOKMARK_CURRENT_ID = "bookmark_current_id";
+
+ /* package */static final String EXTRA_EDIT_BOOKMARK = "bookmark";
+
+ /* package */static final String EXTRA_IS_FOLDER = "is_folder";
+
+ private static final int MAX_CRUMBS_SHOWN = 2;
+
+ private long mOriginalFolder = -1;
+
+ private boolean mIsFolderChanged = false;
+
+ private boolean mIsOtherFolderSelected = false;
+
+ private boolean mIsRecentFolder = false;
+
+ // IDs for the CursorLoaders that are used.
+ private static final int LOADER_ID_ACCOUNTS = 0;
+
+ private static final int LOADER_ID_FOLDER_CONTENTS = 1;
+
+ private static final int LOADER_ID_EDIT_INFO = 2;
+
+ private EditText mTitle;
+
+ private EditText mAddress;
+
+ private TextView mButton;
+
+ private View mCancelButton;
+
+ private Bundle mMap;
+
+ private FolderSpinner mFolder;
+
+ private View mDefaultView;
+
+ private View mFolderSelector;
+
+ private EditText mFolderNamer;
+
+ private View mFolderCancel;
+
+ private boolean mIsFolderNamerShowing;
+
+ private View mFolderNamerHolder;
+
+ private View mAddNewFolder;
+
+ private View mAddSeparator;
+
+ private long mCurrentFolder;
+
+ private FolderAdapter mAdapter;
+
+ private BreadCrumbView mCrumbs;
+
+ private TextView mFakeTitle;
+
+ private View mCrumbHolder;
+
+ private AddBookmarkPage.CustomListView mListView;
+
+ private long mRootFolder;
+
+ private TextView mTopLevelLabel;
+
+ private Drawable mHeaderIcon;
+
+ private View mRemoveLink;
+
+ private View mFakeTitleHolder;
+
+ private FolderSpinnerAdapter mFolderAdapter;
+
+ private Spinner mAccountSpinner;
+
+ private ArrayAdapter<BookmarkAccount> mAccountAdapter;
+
+
+ private static class Folder {
+ String mName;
+
+ long mId;
+
+ Folder(String name, long id) {
+ mName = name;
+ mId = id;
+ }
+ }
+
+ private InputMethodManager getInputMethodManager() {
+ return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ }
+
+ private Uri getUriForFolder(long folder) {
+ BookmarkAccount account = (BookmarkAccount) mAccountSpinner.getSelectedItem();
+ if (folder == mRootFolder && account != null) {
+ return BookmarksLoader.addAccount(BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER,
+ account.mAccountType, account.mAccountName);
+ }
+ return BrowserContract.Bookmarks.buildFolderUri(folder);
+ }
+
+ public static long getIdFromData(Object data) {
+ if (data == null) {
+ return BrowserProvider2.FIXED_ID_ROOT;
+ } else {
+ Folder folder = (Folder) data;
+ return folder.mId;
+ }
+ }
+
+ @Override
+ public void onTop(BreadCrumbView view, int level, Object data) {
+ if (null == data) {
+ return;
+ }
+ Folder folderData = (Folder) data;
+ long folder = folderData.mId;
+ LoaderManager manager = getLoaderManager();
+ CursorLoader loader = (CursorLoader) ((Loader<?>) manager
+ .getLoader(LOADER_ID_FOLDER_CONTENTS));
+ loader.setUri(getUriForFolder(folder));
+ loader.forceLoad();
+ if (mIsFolderNamerShowing) {
+ completeOrCancelFolderNaming(true);
+ }
+ setShowBookmarkIcon(level == 1);
+ }
+
+ /**
+ * Show or hide the icon for bookmarks next to "Bookmarks" in the crumb
+ * view.
+ *
+ * @param show True if the icon should visible, false otherwise.
+ */
+ private void setShowBookmarkIcon(boolean show) {
+ Drawable drawable = show ? mHeaderIcon : null;
+ mTopLevelLabel.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (v == mFolderNamer) {
+ if (v.getText().length() > 0) {
+ if (actionId == EditorInfo.IME_NULL) {
+ // Only want to do this once.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ completeOrCancelFolderNaming(false);
+ }
+ }
+ }
+ // Steal the key press; otherwise a newline will be added
+ return true;
+ }
+ return false;
+ }
+
+ private void switchToDefaultView(boolean changedFolder) {
+ mFolderSelector.setVisibility(View.GONE);
+ mDefaultView.setVisibility(View.VISIBLE);
+ mCrumbHolder.setVisibility(View.GONE);
+ mFakeTitleHolder.setVisibility(View.VISIBLE);
+ if (changedFolder) {
+ Object data = mCrumbs.getTopData();
+ if (data != null) {
+ Folder folder = (Folder) data;
+ mCurrentFolder = folder.mId;
+ if (mCurrentFolder == mRootFolder) {
+ // The Spinner changed to show "Other folder ..." Change
+ // it back to "Bookmarks", which is position 0 if we are
+ // editing a folder, 1 otherwise.
+ mFolder.setSelectionIgnoringSelectionChange(0);
+ } else {
+ mFolderAdapter.setOtherFolderDisplayText(folder.mName);
+ }
+ }
+ } else {
+ if (mCurrentFolder == mRootFolder) {
+ mFolder.setSelectionIgnoringSelectionChange(0);
+ } else {
+ Object data = mCrumbs.getTopData();
+ if (data != null && ((Folder) data).mId == mCurrentFolder) {
+ // We are showing the correct folder hierarchy. The
+ // folder selector will say "Other folder..." Change it
+ // to say the name of the folder once again.
+ mFolderAdapter.setOtherFolderDisplayText(((Folder) data).mName);
+ } else {
+ // We are not showing the correct folder hierarchy.
+ // Clear the Crumbs and find the proper folder
+ setupTopCrumb();
+ LoaderManager manager = getLoaderManager();
+ manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
+
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mButton) {
+ if (mFolderSelector.getVisibility() == View.VISIBLE) {
+ // We are showing the folder selector.
+ if (mIsFolderNamerShowing) {
+ completeOrCancelFolderNaming(false);
+ } else {
+ switchToDefaultView(true);
+ }
+ } else {
+ if (save()) {
+ finish();
+ }
+ }
+ } else if (v == mCancelButton) {
+ if (mIsFolderNamerShowing) {
+ completeOrCancelFolderNaming(true);
+ } else if (mFolderSelector.getVisibility() == View.VISIBLE) {
+ switchToDefaultView(false);
+ } else {
+ finish();
+ }
+ } else if (v == mFolderCancel) {
+ completeOrCancelFolderNaming(true);
+ }
+ }
+
+ private void displayToastForExistingFolder() {
+ Toast.makeText(getApplicationContext(), R.string.duplicated_folder_warning,
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onSetSelection(long id) {
+ int intId = (int) id;
+ mIsFolderChanged = true;
+ mIsOtherFolderSelected = false;
+ mIsRecentFolder = false;
+ switch (intId) {
+ case FolderSpinnerAdapter.ROOT_FOLDER:
+ mCurrentFolder = mRootFolder;
+ mOriginalFolder = mCurrentFolder;
+ break;
+ case FolderSpinnerAdapter.HOME_SCREEN:
+
+ break;
+ case FolderSpinnerAdapter.OTHER_FOLDER:
+ mIsOtherFolderSelected = true;
+ switchToFolderSelector();
+ break;
+ case FolderSpinnerAdapter.RECENT_FOLDER:
+ mCurrentFolder = mFolderAdapter.recentFolderId();
+ mOriginalFolder = mCurrentFolder;
+ mIsRecentFolder = true;
+ // In case the user decides to select OTHER_FOLDER
+ // and choose a different one, so that we will start from
+ // the correct place.
+ LoaderManager manager = getLoaderManager();
+ manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Finish naming a folder, and close the IME
+ *
+ * @param cancel If true, the new folder is not created. If false, the new
+ * folder is created and the user is taken inside it.
+ */
+ private void completeOrCancelFolderNaming(boolean cancel) {
+ if (!cancel && !TextUtils.isEmpty(mFolderNamer.getText())) {
+ String name = mFolderNamer.getText().toString();
+ long id = addFolderToCurrent(mFolderNamer.getText().toString());
+ descendInto(name, id);
+ }
+ setShowFolderNamer(false);
+ getInputMethodManager().hideSoftInputFromWindow(mListView.getWindowToken(), 0);
+ }
+
+ private long addFolderToCurrent(String name) {
+ // Add the folder to the database
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.Bookmarks.TITLE, name);
+ values.put(BrowserContract.Bookmarks.IS_FOLDER, 1);
+ long currentFolder;
+ Object data = null;
+ if (null != mCrumbs) {
+ data = mCrumbs.getTopData();
+ }
+ if (data != null) {
+ currentFolder = ((Folder) data).mId;
+ } else {
+ currentFolder = mRootFolder;
+ }
+ currentFolder = mCurrentFolder;
+ if (mIsRecentFolder) {
+ values.put(BrowserContract.Bookmarks.PARENT, mCurrentFolder);
+ } else if (!(mIsFolderChanged && mIsOtherFolderSelected) && mOriginalFolder != -1) {
+ values.put(BrowserContract.Bookmarks.PARENT, mOriginalFolder);
+ } else {
+ values.put(BrowserContract.Bookmarks.PARENT, currentFolder);
+ }
+ Uri uri = getContentResolver().insert(BrowserContract.Bookmarks.CONTENT_URI, values);
+ if (uri != null) {
+ return ContentUris.parseId(uri);
+ } else {
+ return -1;
+ }
+ }
+
+ private void switchToFolderSelector() {
+ // Set the list to the top in case it is scrolled.
+ mListView.setSelection(0);
+ mFakeTitleHolder.setVisibility(View.GONE);
+ // mFakeTitle.setVisibility(View.GONE);
+ mDefaultView.setVisibility(View.GONE);
+ mFolderSelector.setVisibility(View.VISIBLE);
+ mCrumbHolder.setVisibility(View.VISIBLE);
+ getInputMethodManager().hideSoftInputFromWindow(mListView.getWindowToken(), 0);
+ }
+
+ private void descendInto(String foldername, long id) {
+ if (id != DEFAULT_FOLDER_ID) {
+ mCrumbs.pushView(foldername, new Folder(foldername, id));
+ mCrumbs.notifyController();
+ } else {
+ Toast.makeText(getApplicationContext(), R.string.duplicated_folder_warning,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private LoaderCallbacks<EditBookmarkInfo> mEditInfoLoaderCallbacks = new LoaderCallbacks<EditBookmarkInfo>() {
+
+ @Override
+ public void onLoaderReset(Loader<EditBookmarkInfo> loader) {
+ // Don't care
+ }
+
+ @Override
+ public void onLoadFinished(Loader<EditBookmarkInfo> loader, EditBookmarkInfo info) {
+ boolean setAccount = false;
+ // TODO: Detect if lastUsedId is a subfolder of info.id in the
+ // editing folder case. For now, just don't show the last used
+ // folder at all to prevent any chance of the user adding a parent
+ // folder to a child folder
+ if (info.mLastUsedId != -1 && info.mLastUsedId != info.mId) {
+ if (setAccount && info.mLastUsedId != mRootFolder
+ && TextUtils.equals(info.mLastUsedAccountName, info.mAccountName)
+ && TextUtils.equals(info.mLastUsedAccountType, info.mAccountType)) {
+ mFolderAdapter.addRecentFolder(info.mLastUsedId, info.mLastUsedTitle);
+ } else if (!setAccount) {
+ setAccount = true;
+ setAccount(info.mLastUsedAccountName, info.mLastUsedAccountType);
+ if (info.mLastUsedId != mRootFolder) {
+ mFolderAdapter.addRecentFolder(info.mLastUsedId, info.mLastUsedTitle);
+ }
+ }
+ }
+ if (!setAccount) {
+ mAccountSpinner.setSelection(0);
+ }
+ }
+
+ @Override
+ public Loader<EditBookmarkInfo> onCreateLoader(int id, Bundle args) {
+ return new EditBookmarkInfoLoader(AddBookmarkFolder.this, mMap);
+ }
+ };
+
+ void setAccount(String accountName, String accountType) {
+ for (int i = 0; i < mAccountAdapter.getCount(); i++) {
+ BookmarkAccount account = mAccountAdapter.getItem(i);
+ if (TextUtils.equals(account.mAccountName, accountName)
+ && TextUtils.equals(account.mAccountType, accountType)) {
+ mAccountSpinner.setSelection(i);
+ onRootFolderFound(account.rootFolderId);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ String[] projection;
+ switch (id) {
+ case LOADER_ID_ACCOUNTS:
+ return new AccountsLoader(this);
+ case LOADER_ID_FOLDER_CONTENTS:
+ projection = new String[] {
+ BrowserContract.Bookmarks._ID, BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.IS_FOLDER
+ };
+ String where = BrowserContract.Bookmarks.IS_FOLDER + " != 0" + " AND "
+ + BrowserContract.Bookmarks._ID + " != ?";
+ String whereArgs[] = new String[] {
+ Long.toString(mMap.getLong(BrowserContract.Bookmarks._ID))
+ };
+ long currentFolder;
+ Object data = mCrumbs.getTopData();
+ if (data != null) {
+ currentFolder = ((Folder) data).mId;
+ } else {
+ currentFolder = mRootFolder;
+ }
+ return new CursorLoader(this, getUriForFolder(currentFolder), projection, where,
+ whereArgs, BrowserContract.Bookmarks._ID + " ASC");
+ default:
+ throw new AssertionError("Asking for nonexistant loader!");
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case LOADER_ID_ACCOUNTS:
+ mAccountAdapter.clear();
+ while (cursor.moveToNext()) {
+ mAccountAdapter.add(new BookmarkAccount(this, cursor));
+ }
+ getLoaderManager().destroyLoader(LOADER_ID_ACCOUNTS);
+ getLoaderManager().restartLoader(LOADER_ID_EDIT_INFO, null,
+ mEditInfoLoaderCallbacks);
+ break;
+ case LOADER_ID_FOLDER_CONTENTS:
+ mAdapter.changeCursor(cursor);
+ break;
+ default:
+ break;
+ }
+ }
+
+ public void onLoaderReset(Loader<Cursor> loader) {
+ switch (loader.getId()) {
+ case LOADER_ID_FOLDER_CONTENTS:
+ mAdapter.changeCursor(null);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Move cursor to the position that has folderToFind as its "_id".
+ *
+ * @param cursor Cursor containing folders in the bookmarks database
+ * @param folderToFind "_id" of the folder to move to.
+ * @param idIndex Index in cursor of "_id"
+ * @throws AssertionError if cursor is empty or there is no row with
+ * folderToFind as its "_id".
+ */
+ void moveCursorToFolder(Cursor cursor, long folderToFind, int idIndex) throws AssertionError {
+ if (!cursor.moveToFirst()) {
+ throw new AssertionError("No folders in the database!");
+ }
+ long folder;
+ do {
+ folder = cursor.getLong(idIndex);
+ } while (folder != folderToFind && cursor.moveToNext());
+ if (cursor.isAfterLast()) {
+ throw new AssertionError("Folder(id=" + folderToFind
+ + ") holding this bookmark does not exist!");
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ TextView tv = (TextView) view.findViewById(android.R.id.text1);
+ // Switch to the folder that was clicked on.
+ descendInto(tv.getText().toString(), id);
+ }
+
+ private void setShowFolderNamer(boolean show) {
+ if (show != mIsFolderNamerShowing) {
+ mIsFolderNamerShowing = show;
+ if (show) {
+ // Set the selection to the folder namer so it will be in
+ // view.
+ mListView.addFooterView(mFolderNamerHolder);
+ } else {
+ mListView.removeFooterView(mFolderNamerHolder);
+ }
+ // Refresh the list.
+ mListView.setAdapter(mAdapter);
+ if (show) {
+ mListView.setSelection(mListView.getCount() - 1);
+ }
+ }
+ }
+
+ /**
+ * Shows a list of names of folders.
+ */
+ private class FolderAdapter extends CursorAdapter {
+ public FolderAdapter(Context context) {
+ super(context, null);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ((TextView) view.findViewById(android.R.id.text1)).setText(cursor.getString(cursor
+ .getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE)));
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = LayoutInflater.from(context).inflate(R.layout.folder_list_item, null);
+ view.setBackgroundDrawable(context.getResources().getDrawable(
+ android.R.drawable.list_selector_background));
+ return view;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // Do not show the empty view if the user is creating a new folder.
+ return super.isEmpty() && !mIsFolderNamerShowing;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ mMap = getIntent().getExtras();
+
+ setContentView(R.layout.browser_add_bookmark);
+
+ Window window = getWindow();
+
+ String title = this.getString(R.string.new_folder);
+ mFakeTitle = (TextView) findViewById(R.id.fake_title);
+ mFakeTitleHolder = findViewById(R.id.title_holder);
+ mFakeTitle.setText(this.getString(R.string.new_folder));
+
+ mTitle = (EditText) findViewById(R.id.title);
+ // add for cmcc test about waring limit of edit text
+ BrowserUtils.maxLengthFilter(AddBookmarkFolder.this, mTitle, BrowserUtils.FILENAME_MAX_LENGTH);
+
+ mTitle.setText(title);
+ mAddress = (EditText) findViewById(R.id.address);
+ mAddress.setVisibility(View.GONE);
+ findViewById(R.id.row_address).setVisibility(View.GONE);
+
+ mButton = (TextView) findViewById(R.id.OK);
+ mButton.setOnClickListener(this);
+
+ mCancelButton = findViewById(R.id.cancel);
+ mCancelButton.setOnClickListener(this);
+
+ mFolder = (FolderSpinner) findViewById(R.id.folder);
+ mFolderAdapter = new FolderSpinnerAdapter(this, false);
+ mFolder.setAdapter(mFolderAdapter);
+ mFolder.setOnSetSelectionListener(this);
+
+ mDefaultView = findViewById(R.id.default_view);
+ mFolderSelector = findViewById(R.id.folder_selector);
+
+ mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null);
+ mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer);
+ mFolderNamer.setOnEditorActionListener(this);
+ mFolderCancel = mFolderNamerHolder.findViewById(R.id.close);
+ mFolderCancel.setOnClickListener(this);
+
+ mAddNewFolder = findViewById(R.id.add_new_folder);
+ mAddNewFolder.setVisibility(View.GONE);
+ mAddSeparator = findViewById(R.id.add_divider);
+ mAddSeparator.setVisibility(View.GONE);
+
+ mCrumbs = (BreadCrumbView) findViewById(R.id.crumbs);
+ mCrumbs.setUseBackButton(true);
+ mCrumbs.setController(this);
+ mHeaderIcon = getResources().getDrawable(R.drawable.ic_folder_holo_dark);
+ mCrumbHolder = findViewById(R.id.crumb_holder);
+ mCrumbs.setMaxVisible(MAX_CRUMBS_SHOWN);
+
+ mAdapter = new FolderAdapter(this);
+ mListView = (AddBookmarkPage.CustomListView) findViewById(R.id.list);
+ View empty = findViewById(R.id.empty);
+ mListView.setEmptyView(empty);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(this);
+ mListView.addEditText(mFolderNamer);
+
+ mAccountAdapter = new ArrayAdapter<BookmarkAccount>(this,
+ android.R.layout.simple_spinner_item);
+ mAccountAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mAccountSpinner = (Spinner) findViewById(R.id.accounts);
+ mAccountSpinner.setAdapter(mAccountAdapter);
+ mAccountSpinner.setOnItemSelectedListener(this);
+
+ if (!window.getDecorView().isInTouchMode()) {
+ mButton.requestFocus();
+ }
+ // getLoaderManager().restartLoader(LOADER_ID_ACCOUNTS, null, this);
+
+ setShowFolderNamer(false);
+ mFolderNamer.setText(R.string.new_folder);
+ mFolderNamer.requestFocus();
+ InputMethodManager imm = getInputMethodManager();
+ imm.focusIn(mListView);
+ imm.showSoftInput(mFolderNamer, InputMethodManager.SHOW_IMPLICIT);
+
+ mCurrentFolder = getIntent().getLongExtra(
+ BrowserContract.Bookmarks.PARENT, DEFAULT_FOLDER_ID);
+ mOriginalFolder = mCurrentFolder;
+ if (!(mCurrentFolder == -1 || mCurrentFolder == 1)) {
+ mFolder.setSelectionIgnoringSelectionChange(1);
+ mFolderAdapter.setOtherFolderDisplayText(getNameFromId(mOriginalFolder));
+ }
+
+ getLoaderManager().restartLoader(LOADER_ID_ACCOUNTS, null, this);
+ }
+
+ // get folder title from folder id
+ private String getNameFromId(long mCurrentFolder2) {
+ String title = "";
+ Cursor cursor = null;
+ try {
+ cursor = getApplicationContext().getContentResolver().query(
+ BrowserContract.Bookmarks.CONTENT_URI,
+ new String[] {
+ BrowserContract.Bookmarks.TITLE
+ },
+ BrowserContract.Bookmarks._ID + " = ? AND "
+ + BrowserContract.Bookmarks.IS_DELETED + " = ? AND "
+ + BrowserContract.Bookmarks.IS_FOLDER + " = ? ", new String[] {
+ String.valueOf(mCurrentFolder2), 0 + "", 1 + ""
+ }, null);
+ if (cursor != null && cursor.getCount() != 0) {
+ while (cursor.moveToNext()) {
+ title = cursor.getString(0);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return title;
+ }
+
+ private void showRemoveButton() {
+ findViewById(R.id.remove_divider).setVisibility(View.VISIBLE);
+ mRemoveLink = findViewById(R.id.remove);
+ mRemoveLink.setVisibility(View.VISIBLE);
+ mRemoveLink.setOnClickListener(this);
+ }
+
+ // Called once we have determined which folder is the root folder
+ private void onRootFolderFound(long root) {
+ mRootFolder = root;
+ mCurrentFolder = mRootFolder;
+ setupTopCrumb();
+ onCurrentFolderFound();
+ }
+
+ private void setupTopCrumb() {
+ mCrumbs.clear();
+ String name = getString(R.string.bookmarks);
+ mTopLevelLabel = (TextView) mCrumbs.pushView(name, false, new Folder(name, mRootFolder));
+ // To better match the other folders.
+ mTopLevelLabel.setCompoundDrawablePadding(6);
+ }
+
+ private void onCurrentFolderFound() {
+ LoaderManager manager = getLoaderManager();
+ if (mCurrentFolder != mRootFolder) {
+ // Since we're not in the root folder, change the selection to other
+ // folder now. The text will get changed once we select the correct
+ // folder.
+ mFolder.setSelectionIgnoringSelectionChange(1);
+ } else {
+ setShowBookmarkIcon(true);
+ }
+ // Find the contents of the current folder
+ manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
+ }
+
+ /**
+ * Parse the data entered in the dialog and post a message to update the
+ * bookmarks database.
+ */
+ private boolean save() {
+ String title = mTitle.getText().toString().trim();
+
+ boolean emptyTitle = title.length() == 0;
+ Resources r = getResources();
+ if (emptyTitle) {
+ mTitle.setError(r.getText(R.string.bookmark_needs_title));
+ return false;
+ }
+
+ long id = addFolderToCurrent(title);
+ if (id == -1) {
+ displayToastForExistingFolder();
+ return false;
+ }
+
+ setResult(RESULT_OK);
+ return true;
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (mAccountSpinner == parent) {
+ long root = mAccountAdapter.getItem(position).rootFolderId;
+ if (root != mRootFolder) {
+ onRootFolderFound(root);
+ mFolderAdapter.clearRecentFolder();
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Don't care
+ }
+
+ static class AccountsLoader extends CursorLoader {
+
+ static final String[] PROJECTION = new String[] {
+ Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_TYPE, Accounts.ROOT_ID,
+ };
+
+ static final int COLUMN_INDEX_ACCOUNT_NAME = 0;
+
+ static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
+
+ static final int COLUMN_INDEX_ROOT_ID = 2;
+
+ public AccountsLoader(Context context) {
+ super(context, Accounts.CONTENT_URI, PROJECTION, null, null, null);
+ }
+
+ }
+
+ public static class BookmarkAccount {
+
+ private String mLabel;
+
+ String mAccountName;
+ String mAccountType;
+
+ public long rootFolderId;
+
+ public BookmarkAccount(Context context, Cursor cursor) {
+ mAccountName = cursor.getString(AccountsLoader.COLUMN_INDEX_ACCOUNT_NAME);
+ mAccountType = cursor.getString(AccountsLoader.COLUMN_INDEX_ACCOUNT_TYPE);
+ rootFolderId = cursor.getLong(AccountsLoader.COLUMN_INDEX_ROOT_ID);
+ mLabel = mAccountName;
+ if (TextUtils.isEmpty(mLabel)) {
+ mLabel = context.getString(R.string.local_bookmarks);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mLabel;
+ }
+ }
+
+ static class EditBookmarkInfo {
+ long mId = -1;
+
+ long mParentId = -1;
+
+ String mParentTitle;
+
+ String mEBITitle;
+
+ String mAccountName;
+
+ String mAccountType;
+
+ long mLastUsedId = -1;
+
+ String mLastUsedTitle;
+
+ String mLastUsedAccountName;
+
+ String mLastUsedAccountType;
+ }
+
+ static class EditBookmarkInfoLoader extends AsyncTaskLoader<EditBookmarkInfo> {
+
+ private Context mContext;
+
+ private Bundle mMap;
+
+ public EditBookmarkInfoLoader(Context context, Bundle bundle) {
+ super(context);
+ mContext = context.getApplicationContext();
+ mMap = bundle;
+ }
+
+ @Override
+ public EditBookmarkInfo loadInBackground() {
+ final ContentResolver cr = mContext.getContentResolver();
+ EditBookmarkInfo info = new EditBookmarkInfo();
+ Cursor c = null;
+ try {
+ // First, let's lookup the bookmark (check for dupes, get needed
+ // info)
+ String url = mMap.getString(BrowserContract.Bookmarks.URL);
+ info.mId = mMap.getLong(BrowserContract.Bookmarks._ID, -1);
+ boolean checkForDupe = mMap.getBoolean(CHECK_FOR_DUPE);
+ if (checkForDupe && info.mId == -1 && !TextUtils.isEmpty(url)) {
+ c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, new String[] {
+ BrowserContract.Bookmarks._ID
+ }, BrowserContract.Bookmarks.URL + "=?", new String[] {
+ url
+ }, null);
+ if (c.getCount() == 1 && c.moveToFirst()) {
+ info.mId = c.getLong(0);
+ }
+ c.close();
+ }
+ if (info.mId != -1) {
+ c = cr.query(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ info.mId), new String[] {
+ BrowserContract.Bookmarks.PARENT,
+ BrowserContract.Bookmarks.ACCOUNT_NAME,
+ BrowserContract.Bookmarks.ACCOUNT_TYPE, BrowserContract.Bookmarks.TITLE
+ }, null, null, null);
+ if (c.moveToFirst()) {
+ info.mParentId = c.getLong(0);
+ info.mAccountName = c.getString(1);
+ info.mAccountType = c.getString(2);
+ info.mEBITitle = c.getString(3);
+ }
+ c.close();
+ c = cr.query(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ info.mParentId), new String[] {
+ BrowserContract.Bookmarks.TITLE,
+ }, null, null, null);
+ if (c.moveToFirst()) {
+ info.mParentTitle = c.getString(0);
+ }
+ c.close();
+ }
+
+ // Figure out the last used folder/account
+ c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, new String[] {
+ BrowserContract.Bookmarks.PARENT,
+ }, null, null, BrowserContract.Bookmarks.DATE_MODIFIED + " DESC LIMIT 1");
+ if (c.moveToFirst()) {
+ long parent = c.getLong(0);
+ c.close();
+ c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, new String[] {
+ BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.ACCOUNT_NAME,
+ BrowserContract.Bookmarks.ACCOUNT_TYPE
+ }, BrowserContract.Bookmarks._ID + "=?", new String[] {
+ Long.toString(parent)
+ }, null);
+ if (c.moveToFirst()) {
+ info.mLastUsedId = parent;
+ info.mLastUsedTitle = c.getString(0);
+ info.mLastUsedAccountName = c.getString(1);
+ info.mLastUsedAccountType = c.getString(2);
+ }
+ c.close();
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return info;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+ }
+}
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index fdb34c4..580723c 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -16,10 +16,8 @@
package com.android.browser;
-import com.android.browser.addbookmark.FolderSpinner;
-import com.android.browser.addbookmark.FolderSpinnerAdapter;
-
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
@@ -28,6 +26,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.CursorLoader;
+import android.content.DialogInterface;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
@@ -40,6 +39,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.provider.Browser;
import android.provider.BrowserContract;
import android.provider.BrowserContract.Accounts;
import android.text.TextUtils;
@@ -62,6 +62,10 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.browser.BrowserUtils;
+import com.android.browser.addbookmark.FolderSpinner;
+import com.android.browser.addbookmark.FolderSpinnerAdapter;
+
import java.net.URI;
import java.net.URISyntaxException;
@@ -123,6 +127,9 @@
private FolderSpinnerAdapter mFolderAdapter;
private Spinner mAccountSpinner;
private ArrayAdapter<BookmarkAccount> mAccountAdapter;
+ // add for carrier which requires same title or address can not exist.
+ private long mDuplicateId;
+ private Context mDuplicateContext;
private static class Folder {
String Name;
@@ -256,8 +263,16 @@
mSaveToHomeScreen = false;
switchToDefaultView(true);
}
- } else if (save()) {
- finish();
+ } else {
+ // add for carrier which requires same title or address can not
+ // exist.
+ if (mSaveToHomeScreen) {
+ if (save()) {
+ return;
+ }
+ } else {
+ onSaveWithConfirm();
+ }
}
} else if (v == mCancelButton) {
if (mIsFolderNamerShowing) {
@@ -638,9 +653,11 @@
mTitle = (EditText) findViewById(R.id.title);
mTitle.setText(title);
+ BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mTitle, BrowserUtils.FILENAME_MAX_LENGTH);
mAddress = (EditText) findViewById(R.id.address);
mAddress.setText(url);
+ BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mAddress, BrowserUtils.ADDRESS_MAX_LENGTH);
mButton = (TextView) findViewById(R.id.OK);
mButton.setOnClickListener(this);
@@ -659,6 +676,11 @@
mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null);
mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer);
mFolderNamer.setOnEditorActionListener(this);
+
+ // add for carrier test about warning limit of edit text
+ BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mFolderNamer,
+ BrowserUtils.FILENAME_MAX_LENGTH);
+
mFolderCancel = mFolderNamerHolder.findViewById(R.id.close);
mFolderCancel.setOnClickListener(this);
@@ -835,6 +857,72 @@
}
}
+ static void deleteDuplicateBookmark(final Context context, final long id) {
+ Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id);
+ context.getContentResolver().delete(uri, null, null);
+ }
+
+ private void onSaveWithConfirm() {
+ String title = mTitle.getText().toString().trim();
+ String unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
+ String url = unfilteredUrl.trim();
+ Long id = mMap.getLong(BrowserContract.Bookmarks._ID);
+ int duplicateCount;
+ final ContentResolver cr = getContentResolver();
+
+ Cursor cursor = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
+ BookmarksLoader.PROJECTION,
+ "( title = ? OR url = ? ) AND parent = ?",
+ new String[] {
+ title, url, Long.toString(mCurrentFolder)
+ },
+ null);
+
+ if (cursor == null) {
+ save();
+ return;
+ }
+
+ duplicateCount = cursor.getCount();
+ if (duplicateCount <= 0) {
+ cursor.close();
+ save();
+ return;
+ } else {
+ try {
+ while (cursor.moveToNext()) {
+ mDuplicateId = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
+ mDuplicateContext = AddBookmarkPage.this;
+ }
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
+ if (mEditingExisting && duplicateCount == 1 && mDuplicateId == id) {
+ save();
+ return;
+ }
+
+ new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.save_to_bookmarks_title))
+ .setMessage(getString(R.string.overwrite_bookmark_msg))
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (mDuplicateContext == null) {
+ return;
+ }
+ deleteDuplicateBookmark(mDuplicateContext, mDuplicateId);
+ save();
+ }
+ })
+ .show();
+ }
+
/**
* Parse the data entered in the dialog and post a message to update the bookmarks database.
*/
@@ -842,8 +930,7 @@
createHandler();
String title = mTitle.getText().toString().trim();
- String unfilteredUrl;
- unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
+ String unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
boolean emptyTitle = title.length() == 0;
boolean emptyUrl = unfilteredUrl.trim().length() == 0;
@@ -856,7 +943,6 @@
mAddress.setError(r.getText(R.string.bookmark_needs_url));
}
return false;
-
}
String url = unfilteredUrl.trim();
if (!mEditingFolder) {
@@ -958,6 +1044,7 @@
setResult(RESULT_OK);
LogTag.logBookmarkAdded(url, "bookmarkview");
}
+ finish();
return true;
}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index b35ca04..a58e2a4 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -40,6 +40,8 @@
import android.webkit.JavascriptInterface;
import com.android.browser.UI.ComboViews;
+import com.android.browser.search.DefaultSearchEngine;
+import com.android.browser.search.SearchEngine;
import com.android.browser.stub.NullController;
import com.google.common.annotations.VisibleForTesting;
@@ -88,7 +90,9 @@
// If this was a web search request, pass it on to the default web
// search provider and finish this activity.
- if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) {
+ SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine();
+ boolean result = IntentHandler.handleWebSearchIntent(this, null, getIntent());
+ if (result && (searchEngine instanceof DefaultSearchEngine)) {
finish();
return;
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index b11162c..0d31017 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -41,6 +41,7 @@
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
+import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
@@ -88,6 +89,8 @@
static final String ACCOUNT_TYPE = "account_type";
static final String ACCOUNT_NAME = "account_name";
+ static final long DEFAULT_FOLDER_ID = -1;
+
BookmarksPageCallbacks mCallbacks;
View mRoot;
BookmarkExpandableView mGrid;
@@ -97,6 +100,7 @@
View mHeader;
HashMap<Integer, BrowserBookmarksAdapter> mBookmarkAdapters = new HashMap<Integer, BrowserBookmarksAdapter>();
JSONObject mState;
+ long mCurrentFolderId = BrowserProvider2.FIXED_ID_ROOT;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -147,6 +151,9 @@
} else if (loader.getId() >= LOADER_BOOKMARKS) {
BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
adapter.changeCursor(cursor);
+ if (adapter.getCount() != 0) {
+ mCurrentFolderId = adapter.getItem(0).getLong(BookmarksLoader.COLUMN_INDEX_PARENT);
+ }
}
}
@@ -158,6 +165,31 @@
}
}
+ //add for carrier feature which adds new bookmark/folder function.
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.bookmark, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final Activity activity = getActivity();
+ if (item.getItemId() == R.id.add_bookmark_menu_id) {
+ Intent intent = new Intent(activity, AddBookmarkPage.class);
+ intent.putExtra(BrowserContract.Bookmarks.URL, "http://");
+ intent.putExtra(BrowserContract.Bookmarks.TITLE, "");
+ intent.putExtra(BrowserContract.Bookmarks.PARENT, mCurrentFolderId);
+ activity.startActivity(intent);
+ }
+ if (item.getItemId() == R.id.new_bmfolder_menu_id) {
+ Intent intent = new Intent(activity, AddBookmarkFolder.class);
+ intent.putExtra(BrowserContract.Bookmarks.PARENT, mCurrentFolderId);
+ activity.startActivity(intent);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
@Override
public boolean onContextItemSelected(MenuItem item) {
if (!(item.getMenuInfo() instanceof BookmarkContextMenuInfo)) {
@@ -444,6 +476,9 @@
// update crumbs
crumbs.pushView(title, uri);
crumbs.setVisibility(View.VISIBLE);
+ Object data = crumbs.getTopData();
+ mCurrentFolderId = (data != null ? ContentUris.parseId((Uri) data)
+ : DEFAULT_FOLDER_ID);
}
loadFolder(groupPosition, uri);
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 68c575a..8d2d7b0 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -713,6 +713,11 @@
}
}
+ public String getDownloadPath() {
+ return mPrefs.getString(PREF_DOWNLOAD_PATH,
+ DownloadHandler.getDefaultDownloadPath(mContext));
+ }
+
// -----------------------------
// getter/setters for accessibility_preferences.xml
// -----------------------------
diff --git a/src/com/android/browser/BrowserUtils.java b/src/com/android/browser/BrowserUtils.java
new file mode 100644
index 0000000..e1a3600
--- /dev/null
+++ b/src/com/android/browser/BrowserUtils.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.util.Log;
+import android.widget.EditText;
+
+public class BrowserUtils {
+
+ private static final String LOGTAG = "BrowserUtils";
+ public static final int FILENAME_MAX_LENGTH = 32;
+ public static final int ADDRESS_MAX_LENGTH = 2048;
+ private static AlertDialog.Builder mAlertDialog = null;
+
+ public static void maxLengthFilter(final Context context, final EditText editText,
+ final int max_length) {
+ InputFilter[] contentFilters = new InputFilter[1];
+ contentFilters[0] = new InputFilter.LengthFilter(max_length) {
+ public CharSequence filter(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ int keep = max_length - (dest.length() - (dend - dstart));
+ if (keep <= 0) {
+ showWarningDialog(context, max_length);
+ return "";
+ } else if (keep >= end - start) {
+ return null;
+ } else {
+ if (keep < source.length()) {
+ showWarningDialog(context, max_length);
+ }
+ return source.subSequence(start, start + keep);
+ }
+ }
+ };
+ editText.setFilters(contentFilters);
+ }
+
+ private static void showWarningDialog(final Context context, int max_length) {
+ if (mAlertDialog != null)
+ return;
+
+ mAlertDialog = new AlertDialog.Builder(context);
+ mAlertDialog.setTitle(R.string.browser_max_input_title)
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setMessage(context.getString(R.string.browser_max_input, max_length))
+ .setPositiveButton(R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ return;
+ }
+ })
+ .show()
+ .setOnDismissListener(new DialogInterface.OnDismissListener() {
+ public void onDismiss(DialogInterface dialog) {
+ Log.w("BrowserUtils", "onDismiss");
+ mAlertDialog = null;
+ return;
+ }
+ });
+ }
+}
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 4ec5042..729e79e 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -39,8 +39,11 @@
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.net.Uri;
import android.net.http.SslError;
+import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
@@ -55,6 +58,7 @@
import android.provider.BrowserContract.Images;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Intents.Insert;
+import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.TextUtils;
import android.util.Log;
@@ -113,7 +117,9 @@
private static final String SEND_APP_ID_EXTRA =
"android.speech.extras.SEND_APPLICATION_ID_EXTRA";
private static final String INCOGNITO_URI = "browser:incognito";
-
+ private static final String PROP_NETSWITCH = "persist.env.browser.netswitch";
+ private static final String INTENT_WIFI_SELECTION_DATA_CONNECTION =
+ "android.net.wifi.cmcc.WIFI_SELECTION_DATA_CONNECTION";
// public message ids
public final static int LOAD_URL = 1001;
@@ -175,6 +181,7 @@
private Message mAutoFillSetupMessage;
private boolean mShouldShowErrorConsole;
+ private boolean mNetworkShouldNotify = true;
private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
@@ -518,7 +525,7 @@
case R.id.download_context_menu_id:
DownloadHandler.onDownloadStartNoStream(
mActivity, url, view.getSettings().getUserAgentString(),
- null, null, null, view.isPrivateBrowsingEnabled());
+ null, null, null, view.isPrivateBrowsingEnabled(), 0);
break;
}
break;
@@ -782,7 +789,6 @@
// Stop watching the default geolocation permissions
mSystemAllowGeolocationOrigins.stop();
mSystemAllowGeolocationOrigins = null;
- mCrashRecoveryHandler.clearState();
}
protected boolean isActivityPaused() {
@@ -828,6 +834,41 @@
return mLoadStopped;
}
+ private void handleNetworkNotify(WebView view) {
+ ConnectivityManager conMgr = (ConnectivityManager) this.getContext().getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ WifiManager wifiMgr = (WifiManager) this.getContext()
+ .getSystemService(Context.WIFI_SERVICE);
+ int networkSwitchTypeOK = this.getContext().getResources()
+ .getInteger(R.integer.netswitch_type_remind);
+
+ if (wifiMgr.isWifiEnabled()) {
+ NetworkInfo mNetworkInfo = conMgr.getActiveNetworkInfo();
+ if (mNetworkInfo == null
+ || (mNetworkInfo != null && (mNetworkInfo.getType() !=
+ ConnectivityManager.TYPE_WIFI))) {
+ int isReminder = Settings.System.getInt(
+ mActivity.getContentResolver(),
+ this.getContext().getResources()
+ .getString(R.string.network_switch_remind_type),
+ networkSwitchTypeOK);
+
+ if (isReminder == networkSwitchTypeOK) {
+ Intent intent = new Intent(
+ INTENT_WIFI_SELECTION_DATA_CONNECTION);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ this.getContext().startActivity(intent);
+ }
+ mNetworkShouldNotify = false;
+ }
+ } else {
+ if (!mNetworkHandler.isNetworkUp()) {
+ view.setNetworkAvailable(false);
+ Log.v(LOGTAG, "handleNetworkNotify() Wlan is not enabled.");
+ }
+ }
+ }
+
// WebViewController
@Override
@@ -843,8 +884,26 @@
// reset sync timer to avoid sync starts during loading a page
CookieSyncManager.getInstance().resetSync();
- if (!mNetworkHandler.isNetworkUp()) {
- view.setNetworkAvailable(false);
+ if (SystemProperties.getBoolean(PROP_NETSWITCH, false)) {
+ if (!mNetworkHandler.isNetworkUp()) {
+ Log.d(LOGTAG, "onPageStarted() network unavailable");
+ if (mNetworkShouldNotify) {
+ handleNetworkNotify(view);
+ } else {
+ view.setNetworkAvailable(false);
+ }
+ mNetworkShouldNotify = false;
+ } else {
+ Log.d(LOGTAG, "onPageStarted() network available");
+ if (mNetworkShouldNotify) {
+ handleNetworkNotify(view);
+ }
+ mNetworkShouldNotify = false;
+ }
+ } else {
+ if (!mNetworkHandler.isNetworkUp()) {
+ view.setNetworkAvailable(false);
+ }
}
// when BrowserActivity just starts, onPageStarted may be called before
@@ -1051,7 +1110,7 @@
long contentLength) {
WebView w = tab.getWebView();
boolean ret = DownloadHandler.onDownloadStart(mActivity, url, userAgent,
- contentDisposition, mimetype, referer, w.isPrivateBrowsingEnabled());
+ contentDisposition, mimetype, referer, w.isPrivateBrowsingEnabled(), contentLength);
if (ret == false && w.copyBackForwardList().getSize() == 0) {
// This Tab was opened for the sole purpose of downloading a
// file. Remove it.
@@ -2238,7 +2297,7 @@
saveDataUri();
} else {
DownloadHandler.onDownloadStartNoStream(mActivity, mText, mUserAgent,
- null, null, null, mPrivateBrowsing);
+ null, null, null, mPrivateBrowsing, 0);
}
return true;
}
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
index 46e6cbc..e3d31cd 100644
--- a/src/com/android/browser/DownloadHandler.java
+++ b/src/com/android/browser/DownloadHandler.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
+import android.app.DownloadManager.Request;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -29,13 +30,19 @@
import android.media.MediaFile;
import android.net.Uri;
import android.net.WebAddress;
+import android.os.Bundle;
import android.os.Environment;
+import android.os.StatFs;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.URLUtil;
import android.widget.Toast;
+import java.io.File;
+
/**
* Handle download requests
*/
@@ -45,6 +52,9 @@
com.android.browser.Browser.LOGD_ENABLED;
private static final String LOGTAG = "DLHandler";
+ private static String mInternalStorage;
+ private static String mExternalStorage;
+ private final static String INVALID_PATH = "/storage";
/**
* Notify the host application a download should be done, or that
@@ -57,9 +67,68 @@
* @param referer The referer associated with the downloaded url
* @param privateBrowsing If the request is coming from a private browsing tab.
*/
+
+ public static void startingDownload(Activity activity,
+ String url, String userAgent, String contentDisposition,
+ String mimetype, String referer, boolean privateBrowsing, long contentLength,
+ String filename, String downloadPath) {
+ // java.net.URI is a lot stricter than KURL so we have to encode some
+ // extra characters. Fix for b 2538060 and b 1634719
+ WebAddress webAddress;
+ try {
+ webAddress = new WebAddress(url);
+ webAddress.setPath(encodePath(webAddress.getPath()));
+ } catch (Exception e) {
+ // This only happens for very bad urls, we want to chatch the
+ // exception here
+ Log.e(LOGTAG, "Exception trying to parse url:" + url);
+ return;
+ }
+
+ String addressString = webAddress.toString();
+ Uri uri = Uri.parse(addressString);
+ final DownloadManager.Request request;
+ try {
+ request = new DownloadManager.Request(uri);
+ } catch (IllegalArgumentException e) {
+ Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ request.setMimeType(mimetype);
+ // set downloaded file destination to /sdcard/Download.
+ // or, should it be set to one of several Environment.DIRECTORY* dirs
+ // depending on mimetype?
+ try {
+ setDestinationDir(downloadPath, filename, request);
+ } catch (Exception e) {
+ showNoEnoughMemoryDialog(activity);
+ return;
+ }
+ // let this downloaded file be scanned by MediaScanner - so that it can
+ // show up in Gallery app, for example.
+ request.allowScanningByMediaScanner();
+ request.setDescription(webAddress.getHost());
+ // XXX: Have to use the old url since the cookies were stored using the
+ // old percent-encoded url.
+ String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
+ request.addRequestHeader("cookie", cookies);
+ request.addRequestHeader("User-Agent", userAgent);
+ request.addRequestHeader("Referer", referer);
+ request.setNotificationVisibility(
+ DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ final DownloadManager manager = (DownloadManager) activity
+ .getSystemService(Context.DOWNLOAD_SERVICE);
+ new Thread("Browser download") {
+ public void run() {
+ manager.enqueue(request);
+ }
+ }.start();
+ showStartDownloadToast(activity);
+ }
+
public static boolean onDownloadStart(final Activity activity, final String url,
final String userAgent, final String contentDisposition, final String mimetype,
- final String referer, final boolean privateBrowsing) {
+ final String referer, final boolean privateBrowsing, final long contentLength) {
// if we're dealing wih A/V content that's not explicitly marked
// for download, check if it's streamable.
if (contentDisposition == null
@@ -88,7 +157,7 @@
.setPositiveButton(R.string.video_save, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
- mimetype, referer, privateBrowsing);
+ mimetype, referer, privateBrowsing, contentLength);
}
})
.setNegativeButton(R.string.video_play, new DialogInterface.OnClickListener() {
@@ -142,7 +211,7 @@
}
}
onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
- mimetype, referer, privateBrowsing);
+ mimetype, referer, privateBrowsing, contentLength);
return false;
}
@@ -187,10 +256,11 @@
* @param referer The referer associated with the downloaded url
* @param privateBrowsing If the request is coming from a private browsing tab.
*/
- /*package */ static void onDownloadStartNoStream(Activity activity,
+ /* package */static void onDownloadStartNoStream(Activity activity,
String url, String userAgent, String contentDisposition,
- String mimetype, String referer, boolean privateBrowsing) {
+ String mimetype, String referer, boolean privateBrowsing, long contentLength) {
+ initStorageDefaultPath(activity);
String filename = URLUtil.guessFileName(url,
contentDisposition, mimetype);
@@ -218,63 +288,309 @@
return;
}
- // java.net.URI is a lot stricter than KURL so we have to encode some
- // extra characters. Fix for b 2538060 and b 1634719
- WebAddress webAddress;
- try {
- webAddress = new WebAddress(url);
- webAddress.setPath(encodePath(webAddress.getPath()));
- } catch (Exception e) {
- // This only happens for very bad urls, we want to chatch the
- // exception here
- Log.e(LOGTAG, "Exception trying to parse url:" + url);
- return;
- }
-
- String addressString = webAddress.toString();
- Uri uri = Uri.parse(addressString);
- final DownloadManager.Request request;
- try {
- request = new DownloadManager.Request(uri);
- } catch (IllegalArgumentException e) {
- Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
- return;
- }
- request.setMimeType(mimetype);
- // set downloaded file destination to /sdcard/Download.
- // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
- // let this downloaded file be scanned by MediaScanner - so that it can
- // show up in Gallery app, for example.
- request.allowScanningByMediaScanner();
- request.setDescription(webAddress.getHost());
- // XXX: Have to use the old url since the cookies were stored using the
- // old percent-encoded url.
- String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
- request.addRequestHeader("cookie", cookies);
- request.addRequestHeader("User-Agent", userAgent);
- request.addRequestHeader("Referer", referer);
- request.setNotificationVisibility(
- DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (mimetype == null) {
- if (TextUtils.isEmpty(addressString)) {
- return;
- }
// We must have long pressed on a link or image to download it. We
// are not sure of the mimetype in this case, so do a head request
- new FetchUrlMimeType(activity, request, addressString, cookies,
- userAgent).start();
+ new FetchUrlMimeType(activity, url, userAgent, referer,
+ privateBrowsing, filename).start();
} else {
- final DownloadManager manager
- = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
- new Thread("Browser download") {
- public void run() {
- manager.enqueue(request);
- }
- }.start();
+ startDownloadSettings(activity, url, userAgent, contentDisposition, mimetype, referer,
+ privateBrowsing, contentLength, filename);
}
+
+ }
+
+ public static void initStorageDefaultPath(Context context) {
+ mExternalStorage = getExternalStorageDirectory(context);
+ if (isPhoneStorageSupported()) {
+ mInternalStorage = Environment.getExternalStorageDirectory().getPath();
+ } else {
+ mInternalStorage = null;
+ }
+ }
+
+ public static void startDownloadSettings(Activity activity,
+ String url, String userAgent, String contentDisposition,
+ String mimetype, String referer, boolean privateBrowsing, long contentLength,
+ String filename) {
+ Bundle fileInfo = new Bundle();
+ fileInfo.putString("url", url);
+ fileInfo.putString("userAgent", userAgent);
+ fileInfo.putString("contentDisposition", contentDisposition);
+ fileInfo.putString("mimetype", mimetype);
+ fileInfo.putString("referer", referer);
+ fileInfo.putLong("contentLength", contentLength);
+ fileInfo.putBoolean("privateBrowsing", privateBrowsing);
+ fileInfo.putString("filename", filename);
+ Intent intent = new Intent("android.intent.action.BROWSERDOWNLOAD");
+ intent.putExtras(fileInfo);
+ activity.startActivity(intent);
+ }
+
+ public static void setAppointedFolder(String downloadPath) {
+ File file = new File(downloadPath);
+ if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new IllegalStateException(file.getAbsolutePath() +
+ " already exists and is not a directory");
+ }
+ } else {
+ if (!file.mkdir()) {
+ throw new IllegalStateException("Unable to create directory: " +
+ file.getAbsolutePath());
+ }
+ }
+ }
+
+ private static void setDestinationDir(String downloadPath, String filename, Request request) {
+ File file = new File(downloadPath);
+ if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new IllegalStateException(file.getAbsolutePath() +
+ " already exists and is not a directory");
+ }
+ } else {
+ if (!file.mkdir()) {
+ throw new IllegalStateException("Unable to create directory: " +
+ file.getAbsolutePath());
+ }
+ }
+ setDestinationFromBase(file, filename, request);
+ }
+
+ private static void setDestinationFromBase(File file, String filename, Request request) {
+ if (filename == null) {
+ throw new NullPointerException("filename cannot be null");
+ }
+ request.setDestinationUri(Uri.withAppendedPath(Uri.fromFile(file), filename));
+ }
+
+ public static void fileExistQueryDialog(Activity activity) {
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.download_file_exist)
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setMessage(R.string.download_file_exist_msg)
+ // if yes, delete existed file and start new download thread
+ .setPositiveButton(R.string.ok, null)
+ // if no, do nothing at all
+ .show();
+ }
+
+ public static long getAvailableMemory(String root) {
+ StatFs stat = new StatFs(root);
+ final long LEFT10MByte = 2560;
+ long blockSize = stat.getBlockSize();
+ long availableBlocks = stat.getAvailableBlocks() - LEFT10MByte;
+ return availableBlocks * blockSize;
+ }
+
+ public static void showNoEnoughMemoryDialog(Activity mContext) {
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.download_no_enough_memory)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.download_no_enough_memory)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ }
+
+ public static boolean manageNoEnoughMemory(Activity mContext, long contentLength, String root) {
+ Log.i(LOGTAG, "----------- download file contentLength is ------------>" + contentLength);
+ long mAvailableBytes = getAvailableMemory(root);
+ if (mAvailableBytes > 0) {
+ if (contentLength > mAvailableBytes) {
+ showNoEnoughMemoryDialog(mContext);
+ return true;
+ }
+ } else {
+ showNoEnoughMemoryDialog(mContext);
+ return true;
+ }
+ return false;
+ }
+
+ public static void showStartDownloadToast(Activity activity) {
+ Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ activity.startActivity(intent);
Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT)
.show();
}
+ /**
+ * wheather the storage status OK for download file
+ *
+ * @param activity
+ * @param filename the download file's name
+ * @param downloadPath the download file's path will be in
+ * @return boolean true is ok,and false is not
+ */
+ public static boolean isStorageStatusOK(Activity activity, String filename, String downloadPath) {
+ if (downloadPath.equals(INVALID_PATH)) {
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.path_wrong)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.invalid_path)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ return false;
+ }
+
+ if (!(isPhoneStorageSupported() && downloadPath.contains(mInternalStorage))) {
+ String status = getExternalStorageState(activity);
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ int title;
+ String msg;
+
+ // Check to see if the SDCard is busy, same as the music app
+ if (status.equals(Environment.MEDIA_SHARED)) {
+ msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);
+ title = R.string.download_sdcard_busy_dlg_title;
+ } else {
+ msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);
+ title = R.string.download_no_sdcard_dlg_title;
+ }
+
+ new AlertDialog.Builder(activity)
+ .setTitle(title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(msg)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ return false;
+ }
+ } else {
+ String status = Environment.getExternalStorageState();
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ int mTitle = R.string.download_path_unavailable_dlg_title;
+ String mMsg = activity.getString(R.string.download_path_unavailable_dlg_msg);
+ new AlertDialog.Builder(activity)
+ .setTitle(mTitle)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(mMsg)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * wheather support Phone Storage
+ *
+ * @return boolean true support Phone Storage ,false will be not
+ */
+ public static boolean isPhoneStorageSupported() {
+ return true;
+ }
+
+ /**
+ * show Dialog to warn filename is null
+ *
+ * @param activity
+ */
+ public static void showFilenameEmptyDialog(Activity activity) {
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.filename_empty_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.filename_empty_msg)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .show();
+ }
+
+ /**
+ * get the filename except the suffix and dot
+ *
+ * @return String the filename except suffix and dot
+ */
+ public static String getFilenameBase(String filename) {
+ int dotindex = filename.lastIndexOf('.');
+ if (dotindex != -1) {
+ return filename.substring(0, dotindex);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * get the filename's extension from filename
+ *
+ * @param filename the download filename, may be the user entered
+ * @return String the filename's extension
+ */
+ public static String getFilenameExtension(String filename) {
+ int dotindex = filename.lastIndexOf('.');
+ if (dotindex != -1) {
+ return filename.substring(dotindex + 1);
+ } else {
+ return "";
+ }
+ }
+
+ public static String getDefaultDownloadPath(Context context) {
+ String defaultDownloadPath;
+
+ String defaultStorage;
+ if (isPhoneStorageSupported()) {
+ defaultStorage = Environment.getExternalStorageDirectory().getPath();
+ } else {
+ defaultStorage = getExternalStorageDirectory(context);
+ }
+
+ defaultDownloadPath = defaultStorage + context.getString(R.string.default_savepath_name);
+ Log.e(LOGTAG, "defaultStorage directory is : " + defaultDownloadPath);
+ return defaultDownloadPath;
+ }
+
+ /**
+ * translate the directory name into a name which is easy to know for user
+ *
+ * @param activity
+ * @param downloadPath
+ * @return String
+ */
+ public static String getDownloadPathForUser(Activity activity, String downloadPath) {
+ if (downloadPath == null) {
+ return downloadPath;
+ }
+ final String phoneStorageDir;
+ final String sdCardDir = getExternalStorageDirectory(activity);
+ if (isPhoneStorageSupported()) {
+ phoneStorageDir = Environment.getExternalStorageDirectory().getPath();
+ } else {
+ phoneStorageDir = null;
+ }
+
+ if (downloadPath.startsWith(sdCardDir)) {
+ String sdCardLabel = activity.getResources().getString(
+ R.string.download_path_sd_card_label);
+ downloadPath = downloadPath.replace(sdCardDir, sdCardLabel);
+ } else if ((phoneStorageDir != null) && downloadPath.startsWith(phoneStorageDir)) {
+ String phoneStorageLabel = activity.getResources().getString(
+ R.string.download_path_phone_stroage_label);
+ downloadPath = downloadPath.replace(phoneStorageDir, phoneStorageLabel);
+ }
+ return downloadPath;
+ }
+
+ private static String getExternalStorageDirectory(Context context) {
+ String sd = null;
+ StorageManager mStorageManager = (StorageManager) context
+ .getSystemService(Context.STORAGE_SERVICE);
+ StorageVolume[] volumes = mStorageManager.getVolumeList();
+ for (int i = 0; i < volumes.length; i++) {
+ if (volumes[i].isRemovable() && volumes[i].allowMassStorage()) {
+ sd = volumes[i].getPath();
+ }
+ }
+ return sd;
+ }
+
+ private static String getExternalStorageState(Context context) {
+ StorageManager mStorageManager = (StorageManager) context
+ .getSystemService(Context.STORAGE_SERVICE);
+ return mStorageManager.getVolumeState(getExternalStorageDirectory(context));
+ }
}
diff --git a/src/com/android/browser/DownloadSettings.java b/src/com/android/browser/DownloadSettings.java
new file mode 100644
index 0000000..c1ddcb6
--- /dev/null
+++ b/src/com/android/browser/DownloadSettings.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser;
+
+import java.io.File;
+
+import android.app.Activity;
+import android.content.Intent;
+import java.lang.Thread;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.format.*;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.view.Window;
+import android.widget.Toast;
+import android.text.TextUtils;
+
+public class DownloadSettings extends Activity {
+
+ private EditText downloadFilenameET;
+ private EditText downloadPathET;
+ private TextView downloadEstimateSize;
+ private TextView downloadEstimateTime;
+ private Button downloadStart;
+ private Button downloadCancel;
+ private String url;
+ private String userAgent;
+ private String contentDisposition;
+ private String mimetype;
+ private String referer;
+ private String filenameBase;
+ private String filename;
+ private String filenameExtension;
+ private boolean privateBrowsing;
+ private long contentLength;
+ private String downloadPath;
+ private String downloadPathForUser;
+ private static final int downloadRate = (1024 * 100 * 60);// Download Rate
+ // 100KB/s
+ private final static String LOGTAG = "DownloadSettings";
+ private final static int DOWNLOAD_PATH = 0;
+ private boolean isDownloadStarted = false;
+
+ private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // initial the DownloadSettings view
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.download_settings);
+ downloadFilenameET = (EditText) findViewById(R.id.download_filename_edit);
+ downloadPathET = (EditText) findViewById(R.id.download_filepath_selected);
+ downloadEstimateSize = (TextView) findViewById(R.id.download_estimate_size_content);
+ downloadEstimateTime = (TextView) findViewById(R.id.download_estimate_time_content);
+ downloadStart = (Button) findViewById(R.id.download_start);
+ downloadCancel = (Button) findViewById(R.id.download_cancle);
+ downloadPathET.setOnClickListener(downloadPathListener);
+ downloadStart.setOnClickListener(downloadStartListener);
+ downloadCancel.setOnClickListener(downloadCancelListener);
+
+ // get the bundle from Intent
+ Intent intent = getIntent();
+ Bundle fileInfo = intent.getExtras();
+ url = fileInfo.getString("url");
+ userAgent = fileInfo.getString("userAgent");
+ contentDisposition = fileInfo.getString("contentDisposition");
+ mimetype = fileInfo.getString("mimetype");
+ referer = fileInfo.getString("referer");
+ contentLength = fileInfo.getLong("contentLength");
+ privateBrowsing = fileInfo.getBoolean("privateBrowsing");
+ filename = fileInfo.getString("filename");
+
+ // download filenamebase's length is depended on filenameLength's values
+ // if filenamebase.length >= flienameLength, destroy the last string!
+
+ filenameBase = DownloadHandler.getFilenameBase(filename);
+ if (filenameBase.length() >= (BrowserUtils.FILENAME_MAX_LENGTH)) {
+ filenameBase = filenameBase.substring(0, BrowserUtils.FILENAME_MAX_LENGTH);
+ }
+
+ // warring when user enter more over letters into the EditText
+ BrowserUtils.maxLengthFilter(DownloadSettings.this, downloadFilenameET,
+ BrowserUtils.FILENAME_MAX_LENGTH);
+
+ downloadFilenameET.setText(filenameBase);
+ downloadPath = BrowserSettings.getInstance().getDownloadPath();
+ downloadPathForUser = DownloadHandler.getDownloadPathForUser(DownloadSettings.this,
+ downloadPath);
+ setDownloadPathForUserText(downloadPathForUser);
+ setDownloadFileSizeText();
+ setDownloadFileTimeText();
+
+ }
+
+ private OnClickListener downloadPathListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ // start filemanager for getting download path
+ try {
+ Intent downloadPathIntent = new Intent("com.android.fileexplorer.action.DIR_SEL");
+ DownloadSettings.this.startActivityForResult(downloadPathIntent, DOWNLOAD_PATH);
+ } catch (Exception e) {
+ String err_msg = getString(R.string.activity_not_found,
+ "com.android.fileexplorer.action.DIR_SEL");
+ Toast.makeText(DownloadSettings.this, err_msg, Toast.LENGTH_LONG).show();
+ }
+
+ }
+ };
+
+ private OnClickListener downloadStartListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ filenameBase = getFilenameBaseFromUserEnter();
+ // check the filename user enter is null or not
+ if (filenameBase.length() <= 0) {
+ DownloadHandler.showFilenameEmptyDialog(DownloadSettings.this);
+ return;
+ }
+
+ filenameExtension = DownloadHandler.getFilenameExtension(filename);
+ filename = filenameBase + "." + filenameExtension;
+
+ // check the storage status
+ if (!DownloadHandler.isStorageStatusOK(DownloadSettings.this, filename, downloadPath)) {
+ return;
+ }
+
+ // check the storage memory enough or not
+ try {
+ DownloadHandler.setAppointedFolder(downloadPath);
+ } catch (Exception e) {
+ DownloadHandler.showNoEnoughMemoryDialog(DownloadSettings.this);
+ return;
+ }
+ boolean isNoEnoughMemory = DownloadHandler.manageNoEnoughMemory(DownloadSettings.this,
+ contentLength, downloadPath);
+ if (isNoEnoughMemory) {
+ return;
+ }
+
+ // check the download file is exist or not
+ String fullFilename = downloadPath + "/" + filename;
+ if (mimetype != null && new File(fullFilename).exists()) {
+ DownloadHandler.fileExistQueryDialog(DownloadSettings.this);
+ return;
+ }
+
+ // staring downloading
+ DownloadHandler.startingDownload(DownloadSettings.this,
+ url, userAgent, contentDisposition,
+ mimetype, referer, privateBrowsing, contentLength,
+ Uri.encode(filename), downloadPath);
+ isDownloadStarted = true;
+ }
+ };
+
+ private OnClickListener downloadCancelListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ };
+
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ protected void onPause() {
+ super.onPause();
+ if (isDownloadStarted) {
+ finish();
+ }
+ }
+
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+
+ if (DOWNLOAD_PATH == requestCode) {
+ if (resultCode == Activity.RESULT_OK && intent != null) {
+ downloadPath = intent.getStringExtra("result_dir_sel");
+ if (downloadPath != null) {
+ String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+ if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ if (downloadPath.startsWith("/storage/sdcard0"))
+ downloadPath = downloadPath.replace("/storage/sdcard0",
+ "/storage/emulated/0");
+ if (downloadPath.startsWith("/storage/emulated/legacy"))
+ downloadPath = downloadPath.replace("/storage/emulated/legacy",
+ "/storage/emulated/0");
+ }
+ downloadPathForUser = DownloadHandler.getDownloadPathForUser(
+ DownloadSettings.this, downloadPath);
+ setDownloadPathForUserText(downloadPathForUser);
+ }
+ }
+ }
+ }
+
+ /**
+ * show download path for user
+ *
+ * @param downloadPath the download path user can see
+ */
+ private void setDownloadPathForUserText(String downloadPathForUser) {
+ downloadPathET.setText(downloadPathForUser);
+ }
+
+ /**
+ * get the filename from user select the download path
+ *
+ * @return String the filename from user selected
+ */
+ private String getFilenameBaseFromUserEnter() {
+ return downloadFilenameET.getText().toString();
+ }
+
+ /**
+ * set the download file size for user to be known
+ */
+ private void setDownloadFileSizeText() {
+ String sizeText;
+ if (contentLength <= 0) {
+ sizeText = getString(R.string.unknow_length);
+ } else {
+ sizeText = getDownloadFileSize();
+ }
+ downloadEstimateSize.setText(sizeText);
+
+ }
+
+ /**
+ * set the time which downloaded this file will be estimately use;
+ */
+ private void setDownloadFileTimeText() {
+ String neededTimeText;
+ if (contentLength <= 0) {
+ neededTimeText = getString(R.string.unknow_length);
+ } else {
+ neededTimeText = getNeededTime() + getString(R.string.time_min);
+ }
+ downloadEstimateTime.setText(neededTimeText);
+ }
+
+ /**
+ * count the download file's size and format the values
+ *
+ * @return String the format values
+ */
+ private String getDownloadFileSize() {
+ String currentSizeText = "";
+ if (contentLength > 0) {
+ currentSizeText = Formatter.formatFileSize(DownloadSettings.this, contentLength);
+ }
+ return currentSizeText;
+ }
+
+ /**
+ * get the time download this file will be use,and format this time values
+ *
+ * @return long the valses of time which download this file will be use
+ */
+ private long getNeededTime() {
+ long timeNeeded = contentLength / downloadRate;
+ if (timeNeeded < 1) {
+ timeNeeded = 1;
+ }
+ Log.e(LOGTAG, "TimeNeeded:" + timeNeeded + "min");
+ // return the time like 5 min, not 5 s;
+ return timeNeeded;
+ }
+}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 33b5808..28bfc80 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -22,14 +22,15 @@
import org.apache.http.client.methods.HttpHead;
import org.apache.http.conn.params.ConnRouteParams;
-import android.app.DownloadManager;
+import android.app.Activity;
import android.content.Context;
import android.net.Proxy;
+import android.net.Uri;
import android.net.http.AndroidHttpClient;
-import android.os.Environment;
+import android.text.TextUtils;
import android.util.Log;
+import android.webkit.CookieManager;
import android.webkit.MimeTypeMap;
-import android.webkit.URLUtil;
import java.io.IOException;
@@ -48,18 +49,23 @@
private final static String LOGTAG = "FetchUrlMimeType";
private Context mContext;
- private DownloadManager.Request mRequest;
private String mUri;
- private String mCookies;
private String mUserAgent;
+ private String mFilename;
+ private String mReferer;
+ private Activity mActivity;
+ private boolean mPrivateBrowsing;
+ private long mContentLength;
- public FetchUrlMimeType(Context context, DownloadManager.Request request,
- String uri, String cookies, String userAgent) {
- mContext = context.getApplicationContext();
- mRequest = request;
- mUri = uri;
- mCookies = cookies;
+ public FetchUrlMimeType(Activity activity, String url, String userAgent,
+ String referer, boolean privateBrowsing, String filename) {
+ mActivity = activity;
+ mContext = activity.getApplicationContext();
+ mUri = url;
mUserAgent = userAgent;
+ mPrivateBrowsing = privateBrowsing;
+ mFilename = filename;
+ mReferer = referer;
}
@Override
@@ -80,13 +86,16 @@
}
HttpHead request = new HttpHead(mUri);
- if (mCookies != null && mCookies.length() > 0) {
- request.addHeader("Cookie", mCookies);
+ String cookies = CookieManager.getInstance().getCookie(mUri, mPrivateBrowsing);
+ if (cookies != null && cookies.length() > 0) {
+ request.addHeader("Cookie", cookies);
}
HttpResponse response;
+ String filename = mFilename;
String mimeType = null;
String contentDisposition = null;
+ String contentLength = null;
try {
response = client.execute(request);
// We could get a redirect here, but if we do lets let
@@ -101,6 +110,10 @@
mimeType = mimeType.substring(0, semicolonIndex);
}
}
+ Header contentLengthHeader = response.getFirstHeader("Content-Length");
+ if (contentLengthHeader != null) {
+ contentLength = contentLengthHeader.getValue();
+ }
Header contentDispositionHeader = response.getFirstHeader("Content-Disposition");
if (contentDispositionHeader != null) {
contentDisposition = contentDispositionHeader.getValue();
@@ -114,26 +127,150 @@
client.close();
}
- if (mimeType != null) {
- if (mimeType.equalsIgnoreCase("text/plain") ||
- mimeType.equalsIgnoreCase("application/octet-stream")) {
- String newMimeType =
- MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- MimeTypeMap.getFileExtensionFromUrl(mUri));
- if (newMimeType != null) {
- mimeType = newMimeType;
- mRequest.setMimeType(newMimeType);
- }
- }
- String filename = URLUtil.guessFileName(mUri, contentDisposition,
- mimeType);
- mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
- }
+ if (mimeType != null) {
+ Log.e(LOGTAG, "-----------the mimeType from http header is ------------->" + mimeType);
+ if (mimeType.equalsIgnoreCase("text/plain") ||
+ mimeType.equalsIgnoreCase("application/octet-stream")) {
+ String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ MimeTypeMap.getFileExtensionFromUrl(mUri));
+ if (newMimeType != null) {
+ mimeType = newMimeType;
+ }
+ }
- // Start the download
- DownloadManager manager = (DownloadManager) mContext.getSystemService(
- Context.DOWNLOAD_SERVICE);
- manager.enqueue(mRequest);
+ String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ if (fileExtension == null || (fileExtension != null && fileExtension.equals("bin"))) {
+ fileExtension = MimeTypeMap.getFileExtensionFromUrl(mUri);
+ if (fileExtension == null) {
+ fileExtension = "bin";
+ }
+ }
+ filename = DownloadHandler.getFilenameBase(filename) + "." + fileExtension;
+
+ } else {
+ String fileExtension = getFileExtensionFromUrlEx(mUri);
+ if (fileExtension == "") {
+ fileExtension = "bin";
+ }
+ String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
+ if (newMimeType != null) {
+ mimeType = newMimeType;
+ }
+ filename = guessFileNameEx(mUri, contentDisposition, mimeType);
+
+ }
+
+ if (contentLength != null) {
+ mContentLength = Long.parseLong(contentLength);
+ } else {
+ mContentLength = 0;
+ }
+
+ DownloadHandler.startDownloadSettings(mActivity, mUri, mUserAgent, contentDisposition,
+ mimeType, mReferer, mPrivateBrowsing, mContentLength, filename);
+ }
+
+ /**
+ * when we can not parse MineType and Filename from the header of http body
+ * ,Call the fallowing functions for this matter
+ * getFileExtensionFromUrlEx(String url) : get the file Extension from Url
+ * guessFileNameEx() : get the file name from url Note: this modified for
+ * download http://www.baidu.com girl picture error extension and error
+ * filename
+ */
+ private String getFileExtensionFromUrlEx(String url) {
+ Log.e("FetchUrlMimeType",
+ "--------can not get mimetype from http header, the URL is ---------->" + url);
+ if (!TextUtils.isEmpty(url)) {
+ int fragment = url.lastIndexOf('#');
+ if (fragment > 0) {
+ url = url.substring(0, fragment);
+ }
+
+ int filenamePos = url.lastIndexOf('/');
+ String filename =
+ 0 <= filenamePos ? url.substring(filenamePos + 1) : url;
+ Log.e(LOGTAG,
+ "--------can not get mimetype from http header, the temp filename is----------"
+ + filename);
+ // if the filename contains special characters, we don't
+ // consider it valid for our matching purposes:
+ if (!filename.isEmpty()) {
+ int dotPos = filename.lastIndexOf('.');
+ if (0 <= dotPos) {
+ return filename.substring(dotPos + 1);
+ }
+ }
+ }
+
+ return "";
+ }
+
+ private String guessFileNameEx(String url, String contentDisposition, String mimeType) {
+ String filename = null;
+ String extension = null;
+
+ // If all the other http-related approaches failed, use the plain uri
+ if (filename == null) {
+ String decodedUrl = Uri.decode(url);
+ if (decodedUrl != null) {
+ if (!decodedUrl.endsWith("/")) {
+ int index = decodedUrl.lastIndexOf('/') + 1;
+ if (index > 0) {
+ filename = decodedUrl.substring(index);
+ }
+ }
+ }
+ }
+
+ // Finally, if couldn't get filename from URI, get a generic filename
+ if (filename == null) {
+ filename = "downloadfile";
+ }
+
+ // Split filename between base and extension
+ // Add an extension if filename does not have one
+ int dotIndex = filename.indexOf('.');
+ if (dotIndex < 0) {
+ if (mimeType != null) {
+ extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ extension = "." + extension;
+ }
+ }
+ if (extension == null) {
+ if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) {
+ if (mimeType.equalsIgnoreCase("text/html")) {
+ extension = ".html";
+ } else {
+ extension = ".txt";
+ }
+ } else {
+ extension = ".bin";
+ }
+ }
+ } else {
+ if (mimeType != null) {
+ // Compare the last segment of the extension against the mime
+ // type.
+ // If there's a mismatch, discard the entire extension.
+ int lastDotIndex = filename.lastIndexOf('.');
+ String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ filename.substring(lastDotIndex + 1));
+ if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
+ extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ extension = "." + extension;
+ }
+ }
+ }
+ if (extension == null) {
+ extension = filename.substring(dotIndex);
+ }
+ filename = filename.substring(0, dotIndex);
+ }
+
+ return filename + extension;
}
}
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index 1828032..ffbb39a 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -47,7 +47,8 @@
static final String PREF_SEARCH_ENGINE = "search_engine";
static final String PREF_WEBSITE_SETTINGS = "website_settings";
static final String PREF_ALLOW_APP_TABS = "allow_apptabs";
-
+ // Keys for download path settings
+ static final String PREF_DOWNLOAD_PATH = "download_path_setting_screen";
// ----------------------
// Keys for debug_preferences.xml
// ----------------------
diff --git a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
index 0a97ba0..acc26ce 100644
--- a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
+++ b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
@@ -17,11 +17,16 @@
package com.android.browser.preferences;
import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.DownloadHandler;
import com.android.browser.PreferenceKeys;
import com.android.browser.R;
+import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
+import android.content.SharedPreferences.Editor;
+import android.net.Uri;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -31,6 +36,7 @@
import android.webkit.GeolocationPermissions;
import android.webkit.ValueCallback;
import android.webkit.WebStorage;
+import android.widget.Toast;
import java.util.Map;
import java.util.Set;
@@ -38,6 +44,7 @@
public class AdvancedPreferencesFragment extends PreferenceFragment
implements Preference.OnPreferenceChangeListener {
+ private static final int DOWNLOAD_PATH_RESULT_CODE = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -68,6 +75,62 @@
e = findPreference(PreferenceKeys.PREF_PLUGIN_STATE);
e.setOnPreferenceChangeListener(this);
updateListPreferenceSummary((ListPreference) e);
+ onInitdownloadSettingsPreference();
+ }
+
+ private void onInitdownloadSettingsPreference() {
+ addPreferencesFromResource(R.xml.download_settings_preferences);
+ PreferenceScreen downloadPathPreset =
+ (PreferenceScreen) findPreference(PreferenceKeys.PREF_DOWNLOAD_PATH);
+ downloadPathPreset.setOnPreferenceClickListener(onClickDownloadPathSettings());
+
+ String downloadPath = downloadPathPreset.getSharedPreferences().
+ getString(PreferenceKeys.PREF_DOWNLOAD_PATH,
+ BrowserSettings.getInstance().getDownloadPath());
+ String downloadPathForUser = DownloadHandler.getDownloadPathForUser(this.getActivity(),
+ downloadPath);
+ downloadPathPreset.setSummary(downloadPathForUser);
+ }
+
+ private Preference.OnPreferenceClickListener onClickDownloadPathSettings() {
+ return new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ try {
+ Intent i = new Intent("com.android.fileexplorer.action.DIR_SEL");
+ AdvancedPreferencesFragment.this.startActivityForResult(i,
+ DOWNLOAD_PATH_RESULT_CODE);
+ } catch (Exception e) {
+ String err_msg = getResources().getString(R.string.activity_not_found,
+ "com.android.fileexplorer.action.DIR_SEL");
+ Toast.makeText(getActivity(), err_msg, Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ };
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == DOWNLOAD_PATH_RESULT_CODE) {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String downloadPath = data.getStringExtra("result_dir_sel");
+ if (downloadPath != null) {
+ PreferenceScreen downloadPathPreset =
+ (PreferenceScreen) findPreference(PreferenceKeys.PREF_DOWNLOAD_PATH);
+ Editor editor = downloadPathPreset.getEditor();
+ editor.putString(PreferenceKeys.PREF_DOWNLOAD_PATH, downloadPath);
+ editor.apply();
+ String downloadPathForUser = DownloadHandler.getDownloadPathForUser(
+ this.getActivity(), downloadPath);
+ downloadPathPreset.setSummary(downloadPathForUser);
+ }
+
+ return;
+ }
+ }
+ return;
}
void updateListPreferenceSummary(ListPreference e) {
@@ -154,4 +217,4 @@
return "";
}
-}
\ No newline at end of file
+}