Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
abook_check
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abook_android
abook_check
Commits
f193357b
Commit
f193357b
authored
May 15, 2020
by
Kim Jinsung
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WBS #37838 360編集時のシーン追加処理改善
parent
4e03e1bd
Show whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
4319 additions
and
150 deletions
+4319
-150
ABVJE_Launcher_Android/AndroidManifest.xml
+6
-0
ABVJE_Res_Default_Android/res/values-ja/strings.xml
+14
-6
ABVJE_Res_Default_Android/res/values-ko/strings.xml
+14
-6
ABVJE_Res_Default_Android/res/values/strings.xml
+14
-6
ABVJE_UI_Android/res/layout/ac_device_image_list.xml
+66
-0
ABVJE_UI_Android/res/layout/item_device_thumbnail.xml
+20
-0
ABVJE_UI_Android/src/com/imagepicker/AsyncTask.java
+694
-0
ABVJE_UI_Android/src/com/imagepicker/DiskLruCache.java
+953
-0
ABVJE_UI_Android/src/com/imagepicker/ImageCache.java
+711
-0
ABVJE_UI_Android/src/com/imagepicker/ImageFetcher.java
+290
-0
ABVJE_UI_Android/src/com/imagepicker/ImageInternalFetcher.java
+38
-0
ABVJE_UI_Android/src/com/imagepicker/ImageResizer.java
+298
-0
ABVJE_UI_Android/src/com/imagepicker/ImageWorker.java
+475
-0
ABVJE_UI_Android/src/com/imagepicker/RecyclingBitmapDrawable.java
+102
-0
ABVJE_UI_Android/src/com/imagepicker/Utils.java
+54
-0
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/activity/ABVContentViewActivity.java
+0
-1
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/activity/ABVUIActivity.java
+2
-1
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/util/AlertDialogUtil.java
+20
-0
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/home/helper/ABookPermissionHelper.java
+5
-2
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/home/helper/ActivityHandlingHelper.java
+16
-0
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/DeviceImageListActivity.java
+407
-0
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/HTMLWebViewActivity.java
+0
-4
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/HTMLXWalkWebViewActivity.java
+0
-4
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/ParentWebViewActivity.java
+23
-120
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/adapter/ImageGalleryAdapter.java
+80
-0
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/view/CustomImage.java
+17
-0
No files found.
ABVJE_Launcher_Android/AndroidManifest.xml
View file @
f193357b
...
...
@@ -182,6 +182,12 @@
android:label=
"LoginPasswordChangeActivity"
android:theme=
"@android:style/Theme.NoTitleBar.Fullscreen"
>
</activity>
<activity
android:name=
"jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity"
android:configChanges=
"keyboardHidden|orientation|screenSize"
android:label=
"DeviceImageListActivity"
android:theme=
"@style/Theme_Contentdetailview"
/>
<activity
android:name=
"jp.agentec.abook.abv.ui.home.activity.ABookSettingActivity"
android:theme=
"@android:style/Theme.NoTitleBar"
/>
<activity
android:name=
"jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivity"
android:theme=
"@android:style/Theme.NoTitleBar.Fullscreen"
/>
<activity
android:name=
"jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivityDialog"
android:theme=
"@style/Theme.MyTheme.ModalDialog"
/>
...
...
ABVJE_Res_Default_Android/res/values-ja/strings.xml
View file @
f193357b
...
...
@@ -360,7 +360,7 @@
<string
name=
"label_entry"
>
登録
</string>
<string
name=
"label_base_file"
>
ベースファイル
</string>
<string
name=
"select_image"
>
画像選択
</string>
<string
name=
"error_msg_open_pano_edit"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。
</string>
<string
name=
"error_msg_open_pano_edit"
>
他の端末で資料が編集中の為、編集できません。
</string>
<string
name=
"P001"
>
資料名に半角カタカナは使用できません。
</string>
<string
name=
"P002"
>
既に資料が登録されています。
</string>
...
...
@@ -577,7 +577,7 @@
<string
name=
"label_entry_1"
>
登録(1)
</string>
<string
name=
"label_base_file_1"
>
ベースファイル(1)
</string>
<string
name=
"select_image_1"
>
画像選択(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
他の端末で資料が編集中の為、編集できません。(1)
</string>
<string
name=
"P001_1"
>
資料名に半角カタカナは使用できません。(1)
</string>
<string
name=
"P002_1"
>
既に資料が登録されています。(1)
</string>
...
...
@@ -753,7 +753,7 @@
<string
name=
"label_entry_2"
>
登録(2)
</string>
<string
name=
"label_base_file_2"
>
ベースファイル(2)
</string>
<string
name=
"select_image_2"
>
画像選択(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
他の端末で資料が編集中の為、編集できません。(2)
</string>
<string
name=
"P001_2"
>
資料名に半角カタカナは使用できません。(2)
</string>
<string
name=
"P002_2"
>
既に資料が登録されています。(2)
</string>
...
...
@@ -929,7 +929,7 @@
<string
name=
"label_entry_3"
>
登録(3)
</string>
<string
name=
"label_base_file_3"
>
ベースファイル(3)
</string>
<string
name=
"select_image_3"
>
画像選択(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
他の端末で資料が編集中の為、編集できません。(3)
</string>
<string
name=
"P001_3"
>
資料名に半角カタカナは使用できません。(3)
</string>
<string
name=
"P002_3"
>
既に資料が登録されています。(3)
</string>
...
...
@@ -1105,7 +1105,7 @@
<string
name=
"label_entry_4"
>
登録(4)
</string>
<string
name=
"label_base_file_4"
>
ベースファイル(4)
</string>
<string
name=
"select_image_4"
>
画像選択(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
他の端末で資料が編集中の為、編集できません。(4)
</string>
<string
name=
"P001_4"
>
資料名に半角カタカナは使用できません。(4)
</string>
<string
name=
"P002_4"
>
既に資料が登録されています。(4)
</string>
...
...
@@ -1281,7 +1281,7 @@
<string
name=
"label_entry_5"
>
登録(5)
</string>
<string
name=
"label_base_file_5"
>
ベースファイル(5)
</string>
<string
name=
"select_image_5"
>
画像選択(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
他の端末で資料が編集中の為、編集できません
(%1$s)
。(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
他の端末で資料が編集中の為、編集できません。(5)
</string>
<string
name=
"P001_5"
>
資料名に半角カタカナは使用できません。(5)
</string>
<string
name=
"P002_5"
>
既に資料が登録されています。(5)
</string>
...
...
@@ -1417,4 +1417,12 @@
<string
name=
"msg_confirm_close_edit_page"
>
編集を終了しますか?\n(保存されていない変更は破棄されます。)
</string>
<string
name=
"msg_error_edit_page_save"
>
ファイルの保存中に予想以外のエラーが発生しました。\n編集画面を終了します。
</string>
<string
name=
"msg_error_edit_page_open"
>
編集画面の表示に失敗しました。
</string>
<!-- ABookCheck 1.2.300 -->
<string
name=
"title_scene_image_select"
>
シーン画像選択
</string>
<string
name=
"msg_image_select_max_count_over"
>
シーン画像の選択可能な最大数は%s個までです。
</string>
<string
name=
"msg_image_select_send_comfirm"
>
選択したシーン画像を登録しますか?
</string>
<string
name=
"msg_image_select_send_success"
>
シーン画像の登録に成功しました。
</string>
<string
name=
"msg_image_select_send_fail_retry"
>
シーン画像の登録に失敗しました。再度登録処理を実行しますか?
</string>
<string
name=
"msg_access_registing"
>
登録中
</string>
</resources>
ABVJE_Res_Default_Android/res/values-ko/strings.xml
View file @
f193357b
...
...
@@ -362,7 +362,7 @@
<string
name=
"label_entry"
>
등록
</string>
<string
name=
"label_base_file"
>
베이스 파일
</string>
<string
name=
"select_image"
>
이미지 선택
</string>
<string
name=
"error_msg_open_pano_edit"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.
</string>
<string
name=
"error_msg_open_pano_edit"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.
</string>
<string
name=
"P001"
>
자료명에 반각가타가나는 사용할 수 없습니다.
</string>
<string
name=
"P002"
>
이미 자료가 등록되어 있습니다.
</string>
...
...
@@ -580,7 +580,7 @@
<string
name=
"label_entry_1"
>
등록(1)
</string>
<string
name=
"label_base_file_1"
>
베이스 파일(1)
</string>
<string
name=
"select_image_1"
>
이미지 선택(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(1)
</string>
<string
name=
"P001_1"
>
자료명에 반각가타가나는 사용할 수 없습니다.(1)
</string>
<string
name=
"P002_1"
>
이미 자료가 등록되어 있습니다.(1)
</string>
...
...
@@ -758,7 +758,7 @@
<string
name=
"label_entry_2"
>
등록(2)
</string>
<string
name=
"label_base_file_2"
>
베이스 파일(2)
</string>
<string
name=
"select_image_2"
>
이미지 선택(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(2)
</string>
<string
name=
"P001_2"
>
자료명에 반각가타가나는 사용할 수 없습니다.(2)
</string>
<string
name=
"P002_2"
>
이미 자료가 등록되어 있습니다.(2)
</string>
...
...
@@ -935,7 +935,7 @@
<string
name=
"label_entry_3"
>
등록(3)
</string>
<string
name=
"label_base_file_3"
>
베이스 파일(3)
</string>
<string
name=
"select_image_3"
>
이미지 선택(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(3)
</string>
<string
name=
"P001_3"
>
자료명에 반각가타가나는 사용할 수 없습니다.(3)
</string>
<string
name=
"P002_3"
>
이미 자료가 등록되어 있습니다.(3)
</string>
...
...
@@ -1112,7 +1112,7 @@
<string
name=
"label_entry_4"
>
등록(4)
</string>
<string
name=
"label_base_file_4"
>
베이스 파일(4)
</string>
<string
name=
"select_image_4"
>
이미지 선택(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(4)
</string>
<string
name=
"P001_4"
>
자료명에 반각가타가나는 사용할 수 없습니다.(4)
</string>
<string
name=
"P002_4"
>
이미 자료가 등록되어 있습니다.(4)
</string>
...
...
@@ -1289,7 +1289,7 @@
<string
name=
"label_entry_5"
>
등록(5)
</string>
<string
name=
"label_base_file_5"
>
베이스 파일(5)
</string>
<string
name=
"select_image_5"
>
이미지 선택(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다
(%1$s)
.(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(5)
</string>
<string
name=
"P001_5"
>
자료명에 반각가타가나는 사용할 수 없습니다.(5)
</string>
<string
name=
"P002_5"
>
이미 자료가 등록되어 있습니다.(5)
</string>
...
...
@@ -1425,4 +1425,11 @@
<string
name=
"msg_confirm_close_edit_page"
>
편집을 종료하시겠습니까?\n(저장되지 않은 변경 사항은 삭제됩니다.)
</string>
<string
name=
"msg_error_edit_page_save"
>
파일 저장 중에 예기치 않은 오류가 발생했습니다.\n편집창을 종료합니다.
</string>
<string
name=
"msg_error_edit_page_open"
>
편집창을 여는데 실패했습니다.
</string>
<!-- ABookCheck 1.2.300 -->
<string
name=
"title_scene_image_select"
>
장면 이미지 선택
</string>
<string
name=
"msg_image_select_max_count_over"
>
장면 이미지의 선택 가능한 최대 개수는 %s개입니다.
</string>
<string
name=
"msg_image_select_send_comfirm"
>
선택한 장면 이미지를 등록하시겠습니까?
</string>
<string
name=
"msg_image_select_send_success"
>
장면 이미지 등록에 성공하였습니다.
</string>
<string
name=
"msg_image_select_send_fail_retry"
>
장면 이미지 등록에 실패하였습니다. 다시 등록 처리를 실행 하시겠습니까?
</string>
<string
name=
"msg_access_registing"
>
등록중
</string>
</resources>
\ No newline at end of file
ABVJE_Res_Default_Android/res/values/strings.xml
View file @
f193357b
...
...
@@ -366,7 +366,7 @@
<string
name=
"label_entry"
>
Registration
</string>
<string
name=
"label_base_file"
>
Base file
</string>
<string
name=
"select_image"
>
Image selection
</string>
<string
name=
"error_msg_open_pano_edit"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.
</string>
d
<string
name=
"error_msg_open_pano_edit"
>
The content can not be edited because the content is being edited on another terminal.
</string>
d
<string
name=
"P001"
>
Hankaku katakana can not be used for content name.
</string>
<string
name=
"P002"
>
Content has already been registered.
</string>
...
...
@@ -583,7 +583,7 @@
<string
name=
"label_entry_1"
>
Registration(1)
</string>
<string
name=
"label_base_file_1"
>
Base file(1)
</string>
<string
name=
"select_image_1"
>
Image selection(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.(1)
</string>
<string
name=
"error_msg_open_pano_edit_1"
>
The content can not be edited because the content is being edited on another terminal.(1)
</string>
<string
name=
"P001_1"
>
Hankaku katakana can not be used for content name.(1)
</string>
<string
name=
"P002_1"
>
Content has already been registered.(1)
</string>
...
...
@@ -759,7 +759,7 @@
<string
name=
"label_entry_2"
>
Registration(2)
</string>
<string
name=
"label_base_file_2"
>
Base file(2)
</string>
<string
name=
"select_image_2"
>
Image selection(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.(2)
</string>
<string
name=
"error_msg_open_pano_edit_2"
>
The content can not be edited because the content is being edited on another terminal.(2)
</string>
<string
name=
"P001_2"
>
Hankaku katakana can not be used for content name.(2)
</string>
<string
name=
"P002_2"
>
Content has already been registered.(2)
</string>
...
...
@@ -935,7 +935,7 @@
<string
name=
"label_entry_3"
>
Registration(3)
</string>
<string
name=
"label_base_file_3"
>
Base file(3)
</string>
<string
name=
"select_image_3"
>
Image selection(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.(3)
</string>
<string
name=
"error_msg_open_pano_edit_3"
>
The content can not be edited because the content is being edited on another terminal.(3)
</string>
<string
name=
"P001_3"
>
Hankaku katakana can not be used for content name.(3)
</string>
<string
name=
"P002_3"
>
Content has already been registered.(3)
</string>
...
...
@@ -1111,7 +1111,7 @@
<string
name=
"label_entry_4"
>
Registration(4)
</string>
<string
name=
"label_base_file_4"
>
Base file(4)
</string>
<string
name=
"select_image_4"
>
Image selection(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.(4)
</string>
<string
name=
"error_msg_open_pano_edit_4"
>
The content can not be edited because the content is being edited on another terminal.(4)
</string>
<string
name=
"P001_4"
>
Hankaku katakana can not be used for content name.(4)
</string>
<string
name=
"P002_4"
>
Content has already been registered.(4)
</string>
...
...
@@ -1287,7 +1287,7 @@
<string
name=
"label_entry_5"
>
Registration(5)
</string>
<string
name=
"label_base_file_5"
>
Base file(5)
</string>
<string
name=
"select_image_5"
>
Image selection(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
The content can not be edited
(%1$s)
because the content is being edited on another terminal.(5)
</string>
<string
name=
"error_msg_open_pano_edit_5"
>
The content can not be edited because the content is being edited on another terminal.(5)
</string>
<string
name=
"P001_5"
>
Hankaku katakana can not be used for content name.(5)
</string>
<string
name=
"P002_5"
>
Content has already been registered.(5)
</string>
...
...
@@ -1424,4 +1424,11 @@
<string
name=
"msg_error_edit_page_save"
>
An unexpected error has occurred while saving the image.\nClosing the editing Page.
</string>
<string
name=
"msg_error_edit_page_open"
>
Failed to open the edit tool.
</string>
<!-- ABookCheck 1.2.300 -->
<string
name=
"title_scene_image_select"
>
Select scene image
</string>
<string
name=
"msg_image_select_max_count_over"
>
The maximum number of scene images that can be selected is %s.
</string>
<string
name=
"msg_image_select_send_comfirm"
>
Register the selected scene image?
</string>
<string
name=
"msg_image_select_send_success"
>
You have successfully registered a scene image.
</string>
<string
name=
"msg_image_select_send_fail_retry"
>
Registration of the scene image failed. Do you want to register again?
</string>
<string
name=
"msg_access_registing"
>
Registring
</string>
</resources>
\ No newline at end of file
ABVJE_UI_Android/res/layout/ac_device_image_list.xml
0 → 100644
View file @
f193357b
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:orientation=
"vertical"
>
<RelativeLayout
android:id=
"@+id/device_image_list_list_toolbar"
style=
"@style/OperationSearchToolBar"
android:layout_width=
"match_parent"
android:layout_height=
"50dp"
>
<Button
android:id=
"@+id/regist"
android:layout_width=
"87dp"
android:layout_height=
"45dp"
android:layout_alignParentStart=
"true"
android:layout_alignParentLeft=
"true"
android:layout_alignParentBottom=
"true"
android:layout_centerHorizontal=
"true"
android:text=
"@string/label_entry"
/>
<TextView
android:id=
"@+id/device_image_list_title"
style=
"@style/DialogToolBarTitle"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerInParent=
"true"
android:text=
"@string/title_scene_image_select"
android:textColor=
"@color/edt_text"
android:textSize=
"@dimen/opeartion_title_text_size"
/>
<Button
android:id=
"@+id/close"
android:layout_width=
"30dp"
android:layout_height=
"30dp"
android:layout_alignParentEnd=
"true"
android:layout_alignParentRight=
"true"
android:layout_centerVertical=
"true"
android:background=
"@drawable/ic_operation_close"
android:contentDescription=
"@string/cont_desc"
/>
</RelativeLayout>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"@color/alert_background"
android:orientation=
"vertical"
>
<GridView
android:id=
"@+id/gallery_grid"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center"
android:drawSelectorOnTop=
"false"
android:horizontalSpacing=
"2dp"
android:numColumns=
"3"
android:stretchMode=
"columnWidth"
android:verticalSpacing=
"2dp"
/>
</LinearLayout>
<!--android:listSelector="?android:attr/selectableItemBackground" -->
</LinearLayout>
\ No newline at end of file
ABVJE_UI_Android/res/layout/item_device_thumbnail.xml
0 → 100644
View file @
f193357b
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"#ffffff"
>
<ImageView
android:id=
"@+id/iv_thumbnail"
android:layout_width=
"match_parent"
android:layout_height=
"150dp"
android:adjustViewBounds=
"true"
android:scaleType=
"centerCrop"
/>
<ImageView
android:id=
"@+id/iv_thumbnail_selected"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerInParent=
"true"
android:src=
"@drawable/check_mark"
/>
</RelativeLayout>
ABVJE_UI_Android/src/com/imagepicker/AsyncTask.java
0 → 100644
View file @
f193357b
/*
* 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.
*/
package
com
.
imagepicker
;
import
android.annotation.TargetApi
;
import
android.os.Handler
;
import
android.os.Message
;
import
android.os.Process
;
import
java.util.ArrayDeque
;
import
java.util.concurrent.BlockingQueue
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.CancellationException
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.Executor
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.FutureTask
;
import
java.util.concurrent.LinkedBlockingQueue
;
import
java.util.concurrent.ThreadFactory
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.TimeoutException
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.concurrent.atomic.AtomicInteger
;
/**
* *************************************
* Copied from JB release framework:
* https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/os/AsyncTask.java
*
* so that threading behavior on all OS versions is the same and we can tweak behavior by using
* executeOnExecutor() if needed.
*
* There are 3 changes in this copy of AsyncTask:
* -pre-HC a single thread executor is used for serial operation
* (Executors.newSingleThreadExecutor) and is the default
* -the default THREAD_POOL_EXECUTOR was changed to use DiscardOldestPolicy
* -a new fixed thread pool called DUAL_THREAD_EXECUTOR was added
* *************************************
*
* <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.</p>
*
* <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link android.os.Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> pacakge such as {@link java.util.concurrent.Executor},
* {@link java.util.concurrent.ThreadPoolExecutor} and {@link java.util.concurrent.FutureTask}.</p>
*
* <p>An asynchronous task is defined by a computation that runs on a background thread and
* whose result is published on the UI thread. An asynchronous task is defined by 3 generic
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using tasks and threads, read the
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and
* Threads</a> developer guide.</p>
* </div>
*
* <h2>Usage</h2>
* <p>AsyncTask must be subclassed to be used. The subclass will override at least
* one method ({@link #doInBackground}), and most often will override a
* second one ({@link #onPostExecute}.)</p>
*
* <p>Here is an example of subclassing:</p>
* <pre class="prettyprint">
* private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
* protected Long doInBackground(URL... urls) {
* int count = urls.length;
* long totalSize = 0;
* for (int i = 0; i < count; i++) {
* totalSize += Downloader.downloadFile(urls[i]);
* publishProgress((int) ((i / (float) count) * 100));
* // Escape early if cancel() is called
* if (isCancelled()) break;
* }
* return totalSize;
* }
*
* protected void onProgressUpdate(Integer... progress) {
* setProgressPercent(progress[0]);
* }
*
* protected void onPostExecute(Long result) {
* showDialog("Downloaded " + result + " bytes");
* }
* }
* </pre>
*
* <p>Once created, a task is executed very simply:</p>
* <pre class="prettyprint">
* new DownloadFilesTask().execute(url1, url2, url3);
* </pre>
*
* <h2>AsyncTask's generic types</h2>
* <p>The three types used by an asynchronous task are the following:</p>
* <ol>
* <li><code>Params</code>, the type of the parameters sent to the task upon
* execution.</li>
* <li><code>Progress</code>, the type of the progress units published during
* the background computation.</li>
* <li><code>Result</code>, the type of the result of the background
* computation.</li>
* </ol>
* <p>Not all types are always used by an asynchronous task. To mark a type as unused,
* simply use the type {@link Void}:</p>
* <pre>
* private class MyTask extends AsyncTask<Void, Void, Void> { ... }
* </pre>
*
* <h2>The 4 steps</h2>
* <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
* <ol>
* <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the task
* is executed. This step is normally used to setup the task, for instance by
* showing a progress bar in the user interface.</li>
* <li>{@link #doInBackground}, invoked on the background thread
* immediately after {@link #onPreExecute()} finishes executing. This step is used
* to perform background computation that can take a long time. The parameters
* of the asynchronous task are passed to this step. The result of the computation must
* be returned by this step and will be passed back to the last step. This step
* can also use {@link #publishProgress} to publish one or more units
* of progress. These values are published on the UI thread, in the
* {@link #onProgressUpdate} step.</li>
* <li>{@link #onProgressUpdate}, invoked on the UI thread after a
* call to {@link #publishProgress}. The timing of the execution is
* undefined. This method is used to display any form of progress in the user
* interface while the background computation is still executing. For instance,
* it can be used to animate a progress bar or show logs in a text field.</li>
* <li>{@link #onPostExecute}, invoked on the UI thread after the background
* computation finishes. The result of the background computation is passed to
* this step as a parameter.</li>
* </ol>
*
* <h2>Cancelling a task</h2>
* <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
* this method will cause subsequent calls to {@link #isCancelled()} to return true.
* After invoking this method, {@link #onCancelled(Object)}, instead of
* {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
* returns. To ensure that a task is cancelled as quickly as possible, you should always
* check the return value of {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
*
* <h2>Threading rules</h2>
* <p>There are a few threading rules that must be followed for this class to
* work properly:</p>
* <ul>
* <li>The AsyncTask class must be loaded on the UI thread. This is done
* automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
* <li>The task instance must be created on the UI thread.</li>
* <li>{@link #execute} must be invoked on the UI thread.</li>
* <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
* {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
* <li>The task can be executed only once (an exception will be thrown if
* a second execution is attempted.)</li>
* </ul>
*
* <h2>Memory observability</h2>
* <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
* operations are safe without explicit synchronizations.</p>
* <ul>
* <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
* in {@link #doInBackground}.
* <li>Set member fields in {@link #doInBackground}, and refer to them in
* {@link #onProgressUpdate} and {@link #onPostExecute}.
* </ul>
*
* <h2>Order of execution</h2>
* <p>When first introduced, AsyncTasks were executed serially on a single background
* thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting with
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
* thread to avoid common application errors caused by parallel execution.</p>
* <p>If you truly want parallel execution, you can invoke
* {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
* {@link #THREAD_POOL_EXECUTOR}.</p>
*/
public
abstract
class
AsyncTask
<
Params
,
Progress
,
Result
>
{
private
static
final
String
LOG_TAG
=
"AsyncTask"
;
private
static
final
int
CORE_POOL_SIZE
=
5
;
private
static
final
int
MAXIMUM_POOL_SIZE
=
128
;
private
static
final
int
KEEP_ALIVE
=
1
;
private
static
final
ThreadFactory
sThreadFactory
=
new
ThreadFactory
()
{
private
final
AtomicInteger
mCount
=
new
AtomicInteger
(
1
);
public
Thread
newThread
(
Runnable
r
)
{
return
new
Thread
(
r
,
"AsyncTask #"
+
mCount
.
getAndIncrement
());
}
};
private
static
final
BlockingQueue
<
Runnable
>
sPoolWorkQueue
=
new
LinkedBlockingQueue
<
Runnable
>(
10
);
/**
* An {@link java.util.concurrent.Executor} that can be used to execute tasks in parallel.
*/
public
static
final
Executor
THREAD_POOL_EXECUTOR
=
new
ThreadPoolExecutor
(
CORE_POOL_SIZE
,
MAXIMUM_POOL_SIZE
,
KEEP_ALIVE
,
TimeUnit
.
SECONDS
,
sPoolWorkQueue
,
sThreadFactory
,
new
ThreadPoolExecutor
.
DiscardOldestPolicy
());
/**
* An {@link java.util.concurrent.Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public
static
final
Executor
SERIAL_EXECUTOR
=
Utils
.
hasHoneycomb
()
?
new
SerialExecutor
()
:
Executors
.
newSingleThreadExecutor
(
sThreadFactory
);
public
static
final
Executor
DUAL_THREAD_EXECUTOR
=
Executors
.
newFixedThreadPool
(
2
,
sThreadFactory
);
private
static
final
int
MESSAGE_POST_RESULT
=
0x1
;
private
static
final
int
MESSAGE_POST_PROGRESS
=
0x2
;
private
static
final
InternalHandler
sHandler
=
new
InternalHandler
();
private
static
volatile
Executor
sDefaultExecutor
=
SERIAL_EXECUTOR
;
private
final
WorkerRunnable
<
Params
,
Result
>
mWorker
;
private
final
FutureTask
<
Result
>
mFuture
;
private
volatile
Status
mStatus
=
Status
.
PENDING
;
private
final
AtomicBoolean
mCancelled
=
new
AtomicBoolean
();
private
final
AtomicBoolean
mTaskInvoked
=
new
AtomicBoolean
();
@TargetApi
(
11
)
private
static
class
SerialExecutor
implements
Executor
{
final
ArrayDeque
<
Runnable
>
mTasks
=
new
ArrayDeque
<
Runnable
>();
Runnable
mActive
;
public
synchronized
void
execute
(
final
Runnable
r
)
{
mTasks
.
offer
(
new
Runnable
()
{
public
void
run
()
{
try
{
r
.
run
();
}
finally
{
scheduleNext
();
}
}
});
if
(
mActive
==
null
)
{
scheduleNext
();
}
}
protected
synchronized
void
scheduleNext
()
{
if
((
mActive
=
mTasks
.
poll
())
!=
null
)
{
THREAD_POOL_EXECUTOR
.
execute
(
mActive
);
}
}
}
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
public
enum
Status
{
/**
* Indicates that the task has not been executed yet.
*/
PENDING
,
/**
* Indicates that the task is running.
*/
RUNNING
,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED
,
}
/** @hide Used to force static handler to be created. */
public
static
void
init
()
{
sHandler
.
getLooper
();
}
/** @hide */
public
static
void
setDefaultExecutor
(
Executor
exec
)
{
sDefaultExecutor
=
exec
;
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public
AsyncTask
()
{
mWorker
=
new
WorkerRunnable
<
Params
,
Result
>()
{
public
Result
call
()
throws
Exception
{
mTaskInvoked
.
set
(
true
);
Process
.
setThreadPriority
(
Process
.
THREAD_PRIORITY_BACKGROUND
);
//noinspection unchecked
return
postResult
(
doInBackground
(
mParams
));
}
};
mFuture
=
new
FutureTask
<
Result
>(
mWorker
)
{
@Override
protected
void
done
()
{
try
{
postResultIfNotInvoked
(
get
());
}
catch
(
InterruptedException
e
)
{
android
.
util
.
Log
.
w
(
LOG_TAG
,
e
);
}
catch
(
ExecutionException
e
)
{
throw
new
RuntimeException
(
"An error occured while executing doInBackground()"
,
e
.
getCause
());
}
catch
(
CancellationException
e
)
{
postResultIfNotInvoked
(
null
);
}
}
};
}
private
void
postResultIfNotInvoked
(
Result
result
)
{
final
boolean
wasTaskInvoked
=
mTaskInvoked
.
get
();
if
(!
wasTaskInvoked
)
{
postResult
(
result
);
}
}
private
Result
postResult
(
Result
result
)
{
@SuppressWarnings
(
"unchecked"
)
Message
message
=
sHandler
.
obtainMessage
(
MESSAGE_POST_RESULT
,
new
AsyncTaskResult
<
Result
>(
this
,
result
));
message
.
sendToTarget
();
return
result
;
}
/**
* Returns the current status of this task.
*
* @return The current status.
*/
public
final
Status
getStatus
()
{
return
mStatus
;
}
/**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
protected
abstract
Result
doInBackground
(
Params
...
params
);
/**
* Runs on the UI thread before {@link #doInBackground}.
*
* @see #onPostExecute
* @see #doInBackground
*/
protected
void
onPreExecute
()
{
}
/**
* <p>Runs on the UI thread after {@link #doInBackground}. The
* specified result is the value returned by {@link #doInBackground}.</p>
*
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param result The result of the operation computed by {@link #doInBackground}.
*
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@SuppressWarnings
({
"UnusedDeclaration"
})
protected
void
onPostExecute
(
Result
result
)
{
}
/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* @param values The values indicating progress.
*
* @see #publishProgress
* @see #doInBackground
*/
@SuppressWarnings
({
"UnusedDeclaration"
})
protected
void
onProgressUpdate
(
Progress
...
values
)
{
}
/**
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* <p>The default implementation simply invokes {@link #onCancelled()} and
* ignores the result. If you write your own implementation, do not call
* <code>super.onCancelled(result)</code>.</p>
*
* @param result The result, if any, computed in
* {@link #doInBackground(Object[])}, can be null
*
* @see #cancel(boolean)
* @see #isCancelled()
*/
@SuppressWarnings
({
"UnusedParameters"
})
protected
void
onCancelled
(
Result
result
)
{
onCancelled
();
}
/**
* <p>Applications should preferably override {@link #onCancelled(Object)}.
* This method is invoked by the default implementation of
* {@link #onCancelled(Object)}.</p>
*
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* @see #onCancelled(Object)
* @see #cancel(boolean)
* @see #isCancelled()
*/
protected
void
onCancelled
()
{
}
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally. If you are calling {@link #cancel(boolean)} on the task,
* the value returned by this method should be checked periodically from
* {@link #doInBackground(Object[])} to end the task as soon as possible.
*
* @return <tt>true</tt> if task was cancelled before it completed
*
* @see #cancel(boolean)
*/
public
final
boolean
isCancelled
()
{
return
mCancelled
.
get
();
}
/**
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method will result in {@link #onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public
final
boolean
cancel
(
boolean
mayInterruptIfRunning
)
{
mCancelled
.
set
(
true
);
return
mFuture
.
cancel
(
mayInterruptIfRunning
);
}
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return The computed result.
*
* @throws java.util.concurrent.CancellationException If the computation was cancelled.
* @throws java.util.concurrent.ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
*/
public
final
Result
get
()
throws
InterruptedException
,
ExecutionException
{
return
mFuture
.
get
();
}
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result.
*
* @param timeout Time to wait before cancelling the operation.
* @param unit The time unit for the timeout.
*
* @return The computed result.
*
* @throws java.util.concurrent.CancellationException If the computation was cancelled.
* @throws java.util.concurrent.ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
* @throws java.util.concurrent.TimeoutException If the wait timed out.
*/
public
final
Result
get
(
long
timeout
,
TimeUnit
unit
)
throws
InterruptedException
,
ExecutionException
,
TimeoutException
{
return
mFuture
.
get
(
timeout
,
unit
);
}
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
* executed on a single thread to avoid common application errors caused
* by parallel execution. If you truly want parallel execution, you can use
* the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
* @see #execute(Runnable)
*/
public
final
AsyncTask
<
Params
,
Progress
,
Result
>
execute
(
Params
...
params
)
{
return
executeOnExecutor
(
sDefaultExecutor
,
params
);
}
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
* allow multiple tasks to run in parallel on a pool of threads managed by
* AsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom
* behavior.
*
* <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
* a thread pool is generally <em>not</em> what one wants, because the order
* of their operation is not defined. For example, if these tasks are used
* to modify any state in common (such as writing a file due to a button click),
* there are no guarantees on the order of the modifications.
* Without careful work it is possible in rare cases for the newer version
* of the data to be over-written by an older one, leading to obscure data
* loss and stability issues. Such changes are best
* executed in serial; to guarantee such work is serialized regardless of
* platform version you can use this function with {@link #SERIAL_EXECUTOR}.
*
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #execute(Object[])
*/
public
final
AsyncTask
<
Params
,
Progress
,
Result
>
executeOnExecutor
(
Executor
exec
,
Params
...
params
)
{
if
(
mStatus
!=
Status
.
PENDING
)
{
switch
(
mStatus
)
{
case
RUNNING:
throw
new
IllegalStateException
(
"Cannot execute task:"
+
" the task is already running."
);
case
FINISHED:
throw
new
IllegalStateException
(
"Cannot execute task:"
+
" the task has already been executed "
+
"(a task can be executed only once)"
);
}
}
mStatus
=
Status
.
RUNNING
;
onPreExecute
();
mWorker
.
mParams
=
params
;
exec
.
execute
(
mFuture
);
return
this
;
}
/**
* Convenience version of {@link #execute(Object...)} for use with
* a simple Runnable object. See {@link #execute(Object[])} for more
* information on the order of execution.
*
* @see #execute(Object[])
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
*/
public
static
void
execute
(
Runnable
runnable
)
{
sDefaultExecutor
.
execute
(
runnable
);
}
/**
* This method can be invoked from {@link #doInBackground} to
* publish updates on the UI thread while the background computation is
* still running. Each call to this method will trigger the execution of
* {@link #onProgressUpdate} on the UI thread.
*
* {@link #onProgressUpdate} will note be called if the task has been
* canceled.
*
* @param values The progress values to update the UI with.
*
* @see #onProgressUpdate
* @see #doInBackground
*/
protected
final
void
publishProgress
(
Progress
...
values
)
{
if
(!
isCancelled
())
{
sHandler
.
obtainMessage
(
MESSAGE_POST_PROGRESS
,
new
AsyncTaskResult
<
Progress
>(
this
,
values
)).
sendToTarget
();
}
}
private
void
finish
(
Result
result
)
{
if
(
isCancelled
())
{
onCancelled
(
result
);
}
else
{
onPostExecute
(
result
);
}
mStatus
=
Status
.
FINISHED
;
}
private
static
class
InternalHandler
extends
Handler
{
@SuppressWarnings
({
"unchecked"
,
"RawUseOfParameterizedType"
})
@Override
public
void
handleMessage
(
Message
msg
)
{
AsyncTaskResult
result
=
(
AsyncTaskResult
)
msg
.
obj
;
switch
(
msg
.
what
)
{
case
MESSAGE_POST_RESULT:
// There is only one result
result
.
mTask
.
finish
(
result
.
mData
[
0
]);
break
;
case
MESSAGE_POST_PROGRESS:
result
.
mTask
.
onProgressUpdate
(
result
.
mData
);
break
;
}
}
}
private
static
abstract
class
WorkerRunnable
<
Params
,
Result
>
implements
Callable
<
Result
>
{
Params
[]
mParams
;
}
@SuppressWarnings
({
"RawUseOfParameterizedType"
})
private
static
class
AsyncTaskResult
<
Data
>
{
final
AsyncTask
mTask
;
final
Data
[]
mData
;
AsyncTaskResult
(
AsyncTask
task
,
Data
...
data
)
{
mTask
=
task
;
mData
=
data
;
}
}
}
\ No newline at end of file
ABVJE_UI_Android/src/com/imagepicker/DiskLruCache.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
imagepicker
;
import
java.io.BufferedInputStream
;
import
java.io.BufferedWriter
;
import
java.io.Closeable
;
import
java.io.EOFException
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.FileNotFoundException
;
import
java.io.FileOutputStream
;
import
java.io.FileWriter
;
import
java.io.FilterOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.OutputStream
;
import
java.io.OutputStreamWriter
;
import
java.io.Reader
;
import
java.io.StringWriter
;
import
java.io.Writer
;
import
java.lang.reflect.Array
;
import
java.nio.charset.Charset
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Iterator
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.LinkedBlockingQueue
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
/**
******************************************************************************
* Taken from the JB source code, can be found in:
* libcore/luni/src/main/java/libcore/io/DiskLruCache.java
* or direct link:
* https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
******************************************************************************
*
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Values are byte
* sequences, accessible as streams or files. Each value must be between {@code
* 0} and {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public
final
class
DiskLruCache
implements
Closeable
{
static
final
String
JOURNAL_FILE
=
"journal"
;
static
final
String
JOURNAL_FILE_TMP
=
"journal.tmp"
;
static
final
String
MAGIC
=
"libcore.io.DiskLruCache"
;
static
final
String
VERSION_1
=
"1"
;
static
final
long
ANY_SEQUENCE_NUMBER
=
-
1
;
private
static
final
String
CLEAN
=
"CLEAN"
;
private
static
final
String
DIRTY
=
"DIRTY"
;
private
static
final
String
REMOVE
=
"REMOVE"
;
private
static
final
String
READ
=
"READ"
;
private
static
final
Charset
UTF_8
=
Charset
.
forName
(
"UTF-8"
);
private
static
final
int
IO_BUFFER_SIZE
=
8
*
1024
;
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private
final
File
directory
;
private
final
File
journalFile
;
private
final
File
journalFileTmp
;
private
final
int
appVersion
;
private
final
long
maxSize
;
private
final
int
valueCount
;
private
long
size
=
0
;
private
Writer
journalWriter
;
private
final
LinkedHashMap
<
String
,
Entry
>
lruEntries
=
new
LinkedHashMap
<
String
,
Entry
>(
0
,
0.75f
,
true
);
private
int
redundantOpCount
;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private
long
nextSequenceNumber
=
0
;
/* From java.util.Arrays */
@SuppressWarnings
(
"unchecked"
)
private
static
<
T
>
T
[]
copyOfRange
(
T
[]
original
,
int
start
,
int
end
)
{
final
int
originalLength
=
original
.
length
;
// For exception priority compatibility.
if
(
start
>
end
)
{
throw
new
IllegalArgumentException
();
}
if
(
start
<
0
||
start
>
originalLength
)
{
throw
new
ArrayIndexOutOfBoundsException
();
}
final
int
resultLength
=
end
-
start
;
final
int
copyLength
=
Math
.
min
(
resultLength
,
originalLength
-
start
);
final
T
[]
result
=
(
T
[])
Array
.
newInstance
(
original
.
getClass
().
getComponentType
(),
resultLength
);
System
.
arraycopy
(
original
,
start
,
result
,
0
,
copyLength
);
return
result
;
}
/**
* Returns the remainder of 'reader' as a string, closing it when done.
*/
public
static
String
readFully
(
Reader
reader
)
throws
IOException
{
try
{
StringWriter
writer
=
new
StringWriter
();
char
[]
buffer
=
new
char
[
1024
];
int
count
;
while
((
count
=
reader
.
read
(
buffer
))
!=
-
1
)
{
writer
.
write
(
buffer
,
0
,
count
);
}
return
writer
.
toString
();
}
finally
{
reader
.
close
();
}
}
/**
* Returns the ASCII characters up to but not including the next "\r\n", or
* "\n".
*
* @throws java.io.EOFException if the stream is exhausted before the next newline
* character.
*/
public
static
String
readAsciiLine
(
InputStream
in
)
throws
IOException
{
// TODO: support UTF-8 here instead
StringBuilder
result
=
new
StringBuilder
(
80
);
while
(
true
)
{
int
c
=
in
.
read
();
if
(
c
==
-
1
)
{
throw
new
EOFException
();
}
else
if
(
c
==
'\n'
)
{
break
;
}
result
.
append
((
char
)
c
);
}
int
length
=
result
.
length
();
if
(
length
>
0
&&
result
.
charAt
(
length
-
1
)
==
'\r'
)
{
result
.
setLength
(
length
-
1
);
}
return
result
.
toString
();
}
/**
* Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
*/
public
static
void
closeQuietly
(
Closeable
closeable
)
{
if
(
closeable
!=
null
)
{
try
{
closeable
.
close
();
}
catch
(
RuntimeException
rethrown
)
{
throw
rethrown
;
}
catch
(
Exception
ignored
)
{
}
}
}
/**
* Recursively delete everything in {@code dir}.
*/
// TODO: this should specify paths as Strings rather than as Files
public
static
void
deleteContents
(
File
dir
)
throws
IOException
{
File
[]
files
=
dir
.
listFiles
();
if
(
files
==
null
)
{
throw
new
IllegalArgumentException
(
"not a directory: "
+
dir
);
}
for
(
File
file
:
files
)
{
if
(
file
.
isDirectory
())
{
deleteContents
(
file
);
}
if
(!
file
.
delete
())
{
throw
new
IOException
(
"failed to delete file: "
+
file
);
}
}
}
/** This cache uses a single background thread to evict entries. */
private
final
ExecutorService
executorService
=
new
ThreadPoolExecutor
(
0
,
1
,
60L
,
TimeUnit
.
SECONDS
,
new
LinkedBlockingQueue
<
Runnable
>());
private
final
Callable
<
Void
>
cleanupCallable
=
new
Callable
<
Void
>()
{
@Override
public
Void
call
()
throws
Exception
{
synchronized
(
DiskLruCache
.
this
)
{
if
(
journalWriter
==
null
)
{
return
null
;
// closed
}
trimToSize
();
if
(
journalRebuildRequired
())
{
rebuildJournal
();
redundantOpCount
=
0
;
}
}
return
null
;
}
};
private
DiskLruCache
(
File
directory
,
int
appVersion
,
int
valueCount
,
long
maxSize
)
{
this
.
directory
=
directory
;
this
.
appVersion
=
appVersion
;
this
.
journalFile
=
new
File
(
directory
,
JOURNAL_FILE
);
this
.
journalFileTmp
=
new
File
(
directory
,
JOURNAL_FILE_TMP
);
this
.
valueCount
=
valueCount
;
this
.
maxSize
=
maxSize
;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param appVersion
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws java.io.IOException if reading or writing the cache directory fails
*/
public
static
DiskLruCache
open
(
File
directory
,
int
appVersion
,
int
valueCount
,
long
maxSize
)
throws
IOException
{
if
(
maxSize
<=
0
)
{
throw
new
IllegalArgumentException
(
"maxSize <= 0"
);
}
if
(
valueCount
<=
0
)
{
throw
new
IllegalArgumentException
(
"valueCount <= 0"
);
}
// prefer to pick up where we left off
DiskLruCache
cache
=
new
DiskLruCache
(
directory
,
appVersion
,
valueCount
,
maxSize
);
if
(
cache
.
journalFile
.
exists
())
{
try
{
cache
.
readJournal
();
cache
.
processJournal
();
cache
.
journalWriter
=
new
BufferedWriter
(
new
FileWriter
(
cache
.
journalFile
,
true
),
IO_BUFFER_SIZE
);
return
cache
;
}
catch
(
IOException
journalIsCorrupt
)
{
// System.logW("DiskLruCache " + directory + " is corrupt: "
// + journalIsCorrupt.getMessage() + ", removing");
cache
.
delete
();
}
}
// create a new empty cache
directory
.
mkdirs
();
cache
=
new
DiskLruCache
(
directory
,
appVersion
,
valueCount
,
maxSize
);
cache
.
rebuildJournal
();
return
cache
;
}
private
void
readJournal
()
throws
IOException
{
InputStream
in
=
new
BufferedInputStream
(
new
FileInputStream
(
journalFile
),
IO_BUFFER_SIZE
);
try
{
String
magic
=
readAsciiLine
(
in
);
String
version
=
readAsciiLine
(
in
);
String
appVersionString
=
readAsciiLine
(
in
);
String
valueCountString
=
readAsciiLine
(
in
);
String
blank
=
readAsciiLine
(
in
);
if
(!
MAGIC
.
equals
(
magic
)
||
!
VERSION_1
.
equals
(
version
)
||
!
Integer
.
toString
(
appVersion
).
equals
(
appVersionString
)
||
!
Integer
.
toString
(
valueCount
).
equals
(
valueCountString
)
||
!
""
.
equals
(
blank
))
{
throw
new
IOException
(
"unexpected journal header: ["
+
magic
+
", "
+
version
+
", "
+
valueCountString
+
", "
+
blank
+
"]"
);
}
while
(
true
)
{
try
{
readJournalLine
(
readAsciiLine
(
in
));
}
catch
(
EOFException
endOfJournal
)
{
break
;
}
}
}
finally
{
closeQuietly
(
in
);
}
}
private
void
readJournalLine
(
String
line
)
throws
IOException
{
String
[]
parts
=
line
.
split
(
" "
);
if
(
parts
.
length
<
2
)
{
throw
new
IOException
(
"unexpected journal line: "
+
line
);
}
String
key
=
parts
[
1
];
if
(
parts
[
0
].
equals
(
REMOVE
)
&&
parts
.
length
==
2
)
{
lruEntries
.
remove
(
key
);
return
;
}
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
)
{
entry
=
new
Entry
(
key
);
lruEntries
.
put
(
key
,
entry
);
}
if
(
parts
[
0
].
equals
(
CLEAN
)
&&
parts
.
length
==
2
+
valueCount
)
{
entry
.
readable
=
true
;
entry
.
currentEditor
=
null
;
entry
.
setLengths
(
copyOfRange
(
parts
,
2
,
parts
.
length
));
}
else
if
(
parts
[
0
].
equals
(
DIRTY
)
&&
parts
.
length
==
2
)
{
entry
.
currentEditor
=
new
Editor
(
entry
);
}
else
if
(
parts
[
0
].
equals
(
READ
)
&&
parts
.
length
==
2
)
{
// this work was already done by calling lruEntries.get()
}
else
{
throw
new
IOException
(
"unexpected journal line: "
+
line
);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private
void
processJournal
()
throws
IOException
{
deleteIfExists
(
journalFileTmp
);
for
(
Iterator
<
Entry
>
i
=
lruEntries
.
values
().
iterator
();
i
.
hasNext
();
)
{
Entry
entry
=
i
.
next
();
if
(
entry
.
currentEditor
==
null
)
{
for
(
int
t
=
0
;
t
<
valueCount
;
t
++)
{
size
+=
entry
.
lengths
[
t
];
}
}
else
{
entry
.
currentEditor
=
null
;
for
(
int
t
=
0
;
t
<
valueCount
;
t
++)
{
deleteIfExists
(
entry
.
getCleanFile
(
t
));
deleteIfExists
(
entry
.
getDirtyFile
(
t
));
}
i
.
remove
();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private
synchronized
void
rebuildJournal
()
throws
IOException
{
if
(
journalWriter
!=
null
)
{
journalWriter
.
close
();
}
Writer
writer
=
new
BufferedWriter
(
new
FileWriter
(
journalFileTmp
),
IO_BUFFER_SIZE
);
writer
.
write
(
MAGIC
);
writer
.
write
(
"\n"
);
writer
.
write
(
VERSION_1
);
writer
.
write
(
"\n"
);
writer
.
write
(
Integer
.
toString
(
appVersion
));
writer
.
write
(
"\n"
);
writer
.
write
(
Integer
.
toString
(
valueCount
));
writer
.
write
(
"\n"
);
writer
.
write
(
"\n"
);
for
(
Entry
entry
:
lruEntries
.
values
())
{
if
(
entry
.
currentEditor
!=
null
)
{
writer
.
write
(
DIRTY
+
' '
+
entry
.
key
+
'\n'
);
}
else
{
writer
.
write
(
CLEAN
+
' '
+
entry
.
key
+
entry
.
getLengths
()
+
'\n'
);
}
}
writer
.
close
();
journalFileTmp
.
renameTo
(
journalFile
);
journalWriter
=
new
BufferedWriter
(
new
FileWriter
(
journalFile
,
true
),
IO_BUFFER_SIZE
);
}
private
static
void
deleteIfExists
(
File
file
)
throws
IOException
{
// try {
// Libcore.os.remove(file.getPath());
// } catch (ErrnoException errnoException) {
// if (errnoException.errno != OsConstants.ENOENT) {
// throw errnoException.rethrowAsIOException();
// }
// }
if
(
file
.
exists
()
&&
!
file
.
delete
())
{
throw
new
IOException
();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public
synchronized
Snapshot
get
(
String
key
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
)
{
return
null
;
}
if
(!
entry
.
readable
)
{
return
null
;
}
/*
* Open all streams eagerly to guarantee that we see a single published
* snapshot. If we opened streams lazily then the streams could come
* from different edits.
*/
InputStream
[]
ins
=
new
InputStream
[
valueCount
];
try
{
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
ins
[
i
]
=
new
FileInputStream
(
entry
.
getCleanFile
(
i
));
}
}
catch
(
FileNotFoundException
e
)
{
// a file must have been deleted manually!
return
null
;
}
redundantOpCount
++;
journalWriter
.
append
(
READ
+
' '
+
key
+
'\n'
);
if
(
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
return
new
Snapshot
(
key
,
entry
.
sequenceNumber
,
ins
);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public
Editor
edit
(
String
key
)
throws
IOException
{
return
edit
(
key
,
ANY_SEQUENCE_NUMBER
);
}
private
synchronized
Editor
edit
(
String
key
,
long
expectedSequenceNumber
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
expectedSequenceNumber
!=
ANY_SEQUENCE_NUMBER
&&
(
entry
==
null
||
entry
.
sequenceNumber
!=
expectedSequenceNumber
))
{
return
null
;
// snapshot is stale
}
if
(
entry
==
null
)
{
entry
=
new
Entry
(
key
);
lruEntries
.
put
(
key
,
entry
);
}
else
if
(
entry
.
currentEditor
!=
null
)
{
return
null
;
// another edit is in progress
}
Editor
editor
=
new
Editor
(
entry
);
entry
.
currentEditor
=
editor
;
// flush the journal before creating files to prevent file leaks
journalWriter
.
write
(
DIRTY
+
' '
+
key
+
'\n'
);
journalWriter
.
flush
();
return
editor
;
}
/**
* Returns the directory where this cache stores its data.
*/
public
File
getDirectory
()
{
return
directory
;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public
long
maxSize
()
{
return
maxSize
;
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public
synchronized
long
size
()
{
return
size
;
}
private
synchronized
void
completeEdit
(
Editor
editor
,
boolean
success
)
throws
IOException
{
Entry
entry
=
editor
.
entry
;
if
(
entry
.
currentEditor
!=
editor
)
{
throw
new
IllegalStateException
();
}
// if this edit is creating the entry for the first time, every index must have a value
if
(
success
&&
!
entry
.
readable
)
{
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
if
(!
entry
.
getDirtyFile
(
i
).
exists
())
{
editor
.
abort
();
throw
new
IllegalStateException
(
"edit didn't create file "
+
i
);
}
}
}
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
File
dirty
=
entry
.
getDirtyFile
(
i
);
if
(
success
)
{
if
(
dirty
.
exists
())
{
File
clean
=
entry
.
getCleanFile
(
i
);
dirty
.
renameTo
(
clean
);
long
oldLength
=
entry
.
lengths
[
i
];
long
newLength
=
clean
.
length
();
entry
.
lengths
[
i
]
=
newLength
;
size
=
size
-
oldLength
+
newLength
;
}
}
else
{
deleteIfExists
(
dirty
);
}
}
redundantOpCount
++;
entry
.
currentEditor
=
null
;
if
(
entry
.
readable
|
success
)
{
entry
.
readable
=
true
;
journalWriter
.
write
(
CLEAN
+
' '
+
entry
.
key
+
entry
.
getLengths
()
+
'\n'
);
if
(
success
)
{
entry
.
sequenceNumber
=
nextSequenceNumber
++;
}
}
else
{
lruEntries
.
remove
(
entry
.
key
);
journalWriter
.
write
(
REMOVE
+
' '
+
entry
.
key
+
'\n'
);
}
if
(
size
>
maxSize
||
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private
boolean
journalRebuildRequired
()
{
final
int
REDUNDANT_OP_COMPACT_THRESHOLD
=
2000
;
return
redundantOpCount
>=
REDUNDANT_OP_COMPACT_THRESHOLD
&&
redundantOpCount
>=
lruEntries
.
size
();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public
synchronized
boolean
remove
(
String
key
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
||
entry
.
currentEditor
!=
null
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
File
file
=
entry
.
getCleanFile
(
i
);
if
(!
file
.
delete
())
{
throw
new
IOException
(
"failed to delete "
+
file
);
}
size
-=
entry
.
lengths
[
i
];
entry
.
lengths
[
i
]
=
0
;
}
redundantOpCount
++;
journalWriter
.
append
(
REMOVE
+
' '
+
key
+
'\n'
);
lruEntries
.
remove
(
key
);
if
(
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
return
true
;
}
/**
* Returns true if this cache has been closed.
*/
public
boolean
isClosed
()
{
return
journalWriter
==
null
;
}
private
void
checkNotClosed
()
{
if
(
journalWriter
==
null
)
{
throw
new
IllegalStateException
(
"cache is closed"
);
}
}
/**
* Force buffered operations to the filesystem.
*/
public
synchronized
void
flush
()
throws
IOException
{
checkNotClosed
();
trimToSize
();
journalWriter
.
flush
();
}
/**
* Closes this cache. Stored values will remain on the filesystem.
*/
public
synchronized
void
close
()
throws
IOException
{
if
(
journalWriter
==
null
)
{
return
;
// already closed
}
for
(
Entry
entry
:
new
ArrayList
<
Entry
>(
lruEntries
.
values
()))
{
if
(
entry
.
currentEditor
!=
null
)
{
entry
.
currentEditor
.
abort
();
}
}
trimToSize
();
journalWriter
.
close
();
journalWriter
=
null
;
}
private
void
trimToSize
()
throws
IOException
{
while
(
size
>
maxSize
)
{
// Map.Entry<String, Entry> toEvict = lruEntries.eldest();
final
Map
.
Entry
<
String
,
Entry
>
toEvict
=
lruEntries
.
entrySet
().
iterator
().
next
();
remove
(
toEvict
.
getKey
());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public
void
delete
()
throws
IOException
{
close
();
deleteContents
(
directory
);
}
private
void
validateKey
(
String
key
)
{
if
(
key
.
contains
(
" "
)
||
key
.
contains
(
"\n"
)
||
key
.
contains
(
"\r"
))
{
throw
new
IllegalArgumentException
(
"keys must not contain spaces or newlines: \""
+
key
+
"\""
);
}
}
private
static
String
inputStreamToString
(
InputStream
in
)
throws
IOException
{
return
readFully
(
new
InputStreamReader
(
in
,
UTF_8
));
}
/**
* A snapshot of the values for an entry.
*/
public
final
class
Snapshot
implements
Closeable
{
private
final
String
key
;
private
final
long
sequenceNumber
;
private
final
InputStream
[]
ins
;
private
Snapshot
(
String
key
,
long
sequenceNumber
,
InputStream
[]
ins
)
{
this
.
key
=
key
;
this
.
sequenceNumber
=
sequenceNumber
;
this
.
ins
=
ins
;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public
Editor
edit
()
throws
IOException
{
return
DiskLruCache
.
this
.
edit
(
key
,
sequenceNumber
);
}
/**
* Returns the unbuffered stream with the value for {@code index}.
*/
public
InputStream
getInputStream
(
int
index
)
{
return
ins
[
index
];
}
/**
* Returns the string value for {@code index}.
*/
public
String
getString
(
int
index
)
throws
IOException
{
return
inputStreamToString
(
getInputStream
(
index
));
}
@Override
public
void
close
()
{
for
(
InputStream
in
:
ins
)
{
closeQuietly
(
in
);
}
}
}
/**
* Edits the values for an entry.
*/
public
final
class
Editor
{
private
final
Entry
entry
;
private
boolean
hasErrors
;
private
Editor
(
Entry
entry
)
{
this
.
entry
=
entry
;
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public
InputStream
newInputStream
(
int
index
)
throws
IOException
{
synchronized
(
DiskLruCache
.
this
)
{
if
(
entry
.
currentEditor
!=
this
)
{
throw
new
IllegalStateException
();
}
if
(!
entry
.
readable
)
{
return
null
;
}
return
new
FileInputStream
(
entry
.
getCleanFile
(
index
));
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public
String
getString
(
int
index
)
throws
IOException
{
InputStream
in
=
newInputStream
(
index
);
return
in
!=
null
?
inputStreamToString
(
in
)
:
null
;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public
OutputStream
newOutputStream
(
int
index
)
throws
IOException
{
synchronized
(
DiskLruCache
.
this
)
{
if
(
entry
.
currentEditor
!=
this
)
{
throw
new
IllegalStateException
();
}
return
new
FaultHidingOutputStream
(
new
FileOutputStream
(
entry
.
getDirtyFile
(
index
)));
}
}
/**
* Sets the value at {@code index} to {@code value}.
*/
public
void
set
(
int
index
,
String
value
)
throws
IOException
{
Writer
writer
=
null
;
try
{
writer
=
new
OutputStreamWriter
(
newOutputStream
(
index
),
UTF_8
);
writer
.
write
(
value
);
}
finally
{
closeQuietly
(
writer
);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public
void
commit
()
throws
IOException
{
if
(
hasErrors
)
{
completeEdit
(
this
,
false
);
remove
(
entry
.
key
);
// the previous entry is stale
}
else
{
completeEdit
(
this
,
true
);
}
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public
void
abort
()
throws
IOException
{
completeEdit
(
this
,
false
);
}
private
class
FaultHidingOutputStream
extends
FilterOutputStream
{
private
FaultHidingOutputStream
(
OutputStream
out
)
{
super
(
out
);
}
@Override
public
void
write
(
int
oneByte
)
{
try
{
out
.
write
(
oneByte
);
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
write
(
byte
[]
buffer
,
int
offset
,
int
length
)
{
try
{
out
.
write
(
buffer
,
offset
,
length
);
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
close
()
{
try
{
out
.
close
();
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
flush
()
{
try
{
out
.
flush
();
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
}
}
private
final
class
Entry
{
private
final
String
key
;
/** Lengths of this entry's files. */
private
final
long
[]
lengths
;
/** True if this entry has ever been published */
private
boolean
readable
;
/** The ongoing edit or null if this entry is not being edited. */
private
Editor
currentEditor
;
/** The sequence number of the most recently committed edit to this entry. */
private
long
sequenceNumber
;
private
Entry
(
String
key
)
{
this
.
key
=
key
;
this
.
lengths
=
new
long
[
valueCount
];
}
public
String
getLengths
()
throws
IOException
{
StringBuilder
result
=
new
StringBuilder
();
for
(
long
size
:
lengths
)
{
result
.
append
(
' '
).
append
(
size
);
}
return
result
.
toString
();
}
/**
* Set lengths using decimal numbers like "10123".
*/
private
void
setLengths
(
String
[]
strings
)
throws
IOException
{
if
(
strings
.
length
!=
valueCount
)
{
throw
invalidLengths
(
strings
);
}
try
{
for
(
int
i
=
0
;
i
<
strings
.
length
;
i
++)
{
lengths
[
i
]
=
Long
.
parseLong
(
strings
[
i
]);
}
}
catch
(
NumberFormatException
e
)
{
throw
invalidLengths
(
strings
);
}
}
private
IOException
invalidLengths
(
String
[]
strings
)
throws
IOException
{
throw
new
IOException
(
"unexpected journal line: "
+
Arrays
.
toString
(
strings
));
}
public
File
getCleanFile
(
int
i
)
{
return
new
File
(
directory
,
key
+
""
+
i
);
}
public
File
getDirtyFile
(
int
i
)
{
return
new
File
(
directory
,
key
+
""
+
i
+
".tmp"
);
}
}
}
ABVJE_UI_Android/src/com/imagepicker/ImageCache.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2012 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
.
imagepicker
;
import
android.annotation.TargetApi
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.graphics.Bitmap.CompressFormat
;
import
android.graphics.Bitmap.Config
;
import
android.graphics.BitmapFactory
;
import
android.graphics.drawable.BitmapDrawable
;
import
android.os.Build.VERSION_CODES
;
import
android.os.Bundle
;
import
android.os.Environment
;
import
android.os.StatFs
;
import
android.support.v4.app.Fragment
;
import
android.support.v4.app.FragmentManager
;
import
android.support.v4.util.LruCache
;
import
android.util.Log
;
import
java.io.File
;
import
java.io.FileDescriptor
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.lang.ref.SoftReference
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.Collections
;
import
java.util.HashSet
;
import
java.util.Iterator
;
import
java.util.Set
;
/**
* This class handles disk and memory caching of bitmaps in conjunction with the
* {@link ImageWorker} class and its subclasses. Use
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to get an instance of this
* class, although usually a cache should be added directly to an {@link ImageWorker} by calling
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
*/
public
class
ImageCache
{
private
static
final
String
TAG
=
"ImageCache"
;
// Default memory cache size in kilobytes
private
static
final
int
DEFAULT_MEM_CACHE_SIZE
=
1024
*
5
;
// 5MB
// Default disk cache size in bytes
private
static
final
int
DEFAULT_DISK_CACHE_SIZE
=
1024
*
1024
*
10
;
// 10MB
// Compression settings when writing images to disk cache
private
static
final
CompressFormat
DEFAULT_COMPRESS_FORMAT
=
CompressFormat
.
JPEG
;
private
static
final
int
DEFAULT_COMPRESS_QUALITY
=
70
;
private
static
final
int
DISK_CACHE_INDEX
=
0
;
// Constants to easily toggle various caches
private
static
final
boolean
DEFAULT_MEM_CACHE_ENABLED
=
true
;
private
static
final
boolean
DEFAULT_DISK_CACHE_ENABLED
=
true
;
private
static
final
boolean
DEFAULT_INIT_DISK_CACHE_ON_CREATE
=
false
;
private
DiskLruCache
mDiskLruCache
;
private
LruCache
<
String
,
BitmapDrawable
>
mMemoryCache
;
private
ImageCacheParams
mCacheParams
;
private
final
Object
mDiskCacheLock
=
new
Object
();
private
boolean
mDiskCacheStarting
=
true
;
private
Set
<
SoftReference
<
Bitmap
>>
mReusableBitmaps
;
/**
* Create a new ImageCache object using the specified parameters. This should not be
* called directly by other classes, instead use
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to fetch an ImageCache
* instance.
*
* @param cacheParams The cache parameters to use to initialize the cache
*/
private
ImageCache
(
ImageCacheParams
cacheParams
)
{
init
(
cacheParams
);
}
/**
* Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the
* ImageCache object across configuration changes such as a change in device orientation.
*
* @param fragmentManager The fragment manager to use when dealing with the retained fragment.
* @param cacheParams The cache parameters to use if the ImageCache needs instantiation.
* @return An existing retained ImageCache object or a new one if one did not exist
*/
public
static
ImageCache
getInstance
(
FragmentManager
fragmentManager
,
ImageCacheParams
cacheParams
)
{
// Search for, or create an instance of the non-UI RetainFragment
final
RetainFragment
mRetainFragment
=
findOrCreateRetainFragment
(
fragmentManager
);
// See if we already have an ImageCache stored in RetainFragment
ImageCache
imageCache
=
(
ImageCache
)
mRetainFragment
.
getObject
();
// No existing ImageCache, create one and store it in RetainFragment
if
(
imageCache
==
null
)
{
imageCache
=
new
ImageCache
(
cacheParams
);
mRetainFragment
.
setObject
(
imageCache
);
}
return
imageCache
;
}
/**
* Initialize the cache, providing all parameters.
*
* @param cacheParams The cache parameters to initialize the cache
*/
private
void
init
(
ImageCacheParams
cacheParams
)
{
mCacheParams
=
cacheParams
;
//BEGIN_INCLUDE(init_memory_cache)
// Set up memory cache
if
(
mCacheParams
.
memoryCacheEnabled
)
{
// If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be
// populated into the inBitmap field of BitmapFactory.Options. Note that the set is
// of SoftReferences which will actually not be very effective due to the garbage
// collector being aggressive clearing Soft/WeakReferences. A better approach
// would be to use a strongly references bitmaps, however this would require some
// balancing of memory usage between this set and the bitmap LruCache. It would also
// require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean
// the size would need to be precise, from KitKat onward the size would just need to
// be the upper bound (due to changes in how inBitmap can re-use bitmaps).
if
(
Utils
.
hasHoneycomb
())
{
mReusableBitmaps
=
Collections
.
synchronizedSet
(
new
HashSet
<
SoftReference
<
Bitmap
>>());
}
mMemoryCache
=
new
LruCache
<
String
,
BitmapDrawable
>(
mCacheParams
.
memCacheSize
)
{
/**
* Notify the removed entry that is no longer being cached
*/
@Override
protected
void
entryRemoved
(
boolean
evicted
,
String
key
,
BitmapDrawable
oldValue
,
BitmapDrawable
newValue
)
{
if
(
RecyclingBitmapDrawable
.
class
.
isInstance
(
oldValue
))
{
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((
RecyclingBitmapDrawable
)
oldValue
).
setIsCached
(
false
);
}
else
{
// The removed entry is a standard BitmapDrawable
if
(
Utils
.
hasHoneycomb
())
{
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later
mReusableBitmaps
.
add
(
new
SoftReference
<
Bitmap
>(
oldValue
.
getBitmap
()));
}
}
}
/**
* Measure item size in kilobytes rather than units which is more practical
* for a bitmap cache
*/
@Override
protected
int
sizeOf
(
String
key
,
BitmapDrawable
value
)
{
final
int
bitmapSize
=
getBitmapSize
(
value
)
/
1024
;
return
bitmapSize
==
0
?
1
:
bitmapSize
;
}
};
}
//END_INCLUDE(init_memory_cache)
// By default the disk cache is not initialized here as it should be initialized
// on a separate thread due to disk access.
if
(
cacheParams
.
initDiskCacheOnCreate
)
{
// Set up disk cache
initDiskCache
();
}
}
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an ImageCache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
*/
public
void
initDiskCache
()
{
// Set up disk cache
synchronized
(
mDiskCacheLock
)
{
if
(
mDiskLruCache
==
null
||
mDiskLruCache
.
isClosed
())
{
File
diskCacheDir
=
mCacheParams
.
diskCacheDir
;
if
(
mCacheParams
.
diskCacheEnabled
&&
diskCacheDir
!=
null
)
{
if
(!
diskCacheDir
.
exists
())
{
diskCacheDir
.
mkdirs
();
}
if
(
getUsableSpace
(
diskCacheDir
)
>
mCacheParams
.
diskCacheSize
)
{
try
{
mDiskLruCache
=
DiskLruCache
.
open
(
diskCacheDir
,
1
,
1
,
mCacheParams
.
diskCacheSize
);
}
catch
(
final
IOException
e
)
{
mCacheParams
.
diskCacheDir
=
null
;
Log
.
e
(
TAG
,
"initDiskCache - "
+
e
);
}
}
}
}
mDiskCacheStarting
=
false
;
mDiskCacheLock
.
notifyAll
();
}
}
/**
* Adds a bitmap to both memory and disk cache.
* @param data Unique identifier for the bitmap to store
* @param value The bitmap drawable to store
*/
public
void
addBitmapToCache
(
String
data
,
BitmapDrawable
value
)
{
//BEGIN_INCLUDE(add_bitmap_to_cache)
if
(
data
==
null
||
value
==
null
)
{
return
;
}
// Add to memory cache
if
(
mMemoryCache
!=
null
)
{
if
(
RecyclingBitmapDrawable
.
class
.
isInstance
(
value
))
{
// The removed entry is a recycling drawable, so notify it
// that it has been added into the memory cache
((
RecyclingBitmapDrawable
)
value
).
setIsCached
(
true
);
}
mMemoryCache
.
put
(
data
,
value
);
}
synchronized
(
mDiskCacheLock
)
{
// Add to disk cache
if
(
mDiskLruCache
!=
null
)
{
final
String
key
=
hashKeyForDisk
(
data
);
OutputStream
out
=
null
;
try
{
DiskLruCache
.
Snapshot
snapshot
=
mDiskLruCache
.
get
(
key
);
if
(
snapshot
==
null
)
{
final
DiskLruCache
.
Editor
editor
=
mDiskLruCache
.
edit
(
key
);
if
(
editor
!=
null
)
{
out
=
editor
.
newOutputStream
(
DISK_CACHE_INDEX
);
value
.
getBitmap
().
compress
(
mCacheParams
.
compressFormat
,
mCacheParams
.
compressQuality
,
out
);
editor
.
commit
();
out
.
close
();
}
}
else
{
snapshot
.
getInputStream
(
DISK_CACHE_INDEX
).
close
();
}
}
catch
(
final
IOException
e
)
{
Log
.
e
(
TAG
,
"addBitmapToCache - "
+
e
);
}
catch
(
Exception
e
)
{
Log
.
e
(
TAG
,
"addBitmapToCache - "
+
e
);
}
finally
{
try
{
if
(
out
!=
null
)
{
out
.
close
();
}
}
catch
(
IOException
e
)
{}
}
}
}
//END_INCLUDE(add_bitmap_to_cache)
}
/**
* Get from memory cache.
*
* @param data Unique identifier for which item to get
* @return The bitmap drawable if found in cache, null otherwise
*/
public
BitmapDrawable
getBitmapFromMemCache
(
String
data
)
{
//BEGIN_INCLUDE(get_bitmap_from_mem_cache)
BitmapDrawable
memValue
=
null
;
if
(
mMemoryCache
!=
null
)
{
memValue
=
mMemoryCache
.
get
(
data
);
}
return
memValue
;
//END_INCLUDE(get_bitmap_from_mem_cache)
}
/**
* Get from disk cache.
*
* @param data Unique identifier for which item to get
* @return The bitmap if found in cache, null otherwise
*/
public
Bitmap
getBitmapFromDiskCache
(
String
data
)
{
//BEGIN_INCLUDE(get_bitmap_from_disk_cache)
final
String
key
=
hashKeyForDisk
(
data
);
Bitmap
bitmap
=
null
;
synchronized
(
mDiskCacheLock
)
{
while
(
mDiskCacheStarting
)
{
try
{
mDiskCacheLock
.
wait
();
}
catch
(
InterruptedException
e
)
{}
}
if
(
mDiskLruCache
!=
null
)
{
InputStream
inputStream
=
null
;
try
{
final
DiskLruCache
.
Snapshot
snapshot
=
mDiskLruCache
.
get
(
key
);
if
(
snapshot
!=
null
)
{
inputStream
=
snapshot
.
getInputStream
(
DISK_CACHE_INDEX
);
if
(
inputStream
!=
null
)
{
FileDescriptor
fd
=
((
FileInputStream
)
inputStream
).
getFD
();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap
=
ImageResizer
.
decodeSampledBitmapFromDescriptor
(
fd
,
Integer
.
MAX_VALUE
,
Integer
.
MAX_VALUE
,
this
);
}
}
}
catch
(
final
IOException
e
)
{
Log
.
e
(
TAG
,
"getBitmapFromDiskCache - "
+
e
);
}
finally
{
try
{
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
}
}
catch
(
IOException
e
)
{}
}
}
return
bitmap
;
}
//END_INCLUDE(get_bitmap_from_disk_cache)
}
/**
* @param options - BitmapFactory.Options with out* options populated
* @return Bitmap that case be used for inBitmap
*/
protected
Bitmap
getBitmapFromReusableSet
(
BitmapFactory
.
Options
options
)
{
//BEGIN_INCLUDE(get_bitmap_from_reusable_set)
Bitmap
bitmap
=
null
;
if
(
mReusableBitmaps
!=
null
&&
!
mReusableBitmaps
.
isEmpty
())
{
synchronized
(
mReusableBitmaps
)
{
final
Iterator
<
SoftReference
<
Bitmap
>>
iterator
=
mReusableBitmaps
.
iterator
();
Bitmap
item
;
while
(
iterator
.
hasNext
())
{
item
=
iterator
.
next
().
get
();
if
(
null
!=
item
&&
item
.
isMutable
())
{
// Check to see it the item can be used for inBitmap
if
(
canUseForInBitmap
(
item
,
options
))
{
bitmap
=
item
;
// Remove from reusable set so it can't be used again
iterator
.
remove
();
break
;
}
}
else
{
// Remove from the set if the reference has been cleared.
iterator
.
remove
();
}
}
}
}
return
bitmap
;
//END_INCLUDE(get_bitmap_from_reusable_set)
}
/**
* Clears both the memory and disk cache associated with this ImageCache object. Note that
* this includes disk access so this should not be executed on the main/UI thread.
*/
public
void
clearCache
()
{
if
(
mMemoryCache
!=
null
)
{
mMemoryCache
.
evictAll
();
}
synchronized
(
mDiskCacheLock
)
{
mDiskCacheStarting
=
true
;
if
(
mDiskLruCache
!=
null
&&
!
mDiskLruCache
.
isClosed
())
{
try
{
mDiskLruCache
.
delete
();
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"clearCache - "
+
e
);
}
mDiskLruCache
=
null
;
initDiskCache
();
}
}
}
/**
* Flushes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public
void
flush
()
{
synchronized
(
mDiskCacheLock
)
{
if
(
mDiskLruCache
!=
null
)
{
try
{
mDiskLruCache
.
flush
();
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"flush - "
+
e
);
}
}
}
}
/**
* Closes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public
void
close
()
{
synchronized
(
mDiskCacheLock
)
{
if
(
mDiskLruCache
!=
null
)
{
try
{
if
(!
mDiskLruCache
.
isClosed
())
{
mDiskLruCache
.
close
();
mDiskLruCache
=
null
;
}
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"close - "
+
e
);
}
}
}
}
/**
* A holder class that contains cache parameters.
*/
public
static
class
ImageCacheParams
{
public
int
memCacheSize
=
DEFAULT_MEM_CACHE_SIZE
;
public
int
diskCacheSize
=
DEFAULT_DISK_CACHE_SIZE
;
public
File
diskCacheDir
;
public
CompressFormat
compressFormat
=
DEFAULT_COMPRESS_FORMAT
;
public
int
compressQuality
=
DEFAULT_COMPRESS_QUALITY
;
public
boolean
memoryCacheEnabled
=
DEFAULT_MEM_CACHE_ENABLED
;
public
boolean
diskCacheEnabled
=
DEFAULT_DISK_CACHE_ENABLED
;
public
boolean
initDiskCacheOnCreate
=
DEFAULT_INIT_DISK_CACHE_ON_CREATE
;
/**
* Create a set of image cache parameters that can be provided to
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} or
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
* @param context A context to use.
* @param diskCacheDirectoryName A unique subdirectory name that will be appended to the
* application cache directory. Usually "cache" or "images"
* is sufficient.
*/
public
ImageCacheParams
(
Context
context
,
String
diskCacheDirectoryName
)
{
diskCacheDir
=
getDiskCacheDir
(
context
,
diskCacheDirectoryName
);
}
/**
* Sets the memory cache size based on a percentage of the max available VM memory.
* Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
* memory. Throws {@link IllegalArgumentException} if percent is < 0.01 or > .8.
* memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
* to construct a LruCache which takes an int in its constructor.
*
* This value should be chosen carefully based on a number of factors
* Refer to the corresponding Android Training class for more discussion:
* http://developer.android.com/training/displaying-bitmaps/
*
* @param percent Percent of available app memory to use to size memory cache
*/
public
void
setMemCacheSizePercent
(
float
percent
)
{
if
(
percent
<
0.01f
||
percent
>
0.8f
)
{
throw
new
IllegalArgumentException
(
"setMemCacheSizePercent - percent must be "
+
"between 0.01 and 0.8 (inclusive)"
);
}
memCacheSize
=
Math
.
round
(
percent
*
Runtime
.
getRuntime
().
maxMemory
()
/
1024
);
}
}
/**
* @param candidate - Bitmap to check
* @param targetOptions - Options that have the out* value populated
* @return true if <code>candidate</code> can be used for inBitmap re-use with
* <code>targetOptions</code>
*/
@TargetApi
(
VERSION_CODES
.
KITKAT
)
private
static
boolean
canUseForInBitmap
(
Bitmap
candidate
,
BitmapFactory
.
Options
targetOptions
)
{
//BEGIN_INCLUDE(can_use_for_inbitmap)
if
(!
Utils
.
hasKitKat
())
{
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return
candidate
.
getWidth
()
==
targetOptions
.
outWidth
&&
candidate
.
getHeight
()
==
targetOptions
.
outHeight
&&
targetOptions
.
inSampleSize
==
1
;
}
// From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
// is smaller than the reusable bitmap candidate allocation byte count.
int
width
=
targetOptions
.
outWidth
/
targetOptions
.
inSampleSize
;
int
height
=
targetOptions
.
outHeight
/
targetOptions
.
inSampleSize
;
int
byteCount
=
width
*
height
*
getBytesPerPixel
(
candidate
.
getConfig
());
return
byteCount
<=
candidate
.
getAllocationByteCount
();
//END_INCLUDE(can_use_for_inbitmap)
}
/**
* Return the byte usage per pixel of a bitmap based on its configuration.
* @param config The bitmap configuration.
* @return The byte usage per pixel.
*/
private
static
int
getBytesPerPixel
(
Config
config
)
{
if
(
config
==
Config
.
ARGB_8888
)
{
return
4
;
}
else
if
(
config
==
Config
.
RGB_565
)
{
return
2
;
}
else
if
(
config
==
Config
.
ARGB_4444
)
{
return
2
;
}
else
if
(
config
==
Config
.
ALPHA_8
)
{
return
1
;
}
return
1
;
}
/**
* Get a usable cache directory (external if available, internal otherwise).
*
* @param context The context to use
* @param uniqueName A unique directory name to append to the cache dir
* @return The cache dir
*/
public
static
File
getDiskCacheDir
(
Context
context
,
String
uniqueName
)
{
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final
String
cachePath
=
Environment
.
MEDIA_MOUNTED
.
equals
(
Environment
.
getExternalStorageState
())
||
!
isExternalStorageRemovable
()
?
getExternalCacheDir
(
context
).
getPath
()
:
context
.
getCacheDir
().
getPath
();
return
new
File
(
cachePath
+
File
.
separator
+
uniqueName
);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a
* disk filename.
*/
public
static
String
hashKeyForDisk
(
String
key
)
{
String
cacheKey
;
try
{
final
MessageDigest
mDigest
=
MessageDigest
.
getInstance
(
"MD5"
);
mDigest
.
update
(
key
.
getBytes
());
cacheKey
=
bytesToHexString
(
mDigest
.
digest
());
}
catch
(
NoSuchAlgorithmException
e
)
{
cacheKey
=
String
.
valueOf
(
key
.
hashCode
());
}
return
cacheKey
;
}
private
static
String
bytesToHexString
(
byte
[]
bytes
)
{
// http://stackoverflow.com/questions/332079
StringBuilder
sb
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
bytes
.
length
;
i
++)
{
String
hex
=
Integer
.
toHexString
(
0xFF
&
bytes
[
i
]);
if
(
hex
.
length
()
==
1
)
{
sb
.
append
(
'0'
);
}
sb
.
append
(
hex
);
}
return
sb
.
toString
();
}
/**
* Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat)
* onward this returns the allocated memory size of the bitmap which can be larger than the
* actual bitmap data byte count (in the case it was re-used).
*
* @param value
* @return size in bytes
*/
@TargetApi
(
VERSION_CODES
.
KITKAT
)
public
static
int
getBitmapSize
(
BitmapDrawable
value
)
{
Bitmap
bitmap
=
value
.
getBitmap
();
// From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
// larger than bitmap byte count.
if
(
Utils
.
hasKitKat
())
{
return
bitmap
.
getAllocationByteCount
();
}
if
(
Utils
.
hasHoneycombMR1
())
{
return
bitmap
.
getByteCount
();
}
// Pre HC-MR1
return
bitmap
.
getRowBytes
()
*
bitmap
.
getHeight
();
}
/**
* Check if external storage is built-in or removable.
*
* @return True if external storage is removable (like an SD card), false
* otherwise.
*/
@TargetApi
(
VERSION_CODES
.
GINGERBREAD
)
public
static
boolean
isExternalStorageRemovable
()
{
if
(
Utils
.
hasGingerbread
())
{
return
Environment
.
isExternalStorageRemovable
();
}
return
true
;
}
/**
* Get the external app cache directory.
*
* @param context The context to use
* @return The external cache dir
*/
@TargetApi
(
VERSION_CODES
.
FROYO
)
public
static
File
getExternalCacheDir
(
Context
context
)
{
if
(
Utils
.
hasFroyo
())
{
return
context
.
getExternalCacheDir
();
}
// Before Froyo we need to construct the external cache dir ourselves
final
String
cacheDir
=
"/Android/data/"
+
context
.
getPackageName
()
+
"/cache/"
;
return
new
File
(
Environment
.
getExternalStorageDirectory
().
getPath
()
+
cacheDir
);
}
/**
* Check how much usable space is available at a given path.
*
* @param path The path to check
* @return The space available in bytes
*/
@TargetApi
(
VERSION_CODES
.
GINGERBREAD
)
public
static
long
getUsableSpace
(
File
path
)
{
if
(
Utils
.
hasGingerbread
())
{
return
path
.
getUsableSpace
();
}
final
StatFs
stats
=
new
StatFs
(
path
.
getPath
());
return
(
long
)
stats
.
getBlockSize
()
*
(
long
)
stats
.
getAvailableBlocks
();
}
/**
* Locate an existing instance of this Fragment or if not found, create and
* add it using FragmentManager.
*
* @param fm The FragmentManager manager to use.
* @return The existing instance of the Fragment or the new instance if just
* created.
*/
private
static
RetainFragment
findOrCreateRetainFragment
(
FragmentManager
fm
)
{
//BEGIN_INCLUDE(find_create_retain_fragment)
// Check to see if we have retained the worker fragment.
RetainFragment
mRetainFragment
=
(
RetainFragment
)
fm
.
findFragmentByTag
(
TAG
);
// If not retained (or first time running), we need to create and add it.
if
(
mRetainFragment
==
null
)
{
mRetainFragment
=
new
RetainFragment
();
fm
.
beginTransaction
().
add
(
mRetainFragment
,
TAG
).
commitAllowingStateLoss
();
}
return
mRetainFragment
;
//END_INCLUDE(find_create_retain_fragment)
}
/**
* A simple non-UI Fragment that stores a single Object and is retained over configuration
* changes. It will be used to retain the ImageCache object.
*/
public
static
class
RetainFragment
extends
Fragment
{
private
Object
mObject
;
/**
* Empty constructor as per the Fragment documentation
*/
public
RetainFragment
()
{}
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
// Make sure this Fragment is retained over a configuration change
setRetainInstance
(
true
);
}
/**
* Store a single object in this Fragment.
*
* @param object The object to store
*/
public
void
setObject
(
Object
object
)
{
mObject
=
object
;
}
/**
* Get the stored object.
*
* @return The stored object
*/
public
Object
getObject
()
{
return
mObject
;
}
}
}
ABVJE_UI_Android/src/com/imagepicker/ImageFetcher.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2012 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
.
imagepicker
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.net.ConnectivityManager
;
import
android.net.NetworkInfo
;
import
android.os.Build
;
import
android.util.Log
;
import
java.io.BufferedInputStream
;
import
java.io.BufferedOutputStream
;
import
java.io.File
;
import
java.io.FileDescriptor
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.net.HttpURLConnection
;
import
java.net.URL
;
/**
* A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
*/
public
class
ImageFetcher
extends
ImageResizer
{
private
static
final
String
TAG
=
"ImageFetcher"
;
private
static
final
int
HTTP_CACHE_SIZE
=
10
*
1024
*
1024
;
// 10MB
private
static
final
String
HTTP_CACHE_DIR
=
"http"
;
private
static
final
int
IO_BUFFER_SIZE
=
8
*
1024
;
private
DiskLruCache
mHttpDiskCache
;
private
File
mHttpCacheDir
;
private
boolean
mHttpDiskCacheStarting
=
true
;
private
final
Object
mHttpDiskCacheLock
=
new
Object
();
private
static
final
int
DISK_CACHE_INDEX
=
0
;
/**
* Initialize providing a target image width and height for the processing images.
*
* @param context
* @param imageWidth
* @param imageHeight
*/
public
ImageFetcher
(
Context
context
,
int
imageWidth
,
int
imageHeight
)
{
super
(
context
,
imageWidth
,
imageHeight
);
init
(
context
);
}
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageSize
*/
public
ImageFetcher
(
Context
context
,
int
imageSize
)
{
super
(
context
,
imageSize
);
init
(
context
);
}
private
void
init
(
Context
context
)
{
checkConnection
(
context
);
mHttpCacheDir
=
ImageCache
.
getDiskCacheDir
(
context
,
HTTP_CACHE_DIR
);
}
@Override
protected
void
initDiskCacheInternal
()
{
super
.
initDiskCacheInternal
();
initHttpDiskCache
();
}
private
void
initHttpDiskCache
()
{
if
(!
mHttpCacheDir
.
exists
())
{
mHttpCacheDir
.
mkdirs
();
}
synchronized
(
mHttpDiskCacheLock
)
{
if
(
ImageCache
.
getUsableSpace
(
mHttpCacheDir
)
>
HTTP_CACHE_SIZE
)
{
try
{
mHttpDiskCache
=
DiskLruCache
.
open
(
mHttpCacheDir
,
1
,
1
,
HTTP_CACHE_SIZE
);
}
catch
(
IOException
e
)
{
mHttpDiskCache
=
null
;
}
}
mHttpDiskCacheStarting
=
false
;
mHttpDiskCacheLock
.
notifyAll
();
}
}
@Override
protected
void
clearCacheInternal
()
{
super
.
clearCacheInternal
();
synchronized
(
mHttpDiskCacheLock
)
{
if
(
mHttpDiskCache
!=
null
&&
!
mHttpDiskCache
.
isClosed
())
{
try
{
mHttpDiskCache
.
delete
();
Log
.
d
(
TAG
,
"HTTP cache cleared"
);
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"clearCacheInternal - "
+
e
);
}
mHttpDiskCache
=
null
;
mHttpDiskCacheStarting
=
true
;
initHttpDiskCache
();
}
}
}
@Override
protected
void
flushCacheInternal
()
{
super
.
flushCacheInternal
();
synchronized
(
mHttpDiskCacheLock
)
{
if
(
mHttpDiskCache
!=
null
)
{
try
{
mHttpDiskCache
.
flush
();
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"flush - "
+
e
);
}
}
}
}
@Override
protected
void
closeCacheInternal
()
{
super
.
closeCacheInternal
();
synchronized
(
mHttpDiskCacheLock
)
{
if
(
mHttpDiskCache
!=
null
)
{
try
{
if
(!
mHttpDiskCache
.
isClosed
())
{
mHttpDiskCache
.
close
();
mHttpDiskCache
=
null
;
}
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"closeCacheInternal - "
+
e
);
}
}
}
}
/**
* Simple network connection check.
*
* @param context
*/
private
void
checkConnection
(
Context
context
)
{
final
ConnectivityManager
cm
=
(
ConnectivityManager
)
context
.
getSystemService
(
Context
.
CONNECTIVITY_SERVICE
);
final
NetworkInfo
networkInfo
=
cm
.
getActiveNetworkInfo
();
if
(
networkInfo
==
null
||
!
networkInfo
.
isConnectedOrConnecting
())
{
Log
.
e
(
TAG
,
"checkConnection - no connection found"
);
}
}
/**
* The main process method, which will be called by the ImageWorker in the AsyncTask background
* thread.
*
* @param data The data to load the bitmap, in this case, a regular http URL
* @return The downloaded and resized bitmap
*/
private
Bitmap
processBitmap
(
String
data
)
{
final
String
key
=
ImageCache
.
hashKeyForDisk
(
data
);
FileDescriptor
fileDescriptor
=
null
;
FileInputStream
fileInputStream
=
null
;
DiskLruCache
.
Snapshot
snapshot
;
synchronized
(
mHttpDiskCacheLock
)
{
// Wait for disk cache to initialize
while
(
mHttpDiskCacheStarting
)
{
try
{
mHttpDiskCacheLock
.
wait
();
}
catch
(
InterruptedException
e
)
{}
}
if
(
mHttpDiskCache
!=
null
)
{
try
{
snapshot
=
mHttpDiskCache
.
get
(
key
);
if
(
snapshot
==
null
)
{
DiskLruCache
.
Editor
editor
=
mHttpDiskCache
.
edit
(
key
);
if
(
editor
!=
null
)
{
if
(
downloadUrlToStream
(
data
,
editor
.
newOutputStream
(
DISK_CACHE_INDEX
)))
{
editor
.
commit
();
}
else
{
editor
.
abort
();
}
}
snapshot
=
mHttpDiskCache
.
get
(
key
);
}
if
(
snapshot
!=
null
)
{
fileInputStream
=
(
FileInputStream
)
snapshot
.
getInputStream
(
DISK_CACHE_INDEX
);
fileDescriptor
=
fileInputStream
.
getFD
();
}
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"processBitmap - "
+
e
);
}
catch
(
IllegalStateException
e
)
{
Log
.
e
(
TAG
,
"processBitmap - "
+
e
);
}
finally
{
if
(
fileDescriptor
==
null
&&
fileInputStream
!=
null
)
{
try
{
fileInputStream
.
close
();
}
catch
(
IOException
e
)
{}
}
}
}
}
Bitmap
bitmap
=
null
;
if
(
fileDescriptor
!=
null
)
{
bitmap
=
decodeSampledBitmapFromDescriptor
(
fileDescriptor
,
mImageWidth
,
mImageHeight
,
getImageCache
());
}
if
(
fileInputStream
!=
null
)
{
try
{
fileInputStream
.
close
();
}
catch
(
IOException
e
)
{}
}
return
bitmap
;
}
@Override
protected
Bitmap
processBitmap
(
Object
data
)
{
return
processBitmap
(
String
.
valueOf
(
data
));
}
/**
* Download a bitmap from a URL and write the content to an output stream.
*
* @param urlString The URL to fetch
* @return true if successful, false otherwise
*/
public
boolean
downloadUrlToStream
(
String
urlString
,
OutputStream
outputStream
)
{
disableConnectionReuseIfNecessary
();
HttpURLConnection
urlConnection
=
null
;
BufferedOutputStream
out
=
null
;
BufferedInputStream
in
=
null
;
try
{
final
URL
url
=
new
URL
(
urlString
);
urlConnection
=
(
HttpURLConnection
)
url
.
openConnection
();
in
=
new
BufferedInputStream
(
urlConnection
.
getInputStream
(),
IO_BUFFER_SIZE
);
out
=
new
BufferedOutputStream
(
outputStream
,
IO_BUFFER_SIZE
);
int
b
;
while
((
b
=
in
.
read
())
!=
-
1
)
{
out
.
write
(
b
);
}
return
true
;
}
catch
(
final
IOException
e
)
{
Log
.
e
(
TAG
,
"Error in downloadBitmap - "
+
e
);
}
finally
{
if
(
urlConnection
!=
null
)
{
urlConnection
.
disconnect
();
}
try
{
if
(
out
!=
null
)
{
out
.
close
();
}
if
(
in
!=
null
)
{
in
.
close
();
}
}
catch
(
final
IOException
e
)
{}
}
return
false
;
}
/**
* Workaround for bug pre-Froyo, see here for more info:
* http://android-developers.blogspot.com/2011/09/androids-http-clients.html
*/
public
static
void
disableConnectionReuseIfNecessary
()
{
// HTTP connection reuse which was buggy pre-froyo
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
FROYO
)
{
System
.
setProperty
(
"http.keepAlive"
,
"false"
);
}
}
}
ABVJE_UI_Android/src/com/imagepicker/ImageInternalFetcher.java
0 → 100644
View file @
f193357b
package
com
.
imagepicker
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.net.Uri
;
/**
* Created by Gil on 07/06/2014.
*/
public
class
ImageInternalFetcher
extends
ImageResizer
{
Context
mContext
;
public
ImageInternalFetcher
(
Context
context
,
int
imageWidth
,
int
imageHeight
)
{
super
(
context
,
imageWidth
,
imageHeight
);
init
(
context
);
}
public
ImageInternalFetcher
(
Context
context
,
int
imageSize
)
{
super
(
context
,
imageSize
);
init
(
context
);
}
private
void
init
(
Context
context
){
mContext
=
context
;
}
protected
Bitmap
processBitmap
(
Uri
uri
){
return
decodeSampledBitmapFromFile
(
uri
.
getPath
(),
mImageWidth
,
mImageHeight
,
getImageCache
());
}
@Override
protected
Bitmap
processBitmap
(
Object
data
)
{
return
processBitmap
((
Uri
)
data
);
}
}
ABVJE_UI_Android/src/com/imagepicker/ImageResizer.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2012 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
.
imagepicker
;
import
android.annotation.TargetApi
;
import
android.content.Context
;
import
android.content.res.Resources
;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
import
android.net.Uri
;
import
android.os.Build
;
import
java.io.FileDescriptor
;
import
java.io.FileNotFoundException
;
/**
* A simple subclass of {@link ImageWorker} that resizes images from resources given a target width
* and height. Useful for when the input images might be too large to simply load directly into
* memory.
*/
public
class
ImageResizer
extends
ImageWorker
{
private
static
final
String
TAG
=
"ImageResizer"
;
protected
int
mImageWidth
;
protected
int
mImageHeight
;
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageWidth
* @param imageHeight
*/
public
ImageResizer
(
Context
context
,
int
imageWidth
,
int
imageHeight
)
{
super
(
context
);
setImageSize
(
imageWidth
,
imageHeight
);
}
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageSize
*/
public
ImageResizer
(
Context
context
,
int
imageSize
)
{
super
(
context
);
setImageSize
(
imageSize
);
}
/**
* Set the target image width and height.
*
* @param width
* @param height
*/
public
void
setImageSize
(
int
width
,
int
height
)
{
mImageWidth
=
width
;
mImageHeight
=
height
;
}
/**
* Set the target image size (width and height will be the same).
*
* @param size
*/
public
void
setImageSize
(
int
size
)
{
setImageSize
(
size
,
size
);
}
/**
* The main processing method. This happens in a background task. In this case we are just
* sampling down the bitmap and returning it from a resource.
*
* @param resId
* @return
*/
private
Bitmap
processBitmap
(
int
resId
)
{
return
decodeSampledBitmapFromResource
(
mResources
,
resId
,
mImageWidth
,
mImageHeight
,
getImageCache
());
}
@Override
protected
Bitmap
processBitmap
(
Object
data
)
{
return
processBitmap
(
Integer
.
parseInt
(
String
.
valueOf
(
data
)));
}
/**
* Decode and sample down a bitmap from resources to the requested width and height.
*
* @param res The resources object containing the image data
* @param resId The resource id of the image data
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public
static
Bitmap
decodeSampledBitmapFromResource
(
Resources
res
,
int
resId
,
int
reqWidth
,
int
reqHeight
,
ImageCache
cache
)
{
// BEGIN_INCLUDE (read_bitmap_dimensions)
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
options
.
inJustDecodeBounds
=
true
;
BitmapFactory
.
decodeResource
(
res
,
resId
,
options
);
// Calculate inSampleSize
options
.
inSampleSize
=
calculateInSampleSize
(
options
,
reqWidth
,
reqHeight
);
// END_INCLUDE (read_bitmap_dimensions)
// If we're running on Honeycomb or newer, try to use inBitmap
if
(
Utils
.
hasHoneycomb
())
{
addInBitmapOptions
(
options
,
cache
);
}
// Decode bitmap with inSampleSize set
options
.
inJustDecodeBounds
=
false
;
return
BitmapFactory
.
decodeResource
(
res
,
resId
,
options
);
}
/**
* Decode and sample down a bitmap from a file to the requested width and height.
*
* @param filename The full path of the file to decode
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public
static
Bitmap
decodeSampledBitmapFromFile
(
String
filename
,
int
reqWidth
,
int
reqHeight
,
ImageCache
cache
)
{
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
options
.
inJustDecodeBounds
=
true
;
BitmapFactory
.
decodeFile
(
filename
,
options
);
// Calculate inSampleSize
options
.
inSampleSize
=
calculateInSampleSize
(
options
,
reqWidth
,
reqHeight
);
// If we're running on Honeycomb or newer, try to use inBitmap
if
(
Utils
.
hasHoneycomb
())
{
addInBitmapOptions
(
options
,
cache
);
}
// Decode bitmap with inSampleSize set
options
.
inJustDecodeBounds
=
false
;
return
BitmapFactory
.
decodeFile
(
filename
,
options
);
}
/**
* Decode and sample down a bitmap from a file to the requested width and height.
*
* @param filename The full path of the file to decode
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public
static
Bitmap
decodeSampledBitmapFromUri
(
Context
context
,
Uri
fileuri
,
int
reqWidth
,
int
reqHeight
,
ImageCache
cache
)
{
Bitmap
bm
=
null
;
try
{
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
options
.
inJustDecodeBounds
=
true
;
BitmapFactory
.
decodeStream
(
context
.
getContentResolver
().
openInputStream
(
fileuri
),
null
,
options
);
// Calculate inSampleSize
options
.
inSampleSize
=
calculateInSampleSize
(
options
,
reqWidth
,
reqHeight
);
// Decode bitmap with inSampleSize set
options
.
inJustDecodeBounds
=
false
;
bm
=
BitmapFactory
.
decodeStream
(
context
.
getContentResolver
().
openInputStream
(
fileuri
),
null
,
options
);
}
catch
(
FileNotFoundException
e
)
{
e
.
printStackTrace
();
}
return
bm
;
}
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public
static
Bitmap
decodeSampledBitmapFromDescriptor
(
FileDescriptor
fileDescriptor
,
int
reqWidth
,
int
reqHeight
,
ImageCache
cache
)
{
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
options
.
inJustDecodeBounds
=
true
;
BitmapFactory
.
decodeFileDescriptor
(
fileDescriptor
,
null
,
options
);
// Calculate inSampleSize
options
.
inSampleSize
=
calculateInSampleSize
(
options
,
reqWidth
,
reqHeight
);
// Decode bitmap with inSampleSize set
options
.
inJustDecodeBounds
=
false
;
// If we're running on Honeycomb or newer, try to use inBitmap
if
(
Utils
.
hasHoneycomb
())
{
addInBitmapOptions
(
options
,
cache
);
}
return
BitmapFactory
.
decodeFileDescriptor
(
fileDescriptor
,
null
,
options
);
}
@TargetApi
(
Build
.
VERSION_CODES
.
HONEYCOMB
)
private
static
void
addInBitmapOptions
(
BitmapFactory
.
Options
options
,
ImageCache
cache
)
{
//BEGIN_INCLUDE(add_bitmap_options)
// inBitmap only works with mutable bitmaps so force the decoder to
// return mutable bitmaps.
options
.
inMutable
=
true
;
if
(
cache
!=
null
)
{
// Try and find a bitmap to use for inBitmap
Bitmap
inBitmap
=
cache
.
getBitmapFromReusableSet
(
options
);
if
(
inBitmap
!=
null
)
{
options
.
inBitmap
=
inBitmap
;
}
}
//END_INCLUDE(add_bitmap_options)
}
/**
* Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
* having a width and height equal to or larger than the requested width and height.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
public
static
int
calculateInSampleSize
(
BitmapFactory
.
Options
options
,
int
reqWidth
,
int
reqHeight
)
{
// BEGIN_INCLUDE (calculate_sample_size)
// Raw height and width of image
final
int
height
=
options
.
outHeight
;
final
int
width
=
options
.
outWidth
;
int
inSampleSize
=
1
;
if
(
height
>
reqHeight
||
width
>
reqWidth
)
{
final
int
halfHeight
=
height
/
2
;
final
int
halfWidth
=
width
/
2
;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while
((
halfHeight
/
inSampleSize
)
>
reqHeight
&&
(
halfWidth
/
inSampleSize
)
>
reqWidth
)
{
inSampleSize
*=
2
;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
long
totalPixels
=
width
*
height
/
inSampleSize
;
// Anything more than 2x the requested pixels we'll sample down further
final
long
totalReqPixelsCap
=
reqWidth
*
reqHeight
*
2
;
while
(
totalPixels
>
totalReqPixelsCap
)
{
inSampleSize
*=
2
;
totalPixels
/=
2
;
}
}
return
inSampleSize
;
// END_INCLUDE (calculate_sample_size)
}
}
ABVJE_UI_Android/src/com/imagepicker/ImageWorker.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2012 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
.
imagepicker
;
import
android.content.Context
;
import
android.content.res.Resources
;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
import
android.graphics.Matrix
;
import
android.graphics.drawable.BitmapDrawable
;
import
android.graphics.drawable.ColorDrawable
;
import
android.graphics.drawable.Drawable
;
import
android.graphics.drawable.TransitionDrawable
;
import
android.support.v4.app.FragmentActivity
;
import
android.support.v4.app.FragmentManager
;
import
android.util.Log
;
import
android.widget.ImageView
;
import
java.lang.ref.WeakReference
;
/**
* This class wraps up completing some arbitrary long running work when loading a bitmap to an
* ImageView. It handles things like using a memory and disk cache, running the work in a background
* thread and setting a placeholder image.
*/
public
abstract
class
ImageWorker
{
private
static
final
String
TAG
=
"ImageWorker"
;
private
static
final
int
FADE_IN_TIME
=
200
;
private
ImageCache
mImageCache
;
private
ImageCache
.
ImageCacheParams
mImageCacheParams
;
private
Bitmap
mLoadingBitmap
;
private
boolean
mFadeInBitmap
=
true
;
private
boolean
mExitTasksEarly
=
false
;
protected
boolean
mPauseWork
=
false
;
private
final
Object
mPauseWorkLock
=
new
Object
();
protected
Resources
mResources
;
private
static
final
int
MESSAGE_CLEAR
=
0
;
private
static
final
int
MESSAGE_INIT_DISK_CACHE
=
1
;
private
static
final
int
MESSAGE_FLUSH
=
2
;
private
static
final
int
MESSAGE_CLOSE
=
3
;
protected
ImageWorker
(
Context
context
)
{
mResources
=
context
.
getResources
();
}
/**
* Load an image specified by the data parameter into an ImageView (override
* {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
* disk cache will be used if an {@link ImageCache} has been added using
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
* image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
* will be created to asynchronously load the bitmap.
*
* @param data The URL of the image to download.
* @param imageView The ImageView to bind the downloaded image to.
* @param orientation The Orientation image info.
*/
public
void
loadImage
(
Object
data
,
ImageView
imageView
,
int
orientation
)
{
if
(
data
==
null
)
{
return
;
}
BitmapDrawable
value
=
null
;
if
(
mImageCache
!=
null
)
{
value
=
mImageCache
.
getBitmapFromMemCache
(
String
.
valueOf
(
data
));
}
if
(
value
!=
null
)
{
// Bitmap found in memory cache
imageView
.
setImageDrawable
(
value
);
}
else
if
(
cancelPotentialWork
(
data
,
imageView
))
{
//BEGIN_INCLUDE(execute_background_task)
final
BitmapWorkerTask
task
=
new
BitmapWorkerTask
(
data
,
imageView
,
orientation
);
final
AsyncDrawable
asyncDrawable
=
new
AsyncDrawable
(
mResources
,
mLoadingBitmap
,
task
);
imageView
.
setImageDrawable
(
asyncDrawable
);
// NOTE: This uses a custom version of AsyncTask that has been pulled from the
// framework and slightly modified. Refer to the docs at the top of the class
// for more info on what was changed.
task
.
executeOnExecutor
(
AsyncTask
.
DUAL_THREAD_EXECUTOR
);
//END_INCLUDE(execute_background_task)
}
}
/**
* Set placeholder bitmap that shows when the the background thread is running.
*
* @param bitmap
*/
public
void
setLoadingImage
(
Bitmap
bitmap
)
{
mLoadingBitmap
=
bitmap
;
}
/**
* Set placeholder bitmap that shows when the the background thread is running.
*
* @param resId
*/
public
void
setLoadingImage
(
int
resId
)
{
mLoadingBitmap
=
BitmapFactory
.
decodeResource
(
mResources
,
resId
);
}
/**
* Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
* caching.
* @param fragmentManager
* @param cacheParams The cache parameters to use for the image cache.
*/
public
void
addImageCache
(
FragmentManager
fragmentManager
,
ImageCache
.
ImageCacheParams
cacheParams
)
{
mImageCacheParams
=
cacheParams
;
mImageCache
=
ImageCache
.
getInstance
(
fragmentManager
,
mImageCacheParams
);
new
CacheAsyncTask
().
execute
(
MESSAGE_INIT_DISK_CACHE
);
}
/**
* Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
* caching.
* @param activity
* @param diskCacheDirectoryName See
* {@link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}.
*/
public
void
addImageCache
(
FragmentActivity
activity
,
String
diskCacheDirectoryName
)
{
mImageCacheParams
=
new
ImageCache
.
ImageCacheParams
(
activity
,
diskCacheDirectoryName
);
mImageCache
=
ImageCache
.
getInstance
(
activity
.
getSupportFragmentManager
(),
mImageCacheParams
);
new
CacheAsyncTask
().
execute
(
MESSAGE_INIT_DISK_CACHE
);
}
/**
* If set to true, the image will fade-in once it has been loaded by the background thread.
*/
public
void
setImageFadeIn
(
boolean
fadeIn
)
{
mFadeInBitmap
=
fadeIn
;
}
public
void
setExitTasksEarly
(
boolean
exitTasksEarly
)
{
mExitTasksEarly
=
exitTasksEarly
;
setPauseWork
(
false
);
}
/**
* Subclasses should override this to define any processing or work that must happen to produce
* the final bitmap. This will be executed in a background thread and be long running. For
* example, you could resize a large bitmap here, or pull down an image from the network.
*
* @param data The data to identify which image to process, as provided by
* {@link ImageWorker#loadImage(Object, android.widget.ImageView)}
* @return The processed bitmap
*/
protected
abstract
Bitmap
processBitmap
(
Object
data
);
/**
* @return The {@link ImageCache} object currently being used by this ImageWorker.
*/
protected
ImageCache
getImageCache
()
{
return
mImageCache
;
}
/**
* Cancels any pending work attached to the provided ImageView.
* @param imageView
*/
public
static
void
cancelWork
(
ImageView
imageView
)
{
final
BitmapWorkerTask
bitmapWorkerTask
=
getBitmapWorkerTask
(
imageView
);
if
(
bitmapWorkerTask
!=
null
)
{
bitmapWorkerTask
.
cancel
(
true
);
}
}
/**
* Returns true if the current work has been canceled or if there was no work in
* progress on this image view.
* Returns false if the work in progress deals with the same data. The work is not
* stopped in that case.
*/
public
static
boolean
cancelPotentialWork
(
Object
data
,
ImageView
imageView
)
{
//BEGIN_INCLUDE(cancel_potential_work)
final
BitmapWorkerTask
bitmapWorkerTask
=
getBitmapWorkerTask
(
imageView
);
if
(
bitmapWorkerTask
!=
null
)
{
final
Object
bitmapData
=
bitmapWorkerTask
.
mData
;
if
(
bitmapData
==
null
||
!
bitmapData
.
equals
(
data
))
{
bitmapWorkerTask
.
cancel
(
true
);
}
else
{
// The same work is already in progress.
return
false
;
}
}
return
true
;
//END_INCLUDE(cancel_potential_work)
}
/**
* @param imageView Any imageView
* @return Retrieve the currently active work task (if any) associated with this imageView.
* null if there is no such task.
*/
private
static
BitmapWorkerTask
getBitmapWorkerTask
(
ImageView
imageView
)
{
if
(
imageView
!=
null
)
{
final
Drawable
drawable
=
imageView
.
getDrawable
();
if
(
drawable
instanceof
AsyncDrawable
)
{
final
AsyncDrawable
asyncDrawable
=
(
AsyncDrawable
)
drawable
;
return
asyncDrawable
.
getBitmapWorkerTask
();
}
}
return
null
;
}
/**
* The actual AsyncTask that will asynchronously process the image.
*/
private
class
BitmapWorkerTask
extends
AsyncTask
<
Void
,
Void
,
BitmapDrawable
>
{
private
Object
mData
;
private
final
WeakReference
<
ImageView
>
imageViewReference
;
private
int
mOrientation
;
public
BitmapWorkerTask
(
Object
data
,
ImageView
imageView
,
int
orientation
)
{
mData
=
data
;
mOrientation
=
orientation
;
imageViewReference
=
new
WeakReference
<
ImageView
>(
imageView
);
}
/**
* Background processing.
*/
@Override
protected
BitmapDrawable
doInBackground
(
Void
...
params
)
{
final
String
dataString
=
String
.
valueOf
(
mData
);
Bitmap
bitmap
=
null
;
BitmapDrawable
drawable
=
null
;
// Wait here if work is paused and the task is not cancelled
synchronized
(
mPauseWorkLock
)
{
while
(
mPauseWork
&&
!
isCancelled
())
{
try
{
mPauseWorkLock
.
wait
();
}
catch
(
InterruptedException
e
)
{}
}
}
// If the image cache is available and this task has not been cancelled by another
// thread and the ImageView that was originally bound to this task is still bound back
// to this task and our "exit early" flag is not set then try and fetch the bitmap from
// the cache
if
(
mImageCache
!=
null
&&
!
isCancelled
()
&&
getAttachedImageView
()
!=
null
&&
!
mExitTasksEarly
)
{
bitmap
=
mImageCache
.
getBitmapFromDiskCache
(
dataString
);
}
// If the bitmap was not found in the cache and this task has not been cancelled by
// another thread and the ImageView that was originally bound to this task is still
// bound back to this task and our "exit early" flag is not set, then call the main
// process method (as implemented by a subclass)
if
(
bitmap
==
null
&&
!
isCancelled
()
&&
getAttachedImageView
()
!=
null
&&
!
mExitTasksEarly
)
{
bitmap
=
processBitmap
(
mData
);
}
// If the bitmap was processed and the image cache is available, then add the processed
// bitmap to the cache for future use. Note we don't check if the task was cancelled
// here, if it was, and the thread is still running, we may as well add the processed
// bitmap to our cache as it might be used again in the future
if
(
bitmap
!=
null
)
{
//setting Orientation
if
(
mOrientation
!=
0
)
{
// 回転マトリックス作成(90度回転)
Matrix
mat
=
new
Matrix
();
mat
.
postRotate
(
mOrientation
);
// 回転したビットマップを作成
bitmap
=
Bitmap
.
createBitmap
(
bitmap
,
0
,
0
,
bitmap
.
getWidth
(),
bitmap
.
getHeight
(),
mat
,
true
);
}
if
(
Utils
.
hasHoneycomb
())
{
// Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
drawable
=
new
BitmapDrawable
(
mResources
,
bitmap
);
}
else
{
// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
// which will recycle automagically
drawable
=
new
RecyclingBitmapDrawable
(
mResources
,
bitmap
);
}
if
(
mImageCache
!=
null
)
{
mImageCache
.
addBitmapToCache
(
dataString
,
drawable
);
}
}
return
drawable
;
//END_INCLUDE(load_bitmap_in_background)
}
/**
* Once the image is processed, associates it to the imageView
*/
@Override
protected
void
onPostExecute
(
BitmapDrawable
value
)
{
//BEGIN_INCLUDE(complete_background_work)
// if cancel was called on this task or the "exit early" flag is set then we're done
if
(
isCancelled
()
||
mExitTasksEarly
)
{
value
=
null
;
}
final
ImageView
imageView
=
getAttachedImageView
();
if
(
value
!=
null
&&
imageView
!=
null
)
{
setImageDrawable
(
imageView
,
value
);
}
//END_INCLUDE(complete_background_work)
}
@Override
protected
void
onCancelled
(
BitmapDrawable
value
)
{
super
.
onCancelled
(
value
);
synchronized
(
mPauseWorkLock
)
{
mPauseWorkLock
.
notifyAll
();
}
}
/**
* Returns the ImageView associated with this task as long as the ImageView's task still
* points to this task as well. Returns null otherwise.
*/
private
ImageView
getAttachedImageView
()
{
final
ImageView
imageView
=
imageViewReference
.
get
();
final
BitmapWorkerTask
bitmapWorkerTask
=
getBitmapWorkerTask
(
imageView
);
if
(
this
==
bitmapWorkerTask
)
{
return
imageView
;
}
return
null
;
}
}
/**
* A custom Drawable that will be attached to the imageView while the work is in progress.
* Contains a reference to the actual worker task, so that it can be stopped if a new binding is
* required, and makes sure that only the last started worker process can bind its result,
* independently of the finish order.
*/
private
static
class
AsyncDrawable
extends
BitmapDrawable
{
private
final
WeakReference
<
BitmapWorkerTask
>
bitmapWorkerTaskReference
;
public
AsyncDrawable
(
Resources
res
,
Bitmap
bitmap
,
BitmapWorkerTask
bitmapWorkerTask
)
{
super
(
res
,
bitmap
);
bitmapWorkerTaskReference
=
new
WeakReference
<
BitmapWorkerTask
>(
bitmapWorkerTask
);
}
public
BitmapWorkerTask
getBitmapWorkerTask
()
{
return
bitmapWorkerTaskReference
.
get
();
}
}
/**
* Called when the processing is complete and the final drawable should be
* set on the ImageView.
*
* @param imageView
* @param drawable
*/
private
void
setImageDrawable
(
ImageView
imageView
,
Drawable
drawable
)
{
if
(
mFadeInBitmap
)
{
// Transition drawable with a transparent drawable and the final drawable
final
TransitionDrawable
td
=
new
TransitionDrawable
(
new
Drawable
[]
{
new
ColorDrawable
(
android
.
R
.
color
.
transparent
),
drawable
});
// Set background to loading bitmap
imageView
.
setBackgroundDrawable
(
new
BitmapDrawable
(
mResources
,
mLoadingBitmap
));
imageView
.
setImageDrawable
(
td
);
td
.
startTransition
(
FADE_IN_TIME
);
}
else
{
imageView
.
setImageDrawable
(
drawable
);
}
}
/**
* Pause any ongoing background work. This can be used as a temporary
* measure to improve performance. For example background work could
* be paused when a ListView or GridView is being scrolled using a
* {@link android.widget.AbsListView.OnScrollListener} to keep
* scrolling smooth.
* <p>
* If work is paused, be sure setPauseWork(false) is called again
* before your fragment or activity is destroyed (for example during
* {@link android.app.Activity#onPause()}), or there is a risk the
* background thread will never finish.
*/
public
void
setPauseWork
(
boolean
pauseWork
)
{
synchronized
(
mPauseWorkLock
)
{
mPauseWork
=
pauseWork
;
if
(!
mPauseWork
)
{
mPauseWorkLock
.
notifyAll
();
}
}
}
protected
class
CacheAsyncTask
extends
AsyncTask
<
Object
,
Void
,
Void
>
{
@Override
protected
Void
doInBackground
(
Object
...
params
)
{
switch
((
Integer
)
params
[
0
])
{
case
MESSAGE_CLEAR:
clearCacheInternal
();
break
;
case
MESSAGE_INIT_DISK_CACHE:
initDiskCacheInternal
();
break
;
case
MESSAGE_FLUSH:
flushCacheInternal
();
break
;
case
MESSAGE_CLOSE:
closeCacheInternal
();
break
;
}
return
null
;
}
}
protected
void
initDiskCacheInternal
()
{
if
(
mImageCache
!=
null
)
{
mImageCache
.
initDiskCache
();
}
}
protected
void
clearCacheInternal
()
{
if
(
mImageCache
!=
null
)
{
mImageCache
.
clearCache
();
}
}
protected
void
flushCacheInternal
()
{
if
(
mImageCache
!=
null
)
{
mImageCache
.
flush
();
}
}
protected
void
closeCacheInternal
()
{
if
(
mImageCache
!=
null
)
{
mImageCache
.
close
();
mImageCache
=
null
;
}
}
public
void
clearCache
()
{
new
CacheAsyncTask
().
execute
(
MESSAGE_CLEAR
);
}
public
void
flushCache
()
{
new
CacheAsyncTask
().
execute
(
MESSAGE_FLUSH
);
}
public
void
closeCache
()
{
new
CacheAsyncTask
().
execute
(
MESSAGE_CLOSE
);
}
}
ABVJE_UI_Android/src/com/imagepicker/RecyclingBitmapDrawable.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2013 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
.
imagepicker
;
import
android.content.res.Resources
;
import
android.graphics.Bitmap
;
import
android.graphics.drawable.BitmapDrawable
;
import
android.util.Log
;
/**
* A BitmapDrawable that keeps track of whether it is being displayed or cached.
* When the drawable is no longer being displayed or cached,
* {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
*/
public
class
RecyclingBitmapDrawable
extends
BitmapDrawable
{
static
final
String
TAG
=
"CountingBitmapDrawable"
;
private
int
mCacheRefCount
=
0
;
private
int
mDisplayRefCount
=
0
;
private
boolean
mHasBeenDisplayed
;
public
RecyclingBitmapDrawable
(
Resources
res
,
Bitmap
bitmap
)
{
super
(
res
,
bitmap
);
}
/**
* Notify the drawable that the displayed state has changed. Internally a
* count is kept so that the drawable knows when it is no longer being
* displayed.
*
* @param isDisplayed - Whether the drawable is being displayed or not
*/
public
void
setIsDisplayed
(
boolean
isDisplayed
)
{
//BEGIN_INCLUDE(set_is_displayed)
synchronized
(
this
)
{
if
(
isDisplayed
)
{
mDisplayRefCount
++;
mHasBeenDisplayed
=
true
;
}
else
{
mDisplayRefCount
--;
}
}
// Check to see if recycle() can be called
checkState
();
//END_INCLUDE(set_is_displayed)
}
/**
* Notify the drawable that the cache state has changed. Internally a count
* is kept so that the drawable knows when it is no longer being cached.
*
* @param isCached - Whether the drawable is being cached or not
*/
public
void
setIsCached
(
boolean
isCached
)
{
//BEGIN_INCLUDE(set_is_cached)
synchronized
(
this
)
{
if
(
isCached
)
{
mCacheRefCount
++;
}
else
{
mCacheRefCount
--;
}
}
// Check to see if recycle() can be called
checkState
();
//END_INCLUDE(set_is_cached)
}
private
synchronized
void
checkState
()
{
//BEGIN_INCLUDE(check_state)
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle
if
(
mCacheRefCount
<=
0
&&
mDisplayRefCount
<=
0
&&
mHasBeenDisplayed
&&
hasValidBitmap
())
{
getBitmap
().
recycle
();
}
//END_INCLUDE(check_state)
}
private
synchronized
boolean
hasValidBitmap
()
{
Bitmap
bitmap
=
getBitmap
();
return
bitmap
!=
null
&&
!
bitmap
.
isRecycled
();
}
}
ABVJE_UI_Android/src/com/imagepicker/Utils.java
0 → 100644
View file @
f193357b
/*
* Copyright (C) 2012 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
.
imagepicker
;
import
android.os.Build
;
import
android.os.Build.VERSION_CODES
;
/**
* Class containing some static utility methods.
*/
public
class
Utils
{
private
Utils
()
{};
public
static
boolean
hasFroyo
()
{
// Can use static final constants like FROYO, declared in later versions
// of the OS since they are inlined at compile time. This is guaranteed behavior.
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
FROYO
;
}
public
static
boolean
hasGingerbread
()
{
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
GINGERBREAD
;
}
public
static
boolean
hasHoneycomb
()
{
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
HONEYCOMB
;
}
public
static
boolean
hasHoneycombMR1
()
{
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
HONEYCOMB_MR1
;
}
public
static
boolean
hasJellyBean
()
{
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
JELLY_BEAN
;
}
public
static
boolean
hasKitKat
()
{
return
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
KITKAT
;
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/activity/ABVContentViewActivity.java
View file @
f193357b
...
...
@@ -88,7 +88,6 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
private
static
final
String
TAG
=
"ABVContentViewActivity"
;
public
final
static
int
ABOOK_CHECK_TASK_IMAGE
=
103
;
public
final
static
int
ABOOK_CHECK_TASK_VIDEO
=
104
;
protected
final
static
int
ABOOK_CHECK_SELECT_SCENE
=
105
;
protected
long
contentId
;
// 表示中のコンテンツID
protected
long
objectId
;
// オブジェクトID(オブジェクト用のActivityのときのみ使用)
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/activity/ABVUIActivity.java
View file @
f193357b
...
...
@@ -107,7 +107,8 @@ public abstract class ABVUIActivity extends ABVAuthenticatedActivity {
// OS 8.0で起きるバグ(ダイアログActivity表示後(windowIsTranslucent: true)、画面を固定するとCrashされる問題対策)
// showPushMessageActivityが存在する場合は、以下のメソッドを実行しない
if
(!
ActivityHandlingHelper
.
getInstance
().
hasShowPushMessageActivity
())
{
if
(!
ActivityHandlingHelper
.
getInstance
().
hasShowPushMessageActivity
()
&&
!
ActivityHandlingHelper
.
getInstance
().
hasShowDeviceImageListActivity
())
{
setPortraitIfNormal
();
}
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/common/util/AlertDialogUtil.java
View file @
f193357b
...
...
@@ -3,6 +3,7 @@ import jp.agentec.abook.abv.launcher.android.R;
import
jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog
;
import
android.content.Context
;
import
android.content.DialogInterface
;
import
android.view.Gravity
;
import
android.view.ViewGroup
;
import
android.widget.FrameLayout
;
...
...
@@ -42,6 +43,25 @@ public class AlertDialogUtil {
return
alertDialog
;
}
/**
* 共通ダイアログ表示用(Cancel表示・非表示、OKボタンコールバック)
* @param context コンテキスト
* @param title タイトル
* @param message メッセージ
* @param isCancleButtonHidden Cancelボタン表示(false)・非表示(true)
* @param okOnClick OKボタンコールバック
*/
public
static
void
showAlertDialog
(
Context
context
,
int
title
,
int
message
,
boolean
isCancleButtonHidden
,
DialogInterface
.
OnClickListener
okOnClick
)
{
ABookAlertDialog
alertDialog
=
createAlertDialog
(
context
,
context
.
getResources
().
getString
(
title
),
context
.
getResources
().
getString
(
message
));
if
(!
isCancleButtonHidden
)
{
alertDialog
.
setNegativeButton
(
R
.
string
.
cancel
,
null
);
}
alertDialog
.
setPositiveButton
(
R
.
string
.
ok
,
okOnClick
);
alertDialog
.
show
();
}
public
static
ABookAlertDialog
deleteContentAlertDialog
(
Context
context
,
int
deleteMessage
)
{
ABookAlertDialog
alertDialog
=
createAlertDialog
(
context
,
R
.
string
.
delete
);
alertDialog
.
setMessage
(
deleteMessage
);
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/home/helper/ABookPermissionHelper.java
View file @
f193357b
...
...
@@ -21,6 +21,7 @@ import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import
jp.agentec.abook.abv.launcher.android.R
;
import
jp.agentec.abook.abv.ui.common.util.PatternStringUtil
;
import
jp.agentec.abook.abv.ui.home.activity.OperationListActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity
;
...
...
@@ -97,7 +98,10 @@ public class ABookPermissionHelper {
// ストレージ
if
(
ContextCompat
.
checkSelfPermission
(
mContext
,
android
.
Manifest
.
permission
.
READ_EXTERNAL_STORAGE
)
!=
PERMISSION_GRANTED
||
ContextCompat
.
checkSelfPermission
(
mContext
,
android
.
Manifest
.
permission
.
WRITE_EXTERNAL_STORAGE
)
!=
PERMISSION_GRANTED
)
{
if
(
mContext
instanceof
HTMLWebViewActivity
||
mContext
instanceof
HTMLXWalkWebViewActivity
||
mContext
instanceof
OperationListActivity
)
{
if
(
mContext
instanceof
HTMLWebViewActivity
||
mContext
instanceof
HTMLXWalkWebViewActivity
||
mContext
instanceof
OperationListActivity
||
mContext
instanceof
DeviceImageListActivity
)
{
// リソースパターンの適用
permitionTextResourceId
=
PatternStringUtil
.
patternToInt
(
mContext
,
R
.
string
.
msg_permission_dialog_storage_album
,
...
...
@@ -108,7 +112,6 @@ public class ABookPermissionHelper {
R
.
string
.
msg_permission_dialog_storage_update
,
getUserPref
(
mContext
,
AppDefType
.
UserPrefKey
.
RESOURCE_PATTERN_TYPE
,
0
));
}
}
break
;
case
Constant
.
ABookPermissionType
.
AccessFineLocation
:
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/home/helper/ActivityHandlingHelper.java
View file @
f193357b
...
...
@@ -78,6 +78,7 @@ import jp.agentec.abook.abv.ui.home.activity.OperationRelatedContentActivity;
import
jp.agentec.abook.abv.ui.viewer.activity.AudioPlayActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.CheckOZDViewActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.ContentViewActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.EnqueteWebViewActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity
;
import
jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity
;
...
...
@@ -820,6 +821,21 @@ public class ActivityHandlingHelper extends ABookHelper implements RemoteObserve
}
/**
* シーン選択画面Activityが存在確認
* @return true:表示中状態、false:非表示状態
*/
public
boolean
hasShowDeviceImageListActivity
()
{
if
(
CollectionUtil
.
isNotEmpty
(
currentActivityStack
))
{
for
(
ABVAuthenticatedActivity
activity
:
currentActivityStack
)
{
if
((
activity
instanceof
DeviceImageListActivity
))
{
return
true
;
}
}
}
return
false
;
}
/**
* 履歴から該当の360コンテンツを削除する
* @param contentId long
*/
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/DeviceImageListActivity.java
0 → 100644
View file @
f193357b
package
jp
.
agentec
.
abook
.
abv
.
ui
.
viewer
.
activity
;
import
android.content.DialogInterface
;
import
android.content.Intent
;
import
android.content.res.Configuration
;
import
android.content.res.Resources
;
import
android.database.Cursor
;
import
android.net.Uri
;
import
android.os.Bundle
;
import
android.provider.BaseColumns
;
import
android.provider.MediaStore
;
import
android.view.View
;
import
android.view.WindowManager
;
import
android.widget.AdapterView
;
import
android.widget.Button
;
import
android.widget.GridView
;
import
com.imagepicker.ImageInternalFetcher
;
import
java.io.File
;
import
java.util.ArrayList
;
import
java.util.HashSet
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.Set
;
import
jp.agentec.abook.abv.bl.common.ABVEnvironment
;
import
jp.agentec.abook.abv.bl.common.Callback
;
import
jp.agentec.abook.abv.bl.common.CommonExecutor
;
import
jp.agentec.abook.abv.bl.common.Constant
;
import
jp.agentec.abook.abv.bl.common.constant.ABookKeys
;
import
jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode
;
import
jp.agentec.abook.abv.bl.common.exception.AcmsException
;
import
jp.agentec.abook.abv.bl.common.log.Logger
;
import
jp.agentec.abook.abv.bl.logic.AbstractLogic
;
import
jp.agentec.abook.abv.bl.logic.OperationLogic
;
import
jp.agentec.abook.abv.launcher.android.R
;
import
jp.agentec.abook.abv.ui.common.activity.ABVUIActivity
;
import
jp.agentec.abook.abv.ui.common.appinfo.AppDefType
;
import
jp.agentec.abook.abv.ui.common.util.AlertDialogUtil
;
import
jp.agentec.abook.abv.ui.common.util.PatternStringUtil
;
import
jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper
;
import
jp.agentec.abook.abv.ui.viewer.adapter.ImageGalleryAdapter
;
import
jp.agentec.abook.abv.ui.viewer.view.CustomImage
;
public
class
DeviceImageListActivity
extends
ABVUIActivity
{
private
static
final
String
TAG
=
"DeviceImageListActivity"
;
//表示画像の解像度(低くなると画質が悪くなる)
private
static
final
int
THUMBNALE_SIZE
=
1000
;
//タブレットの1行最大表示数
private
static
final
int
TABLET_COLUMNS
=
4
;
//タブレットの縦表示時、1行最大表示数
private
static
final
int
PHONE_COLUMNS_PORTRAIT
=
3
;
//タブレットの横表示時、1行最大表示数
private
static
final
int
PHONE_COLUMNS_LANDSCAPE
=
4
;
//画像選択最大数
private
static
final
int
IMAGE_SELECT_MAX_COUNT
=
5
;
//タブレット表示の画面サイズ調整スケール
public
static
final
double
WINDOW_RESIZE_SCALE
=
0.9
;
//連続タップ防止用のボタン活性化するタイム
public
static
final
int
BUTTON_ENABLE_DELAY_MILLIS
=
500
;
//登録完了後、100%プログレスバー表示のため、完了ダイアログ表示タイム
public
static
final
int
SEND_FINISH_DIALOG_DELAY_MILLIS
=
200
;
GridView
mGalleryGridView
;
ImageGalleryAdapter
mGalleryAdapter
;
private
Set
<
String
>
mSelectedImages
;
private
List
<
CustomImage
>
mLocalImageList
;
private
Set
<
String
>
mLocalImageUriList
;
public
ImageInternalFetcher
mImageFetcher
;
private
Button
mRegistBtn
;
private
int
mGridViewRowHeight
;
private
boolean
mIsOnResume
;
private
Long
mContentId
;
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
Logger
.
i
(
TAG
,
"onCreate"
);
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
ac_device_image_list
);
Intent
intent
=
getIntent
();
mContentId
=
intent
.
getLongExtra
(
ABookKeys
.
CONTENT_ID
,
-
1
);
mSelectedImages
=
new
HashSet
<>();
mLocalImageList
=
new
ArrayList
<>();
mLocalImageUriList
=
new
HashSet
<>();
mImageFetcher
=
new
ImageInternalFetcher
(
this
,
THUMBNALE_SIZE
);
mIsOnResume
=
false
;
mGalleryGridView
=
findViewById
(
R
.
id
.
gallery_grid
);
Button
closeBtn
=
findViewById
(
R
.
id
.
close
);
// 閉じるボタン
closeBtn
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
Logger
.
v
(
TAG
,
"CloseBtn.onClick"
);
finish
();
}
});
mRegistBtn
=
findViewById
(
R
.
id
.
regist
);
mRegistBtn
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
Logger
.
v
(
TAG
,
"RegistBtn.onClick"
);
if
(
checkNetworkConnected
())
{
mRegistBtn
.
setEnabled
(
false
);
AlertDialogUtil
.
showAlertDialog
(
DeviceImageListActivity
.
this
,
R
.
string
.
app_name
,
R
.
string
.
msg_image_select_send_comfirm
,
false
,
new
DialogInterface
.
OnClickListener
()
{
@Override
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
if
(
checkNetworkConnected
())
{
sendImageList
(
mSelectedImages
);
}
}
});
//連続タップ防止
handler
.
postDelayed
(
new
Runnable
()
{
@Override
public
void
run
()
{
mRegistBtn
.
setEnabled
(
true
);
}
},
BUTTON_ENABLE_DELAY_MILLIS
);
}
}
});
mRegistBtn
.
setEnabled
(
false
);
//表示中にストレージ権限がなくなった場合、シーン画像選択画面を非表示
final
Callback
resultCallback
=
new
Callback
()
{
@Override
public
Object
callback
(
Object
ret
)
{
finish
();
return
null
;
}
};
ABookPermissionHelper
helper
=
new
ABookPermissionHelper
(
this
,
Constant
.
ABookPermissionType
.
ReadExternalStorage
,
resultCallback
);
if
(!
helper
.
checkMultiPermissions
(
true
))
{
if
(!
isNormalSize
())
{
//タブレットの場合、何も表示されないダイアログのサイズ調整
Resources
r
=
Resources
.
getSystem
();
Configuration
config
=
r
.
getConfiguration
();
onConfigurationChanged
(
config
);
}
return
;
}
getDeviceGalleryImageList
();
//ダイアログサイズ調整
Resources
r
=
Resources
.
getSystem
();
Configuration
config
=
r
.
getConfiguration
();
onConfigurationChanged
(
config
);
}
@Override
public
void
onResume
()
{
super
.
onResume
();
//初回画面表示時にはよばれないようにする。
if
(
mIsOnResume
)
{
getDeviceGalleryImageList
();
displayGalleryGridView
();
}
mIsOnResume
=
true
;
}
@Override
public
void
onConfigurationChanged
(
Configuration
newConfig
)
{
super
.
onConfigurationChanged
(
newConfig
);
int
widthPixels
=
(
int
)
(
getResources
().
getDisplayMetrics
().
widthPixels
*
WINDOW_RESIZE_SCALE
)
;
int
heightPixels
=
(
int
)
(
getResources
().
getDisplayMetrics
().
heightPixels
*
WINDOW_RESIZE_SCALE
);
int
numColumn
;
if
(
isNormalSize
())
{
//スマートフォン
if
(
widthPixels
>
heightPixels
)
{
numColumn
=
PHONE_COLUMNS_LANDSCAPE
;
}
else
{
numColumn
=
PHONE_COLUMNS_PORTRAIT
;
}
}
else
{
//タブレット端末
WindowManager
.
LayoutParams
lp
=
getWindow
().
getAttributes
();
if
(
widthPixels
>
heightPixels
)
{
lp
.
width
=
heightPixels
;
lp
.
height
=
heightPixels
;
}
else
{
lp
.
width
=
widthPixels
;
lp
.
height
=
widthPixels
;
}
getWindow
().
setAttributes
(
lp
);
numColumn
=
TABLET_COLUMNS
;
widthPixels
=
lp
.
width
;
}
mGalleryGridView
.
setNumColumns
(
numColumn
);
mGridViewRowHeight
=
widthPixels
/
numColumn
;
//回転時、横・縦表示数が異なるため、スマートフォンだけ再描画する。
if
(
isNormalSize
())
{
//スマートフォン
displayGalleryGridView
();
}
else
{
//回転時にはよばれないようにチェックし、初回表示時のみ実行(onCreateから呼ばれた時)
if
(!
mIsOnResume
)
{
displayGalleryGridView
();
}
}
}
/**
* 端末のギャラリーから画像情報を取得
*/
private
void
getDeviceGalleryImageList
()
{
if
(
mLocalImageList
.
size
()
!=
0
)
{
mLocalImageList
.
clear
();
mLocalImageUriList
.
clear
();
}
final
String
[]
columns
=
{
MediaStore
.
MediaColumns
.
DATA
,
BaseColumns
.
_ID
,
MediaStore
.
Images
.
ImageColumns
.
ORIENTATION
,
MediaStore
.
MediaColumns
.
DATE_ADDED
};
final
String
orderBy
=
BaseColumns
.
_ID
+
" DESC"
;
Cursor
imageCursor
=
getContentResolver
().
query
(
MediaStore
.
Images
.
Media
.
EXTERNAL_CONTENT_URI
,
columns
,
null
,
null
,
orderBy
);
if
(
imageCursor
!=
null
)
{
while
(
imageCursor
.
moveToNext
())
{
String
uriStr
=
imageCursor
.
getString
(
imageCursor
.
getColumnIndex
(
MediaStore
.
MediaColumns
.
DATA
));
Uri
uri
=
Uri
.
parse
(
uriStr
);
int
orientation
=
imageCursor
.
getInt
(
imageCursor
.
getColumnIndex
(
MediaStore
.
Images
.
ImageColumns
.
ORIENTATION
));
mLocalImageList
.
add
(
new
CustomImage
(
uri
,
orientation
));
mLocalImageUriList
.
add
(
uriStr
);
}
imageCursor
.
close
();
}
//選択された画像情報から削除された画像情報削除
if
(
mSelectedImages
.
size
()
>
0
)
{
Iterator
<
String
>
it
=
mSelectedImages
.
iterator
();
while
(
it
.
hasNext
()){
String
uriStr
=
it
.
next
();
if
(!
mLocalImageUriList
.
contains
(
uriStr
))
{
it
.
remove
();
}
}
if
(
mSelectedImages
.
size
()
==
0
)
{
mRegistBtn
.
setEnabled
(
false
);
}
}
}
/**
* 端末のギャラリーから画像情報を元にして、GridViewビューア描画
*/
private
void
displayGalleryGridView
()
{
mGalleryAdapter
=
new
ImageGalleryAdapter
(
this
,
mLocalImageList
,
mGridViewRowHeight
);
mGalleryGridView
.
setAdapter
(
mGalleryAdapter
);
mGalleryGridView
.
setOnItemClickListener
(
new
AdapterView
.
OnItemClickListener
()
{
private
boolean
isClicked
=
false
;
@Override
public
void
onItemClick
(
AdapterView
<?>
adapterView
,
View
view
,
int
i
,
long
l
)
{
CustomImage
customImage
=
mGalleryAdapter
.
getItem
(
i
);
if
(
containsCustomImageUri
(
customImage
.
mUri
))
{
mSelectedImages
.
remove
(
customImage
.
mUri
.
toString
());
}
else
{
//最大選択画像をチェック
if
(
mSelectedImages
.
size
()
+
1
>
IMAGE_SELECT_MAX_COUNT
)
{
if
(
isClicked
)
{
return
;
}
isClicked
=
true
;
showSimpleAlertDialog
(
getResources
().
getString
(
R
.
string
.
app_name
),
String
.
format
(
getResources
().
getString
(
R
.
string
.
msg_image_select_max_count_over
),
String
.
valueOf
(
IMAGE_SELECT_MAX_COUNT
)));
//連続タップ防止
handler
.
postDelayed
(
new
Runnable
()
{
@Override
public
void
run
()
{
isClicked
=
false
;
}
},
1000
);
return
;
}
mSelectedImages
.
add
(
customImage
.
mUri
.
toString
());
}
//refresh the view to
mGalleryAdapter
.
getView
(
i
,
view
,
adapterView
);
mRegistBtn
.
setEnabled
(
mSelectedImages
.
size
()
!=
0
);
}
});
}
/**
* チェック画像を表示するため、
* 該当画像URIが保存されているデータに存在チェック
* @param imageUri 対象画像情報
* @return true:ある、false:ない
*/
public
boolean
containsCustomImageUri
(
Uri
imageUri
){
return
mSelectedImages
.
contains
(
imageUri
.
toString
());
}
/**
* 選択されている画像をサーバ側に送信する。
*/
private
void
sendImageList
(
final
Set
<
String
>
sendImages
)
{
showProgressView
(
PatternStringUtil
.
patternToString
(
getApplicationContext
(),
R
.
string
.
msg_access_registing
,
getUserPref
(
AppDefType
.
UserPrefKey
.
RESOURCE_PATTERN_TYPE
,
0
)));
CommonExecutor
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
final
Set
<
String
>
needSendImages
=
new
HashSet
<>(
sendImages
);
for
(
String
filePath
:
sendImages
){
File
file
=
new
File
(
filePath
);
try
{
AbstractLogic
.
getLogic
(
OperationLogic
.
class
).
sendScene
(
file
,
mContentId
);
needSendImages
.
remove
(
filePath
);
//プログレスバー進捗
final
int
progress
=
(
int
)
(((
float
)(
sendImages
.
size
()
-
needSendImages
.
size
())
/
sendImages
.
size
())
*
100
);
runOnUiThread
(
new
Runnable
()
{
@Override
public
void
run
()
{
progressDialogHorizontal
.
setProgress
(
progress
);
}
});
//送信終了
if
(
needSendImages
.
size
()
==
0
)
{
//プログレスバーを100%を表示してから非表示するため、ディレーする。
handler
.
postDelayed
(
new
Runnable
()
{
@Override
public
void
run
()
{
//プログレスバー非表示
closeProgressPopup
();
//成功ダイアログ表示
AlertDialogUtil
.
showAlertDialog
(
DeviceImageListActivity
.
this
,
R
.
string
.
app_name
,
R
.
string
.
msg_image_select_send_success
,
true
,
new
DialogInterface
.
OnClickListener
()
{
@Override
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
finish
();
}
});
}
},
SEND_FINISH_DIALOG_DELAY_MILLIS
);
}
}
catch
(
AcmsException
ex
)
{
Logger
.
e
(
TAG
,
ex
);
if
(
ex
.
getCode
()
==
ABVExceptionCode
.
P_E_ACMS_P007
)
{
// シーン追加時、ロック状態である場合
runOnUiThread
(
new
Runnable
()
{
@Override
public
void
run
()
{
closeProgressPopup
();
showSimpleAlertDialog
(
R
.
string
.
app_name
,
R
.
string
.
error_msg_open_pano_edit
);
}
});
}
else
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
sendImageFailDialog
(
needSendImages
);
}
});
}
break
;
}
catch
(
Exception
e
)
{
Logger
.
e
(
TAG
,
e
);
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
sendImageFailDialog
(
needSendImages
);
}
});
break
;
}
}
}
});
}
/**
* サーバ側通信時、失敗の時の再登録ダイアログ表示
* @param needSendImages 登録必要な画像URI配列
*/
private
void
sendImageFailDialog
(
final
Set
<
String
>
needSendImages
)
{
closeProgressPopup
();
AlertDialogUtil
.
showAlertDialog
(
this
,
R
.
string
.
app_name
,
R
.
string
.
msg_image_select_send_fail_retry
,
false
,
new
DialogInterface
.
OnClickListener
()
{
@Override
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
//インターネット非接続チェック
if
(
checkNetworkConnected
())
{
sendImageList
(
needSendImages
);
}
}
});
}
/**
* ネットワークチェックして非接続時にダイアログ表示
* @return true:接続、false:非接続
*/
private
boolean
checkNetworkConnected
()
{
if
(!
ABVEnvironment
.
getInstance
().
networkAdapter
.
isNetworkConnected
())
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
showSimpleAlertDialog
(
R
.
string
.
app_name
,
R
.
string
.
msg_network_offline
);
}
});
return
false
;
}
return
true
;
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/HTMLWebViewActivity.java
View file @
f193357b
...
...
@@ -657,10 +657,6 @@ public class HTMLWebViewActivity extends ParentWebViewActivity {
return
;
}
mUploadMessage
.
onReceiveValue
(
result
);
}
else
if
(
requestCode
==
ABOOK_CHECK_SELECT_SCENE
)
{
if
(
intent
!=
null
&&
result
!=
null
)
{
confirmEntrySceneDialog
(
result
[
0
]);
}
}
mUploadMessage
=
null
;
}
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/HTMLXWalkWebViewActivity.java
View file @
f193357b
...
...
@@ -729,10 +729,6 @@ public class HTMLXWalkWebViewActivity extends ParentWebViewActivity {
}
// 動画
mUploadMessage
.
onReceiveValue
(
result
);
}
else
if
(
requestCode
==
ABOOK_CHECK_SELECT_SCENE
)
{
if
(
intent
!=
null
&&
result
!=
null
)
{
confirmEntrySceneDialog
(
result
);
}
}
mUploadMessage
=
null
;
}
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/activity/ParentWebViewActivity.java
View file @
f193357b
...
...
@@ -5,6 +5,7 @@ import android.content.Context;
import
android.content.DialogInterface
;
import
android.content.Intent
;
import
android.net.Uri
;
import
android.util.Log
;
import
android.view.KeyEvent
;
import
android.view.View
;
import
android.widget.Button
;
...
...
@@ -44,6 +45,7 @@ import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import
jp.agentec.abook.abv.ui.common.util.AlertDialogUtil
;
import
jp.agentec.abook.abv.ui.common.util.PatternStringUtil
;
import
jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper
;
import
jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper
;
import
jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper
;
import
jp.agentec.abook.abv.ui.home.helper.ContentViewHelper
;
import
jp.agentec.adf.util.DateTimeFormat
;
...
...
@@ -89,10 +91,30 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
btnLinkOriginalBack
=
(
Button
)
findViewById
(
R
.
id
.
btn_link_original_back
);
if
(!
isLinkedContent
)
{
final
Context
context
=
this
;
addSceneButton
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
startCameraIntent
(
ABOOK_CHECK_SELECT_SCENE
,
"Camera"
,
ABookKeys
.
IMAGE
,
false
);
addSceneButton
.
setEnabled
(
false
);
//パーミッションチェック
ABookPermissionHelper
helper
=
new
ABookPermissionHelper
(
context
,
Constant
.
ABookPermissionType
.
ReadExternalStorage
,
null
);
if
(
helper
.
checkMultiPermissions
(
true
))
{
//シーン画像選択画面表示
Logger
.
d
(
"test"
);
Intent
intent
=
new
Intent
();
intent
.
putExtra
(
ABookKeys
.
CONTENT_ID
,
contentId
);
intent
.
setClassName
(
getPackageName
(),
DeviceImageListActivity
.
class
.
getName
());
startActivity
(
intent
);
}
else
{
Logger
.
w
(
TAG
,
"ReadExternalStorage checkMultiPermissions false"
);
}
//連続タップ防止
handler
.
postDelayed
(
new
Runnable
()
{
@Override
public
void
run
()
{
addSceneButton
.
setEnabled
(
true
);
}
},
500
);
}
});
createCheckToolbar
();
...
...
@@ -237,125 +259,6 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
super
.
onDestroy
();
}
// シーン追加ダイアログ表示
protected
void
confirmEntrySceneDialog
(
Uri
result
)
{
// 画像
try
{
final
Uri
responseUri
=
attachmentImageProcessing
(
result
);
// リソースパターンの適用
ABookAlertDialog
alertDialog
=
AlertDialogUtil
.
createAlertDialog
(
this
,
PatternStringUtil
.
patternToInt
(
getApplicationContext
(),
R
.
string
.
pano_edit
,
getUserPref
(
AppDefType
.
UserPrefKey
.
RESOURCE_PATTERN_TYPE
,
0
)));
alertDialog
.
setMessage
(
PatternStringUtil
.
patternToInt
(
getApplicationContext
(),
R
.
string
.
msg_confirm_entry_scene
,
getUserPref
(
AppDefType
.
UserPrefKey
.
RESOURCE_PATTERN_TYPE
,
0
)));
alertDialog
.
setNegativeButton
(
R
.
string
.
cancel
,
null
);
alertDialog
.
setPositiveButton
(
R
.
string
.
ok
,
new
DialogInterface
.
OnClickListener
()
{
@Override
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
dialog
.
dismiss
();
// リソースパターンの適用
showProgressView
(
PatternStringUtil
.
patternToString
(
getApplicationContext
(),
R
.
string
.
msg_common_processing
,
getUserPref
(
AppDefType
.
UserPrefKey
.
RESOURCE_PATTERN_TYPE
,
0
)));
CommonExecutor
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
if
(
responseUri
!=
null
&&
responseUri
.
getPath
()
!=
null
)
{
File
file
=
new
File
(
responseUri
.
getPath
());
try
{
final
Integer
resourceId
=
AbstractLogic
.
getLogic
(
OperationLogic
.
class
).
sendScene
(
file
,
contentId
);
if
(
resourceId
!=
null
)
{
runOnUiThread
(
new
Runnable
()
{
@Override
public
void
run
()
{
progressDialogHorizontal
.
setProgress
(
20
);
webViewLoadUrl
(
String
.
format
(
"javascript:CHK_E.checkResourceEntry('%s')"
,
resourceId
));
}
});
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
int
progress
=
progressDialogHorizontal
.
getProgress
();
if
(
progress
==
0
)
{
return
;
}
else
if
(
progress
==
100
)
{
progressDialogHorizontal
.
setProgress
(
0
);
closeProgressPopup
();
}
else
if
(
progress
<
100
&&
progress
>
80
)
{
progressDialogHorizontal
.
setProgress
(
progress
+
1
);
handler
.
postDelayed
(
this
,
2000
);
}
else
if
(
progress
<=
80
&&
progress
>=
60
)
{
progressDialogHorizontal
.
setProgress
(
progress
+
2
);
handler
.
postDelayed
(
this
,
1000
);
}
else
if
(
progress
<
60
)
{
progressDialogHorizontal
.
setProgress
(
progress
+
4
);
handler
.
postDelayed
(
this
,
1000
);
}
}
});
}
else
{
Logger
.
e
(
TAG
,
"resourceId == null"
);
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
closeProgressPopup
();
}
});
}
}
catch
(
AcmsException
ex
)
{
Logger
.
e
(
TAG
,
ex
);
if
(
ex
.
getCode
()
==
ABVExceptionCode
.
P_E_ACMS_P007
)
{
// シーン追加時、ロック状態である場合
runOnUiThread
(
new
Runnable
()
{
@Override
public
void
run
()
{
closeProgressPopup
();
webViewLoadUrl
(
String
.
format
(
"javascript:EDC.handleError({ 'status' : 400 }, '', {'message' : 'C018'})"
));
}
});
}
else
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
closeProgressPopup
();
showFailedSceneApiDialog
();
}
});
}
}
catch
(
Exception
e
)
{
Logger
.
e
(
TAG
,
e
);
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
closeProgressPopup
();
showFailedSceneApiDialog
();
}
});
}
finally
{
//アプリ内のファイルのみ削除(Galleryファイルは削除しない)
if
(
mLocalFile
!=
null
&&
mLocalFile
.
getPath
().
contains
(
getPackageName
()))
{
FileUtil
.
delete
(
mLocalFile
);
}
mLocalFile
=
null
;
}
}
}
});
}
});
alertDialog
.
show
();
}
catch
(
Exception
e
)
{
Logger
.
e
(
TAG
,
e
);
closeProgressPopup
();
ErrorMessage
.
showErrorMessageToast
(
getApplicationContext
(),
ErrorCode
.
E107
);
}
}
/**
* シーン追加API通信で失敗時のダイアログ表示
*/
...
...
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/adapter/ImageGalleryAdapter.java
0 → 100644
View file @
f193357b
package
jp
.
agentec
.
abook
.
abv
.
ui
.
viewer
.
adapter
;
import
android.content.Context
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.BaseAdapter
;
import
android.widget.ImageView
;
import
java.util.List
;
import
jp.agentec.abook.abv.launcher.android.R
;
import
jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity
;
import
jp.agentec.abook.abv.ui.viewer.view.CustomImage
;
public
class
ImageGalleryAdapter
extends
BaseAdapter
{
Context
context
;
LayoutInflater
inflater
;
List
<
CustomImage
>
imageUriList
;
int
mRowHeight
;
public
ImageGalleryAdapter
(
Context
context
,
List
<
CustomImage
>
imageUriList
,
int
rowHeight
)
{
this
.
context
=
context
;
this
.
imageUriList
=
imageUriList
;
this
.
mRowHeight
=
rowHeight
;
inflater
=
(
LayoutInflater
)
context
.
getSystemService
(
Context
.
LAYOUT_INFLATER_SERVICE
);
}
@Override
public
int
getCount
()
{
return
imageUriList
.
size
();
}
@Override
public
CustomImage
getItem
(
int
position
)
{
return
imageUriList
.
get
(
position
);
}
@Override
public
long
getItemId
(
int
position
)
{
return
position
;
}
@Override
public
View
getView
(
int
position
,
View
convertView
,
ViewGroup
parent
)
{
ViewHolder
holder
;
if
(
convertView
==
null
)
{
convertView
=
inflater
.
inflate
(
R
.
layout
.
item_device_thumbnail
,
null
);
holder
=
new
ViewHolder
();
holder
.
mThumbnail
=
(
ImageView
)
convertView
.
findViewById
(
R
.
id
.
iv_thumbnail
);
holder
.
mThumbnailSelected
=
(
ImageView
)
convertView
.
findViewById
(
R
.
id
.
iv_thumbnail_selected
);
convertView
.
setTag
(
holder
);
}
else
{
holder
=
(
ViewHolder
)
convertView
.
getTag
();
}
holder
.
mThumbnail
.
getLayoutParams
().
height
=
mRowHeight
;
holder
.
mThumbnail
.
requestLayout
();
CustomImage
customImage
=
getItem
(
position
);
boolean
isSelected
=
((
DeviceImageListActivity
)
context
).
containsCustomImageUri
(
customImage
.
mUri
);
if
(
isSelected
)
{
holder
.
mThumbnailSelected
.
setVisibility
(
View
.
VISIBLE
);
}
else
{
holder
.
mThumbnailSelected
.
setVisibility
(
View
.
INVISIBLE
);
}
if
(
holder
.
customImage
==
null
||
!
holder
.
customImage
.
mUri
.
equals
(
customImage
.
mUri
))
{
((
DeviceImageListActivity
)
context
).
mImageFetcher
.
loadImage
(
customImage
.
mUri
,
holder
.
mThumbnail
,
customImage
.
mOrientation
);
holder
.
customImage
=
customImage
;
}
return
convertView
;
}
class
ViewHolder
{
ImageView
mThumbnail
;
ImageView
mThumbnailSelected
;
CustomImage
customImage
;
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/ui/viewer/view/CustomImage.java
0 → 100644
View file @
f193357b
package
jp
.
agentec
.
abook
.
abv
.
ui
.
viewer
.
view
;
import
android.net.Uri
;
/**
* シーン選択画面で表示するイメージ情報
* Created by 金鎭星 on 2020/05/08.
*/
public
class
CustomImage
{
public
Uri
mUri
;
public
int
mOrientation
;
public
CustomImage
(
Uri
uri
,
int
orientation
){
mUri
=
uri
;
mOrientation
=
orientation
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment