├── .gitignore
├── AndroidManifest.xml
├── NOTICE
├── README
├── res
├── drawable-hdpi
│ ├── background.png
│ ├── background_key.png
│ ├── background_number.png
│ ├── background_vumeter.png
│ ├── btn_delete_disabled.9.png
│ ├── btn_delete_normal.9.png
│ ├── btn_delete_pressed.9.png
│ ├── btn_finish_disabled.9.png
│ ├── btn_finish_normal.9.png
│ ├── btn_finish_pressed.9.png
│ ├── btn_new_disabled.9.png
│ ├── btn_new_normal.9.png
│ ├── btn_new_pressed.9.png
│ ├── btn_pause_normal.9.png
│ ├── btn_pause_pressed.9.png
│ ├── btn_play_normal.9.png
│ ├── btn_play_pressed.9.png
│ ├── btn_record_normal.9.png
│ ├── btn_record_pressed.9.png
│ ├── btn_stop_normal.9.png
│ ├── btn_stop_pressed.9.png
│ ├── colon.png
│ ├── empty_icon.png
│ ├── folder.png
│ ├── ic_launcher_soundrecorder.png
│ ├── ic_menu_fm.png
│ ├── ic_menu_view_list.png
│ ├── icon_vumeter.png
│ ├── number_0.png
│ ├── number_1.png
│ ├── number_2.png
│ ├── number_3.png
│ ├── number_4.png
│ ├── number_5.png
│ ├── number_6.png
│ ├── number_7.png
│ ├── number_8.png
│ ├── number_9.png
│ ├── play.png
│ ├── record.png
│ ├── stat_sys_call_record.png
│ ├── stat_sys_call_record_full.png
│ ├── stop.png
│ ├── tape_bottom.png
│ ├── tape_top.png
│ ├── wheel_left.png
│ ├── wheel_right.png
│ ├── wheel_small_left.png
│ └── wheel_small_right.png
├── drawable
│ ├── btn_delete.xml
│ ├── btn_finish.xml
│ ├── btn_new.xml
│ ├── btn_pause.xml
│ ├── btn_play.xml
│ ├── btn_record.xml
│ └── btn_stop.xml
├── layout
│ ├── context_menu_header.xml
│ ├── main.xml
│ └── view_list_menu.xml
├── values-zh-rCN
│ └── strings.xml
├── values
│ ├── arrays.xml
│ └── strings.xml
└── xml
│ └── preferences.xml
└── src
└── net
└── micode
└── soundrecorder
├── RecordNameEditText.java
├── Recorder.java
├── RecorderService.java
├── RemainingTimeCalculator.java
├── SeamlessAnimation.java
├── SoundRecorder.java
├── SoundRecorderPreferenceActivity.java
└── WheelImageView.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # generated files
2 | bin/
3 | gen/
4 |
5 | # Local configuration file (sdk path, etc)
6 | proguard.cfg
7 | project.properties
8 | .settings/
9 | .classpath
10 | .project
11 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 |
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 |
13 |
14 | Apache License
15 | Version 2.0, January 2004
16 | http://www.apache.org/licenses/
17 |
18 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
19 |
20 | 1. Definitions.
21 |
22 | "License" shall mean the terms and conditions for use, reproduction,
23 | and distribution as defined by Sections 1 through 9 of this document.
24 |
25 | "Licensor" shall mean the copyright owner or entity authorized by
26 | the copyright owner that is granting the License.
27 |
28 | "Legal Entity" shall mean the union of the acting entity and all
29 | other entities that control, are controlled by, or are under common
30 | control with that entity. For the purposes of this definition,
31 | "control" means (i) the power, direct or indirect, to cause the
32 | direction or management of such entity, whether by contract or
33 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
34 | outstanding shares, or (iii) beneficial ownership of such entity.
35 |
36 | "You" (or "Your") shall mean an individual or Legal Entity
37 | exercising permissions granted by this License.
38 |
39 | "Source" form shall mean the preferred form for making modifications,
40 | including but not limited to software source code, documentation
41 | source, and configuration files.
42 |
43 | "Object" form shall mean any form resulting from mechanical
44 | transformation or translation of a Source form, including but
45 | not limited to compiled object code, generated documentation,
46 | and conversions to other media types.
47 |
48 | "Work" shall mean the work of authorship, whether in Source or
49 | Object form, made available under the License, as indicated by a
50 | copyright notice that is included in or attached to the work
51 | (an example is provided in the Appendix below).
52 |
53 | "Derivative Works" shall mean any work, whether in Source or Object
54 | form, that is based on (or derived from) the Work and for which the
55 | editorial revisions, annotations, elaborations, or other modifications
56 | represent, as a whole, an original work of authorship. For the purposes
57 | of this License, Derivative Works shall not include works that remain
58 | separable from, or merely link (or bind by name) to the interfaces of,
59 | the Work and Derivative Works thereof.
60 |
61 | "Contribution" shall mean any work of authorship, including
62 | the original version of the Work and any modifications or additions
63 | to that Work or Derivative Works thereof, that is intentionally
64 | submitted to Licensor for inclusion in the Work by the copyright owner
65 | or by an individual or Legal Entity authorized to submit on behalf of
66 | the copyright owner. For the purposes of this definition, "submitted"
67 | means any form of electronic, verbal, or written communication sent
68 | to the Licensor or its representatives, including but not limited to
69 | communication on electronic mailing lists, source code control systems,
70 | and issue tracking systems that are managed by, or on behalf of, the
71 | Licensor for the purpose of discussing and improving the Work, but
72 | excluding communication that is conspicuously marked or otherwise
73 | designated in writing by the copyright owner as "Not a Contribution."
74 |
75 | "Contributor" shall mean Licensor and any individual or Legal Entity
76 | on behalf of whom a Contribution has been received by Licensor and
77 | subsequently incorporated within the Work.
78 |
79 | 2. Grant of Copyright License. Subject to the terms and conditions of
80 | this License, each Contributor hereby grants to You a perpetual,
81 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
82 | copyright license to reproduce, prepare Derivative Works of,
83 | publicly display, publicly perform, sublicense, and distribute the
84 | Work and such Derivative Works in Source or Object form.
85 |
86 | 3. Grant of Patent License. Subject to the terms and conditions of
87 | this License, each Contributor hereby grants to You a perpetual,
88 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
89 | (except as stated in this section) patent license to make, have made,
90 | use, offer to sell, sell, import, and otherwise transfer the Work,
91 | where such license applies only to those patent claims licensable
92 | by such Contributor that are necessarily infringed by their
93 | Contribution(s) alone or by combination of their Contribution(s)
94 | with the Work to which such Contribution(s) was submitted. If You
95 | institute patent litigation against any entity (including a
96 | cross-claim or counterclaim in a lawsuit) alleging that the Work
97 | or a Contribution incorporated within the Work constitutes direct
98 | or contributory patent infringement, then any patent licenses
99 | granted to You under this License for that Work shall terminate
100 | as of the date such litigation is filed.
101 |
102 | 4. Redistribution. You may reproduce and distribute copies of the
103 | Work or Derivative Works thereof in any medium, with or without
104 | modifications, and in Source or Object form, provided that You
105 | meet the following conditions:
106 |
107 | (a) You must give any other recipients of the Work or
108 | Derivative Works a copy of this License; and
109 |
110 | (b) You must cause any modified files to carry prominent notices
111 | stating that You changed the files; and
112 |
113 | (c) You must retain, in the Source form of any Derivative Works
114 | that You distribute, all copyright, patent, trademark, and
115 | attribution notices from the Source form of the Work,
116 | excluding those notices that do not pertain to any part of
117 | the Derivative Works; and
118 |
119 | (d) If the Work includes a "NOTICE" text file as part of its
120 | distribution, then any Derivative Works that You distribute must
121 | include a readable copy of the attribution notices contained
122 | within such NOTICE file, excluding those notices that do not
123 | pertain to any part of the Derivative Works, in at least one
124 | of the following places: within a NOTICE text file distributed
125 | as part of the Derivative Works; within the Source form or
126 | documentation, if provided along with the Derivative Works; or,
127 | within a display generated by the Derivative Works, if and
128 | wherever such third-party notices normally appear. The contents
129 | of the NOTICE file are for informational purposes only and
130 | do not modify the License. You may add Your own attribution
131 | notices within Derivative Works that You distribute, alongside
132 | or as an addendum to the NOTICE text from the Work, provided
133 | that such additional attribution notices cannot be construed
134 | as modifying the License.
135 |
136 | You may add Your own copyright statement to Your modifications and
137 | may provide additional or different license terms and conditions
138 | for use, reproduction, or distribution of Your modifications, or
139 | for any such Derivative Works as a whole, provided Your use,
140 | reproduction, and distribution of the Work otherwise complies with
141 | the conditions stated in this License.
142 |
143 | 5. Submission of Contributions. Unless You explicitly state otherwise,
144 | any Contribution intentionally submitted for inclusion in the Work
145 | by You to the Licensor shall be under the terms and conditions of
146 | this License, without any additional terms or conditions.
147 | Notwithstanding the above, nothing herein shall supersede or modify
148 | the terms of any separate license agreement you may have executed
149 | with Licensor regarding such Contributions.
150 |
151 | 6. Trademarks. This License does not grant permission to use the trade
152 | names, trademarks, service marks, or product names of the Licensor,
153 | except as required for reasonable and customary use in describing the
154 | origin of the Work and reproducing the content of the NOTICE file.
155 |
156 | 7. Disclaimer of Warranty. Unless required by applicable law or
157 | agreed to in writing, Licensor provides the Work (and each
158 | Contributor provides its Contributions) on an "AS IS" BASIS,
159 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
160 | implied, including, without limitation, any warranties or conditions
161 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
162 | PARTICULAR PURPOSE. You are solely responsible for determining the
163 | appropriateness of using or redistributing the Work and assume any
164 | risks associated with Your exercise of permissions under this License.
165 |
166 | 8. Limitation of Liability. In no event and under no legal theory,
167 | whether in tort (including negligence), contract, or otherwise,
168 | unless required by applicable law (such as deliberate and grossly
169 | negligent acts) or agreed to in writing, shall any Contributor be
170 | liable to You for damages, including any direct, indirect, special,
171 | incidental, or consequential damages of any character arising as a
172 | result of this License or out of the use or inability to use the
173 | Work (including but not limited to damages for loss of goodwill,
174 | work stoppage, computer failure or malfunction, or any and all
175 | other commercial damages or losses), even if such Contributor
176 | has been advised of the possibility of such damages.
177 |
178 | 9. Accepting Warranty or Additional Liability. While redistributing
179 | the Work or Derivative Works thereof, You may choose to offer,
180 | and charge a fee for, acceptance of support, warranty, indemnity,
181 | or other liability obligations and/or rights consistent with this
182 | License. However, in accepting such obligations, You may act only
183 | on Your own behalf and on Your sole responsibility, not on behalf
184 | of any other Contributor, and only if You agree to indemnify,
185 | defend, and hold each Contributor harmless for any liability
186 | incurred by, or claims asserted against, such Contributor by reason
187 | of your accepting any such warranty or additional liability.
188 |
189 | END OF TERMS AND CONDITIONS
190 |
191 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | [中文]
2 |
3 | 1. 本项目是MIUI录音机的社区开源版,由MIUI团队(www.miui.com) 发起并贡献第一批代码,遵循NOTICE文件所描述的开源协议,
4 | 今后为MiCode社区(www.micode.net) 拥有,并由社区发布和维护。
5 |
6 | 2. Bug反馈和跟踪,请访问Github,
7 | https://github.com/MiCode/SoundRecorder/issues?sort=created&direction=desc&state=open
8 |
9 | 3. 功能建议和综合讨论,请访问MiCode,
10 | http://micode.net/forum-39-1.html
11 |
12 |
13 | [English]
14 |
15 | 1. This is open source edition of MIUI SoundRecorder, it's first initiated and sponsored by MIUI team (www.miui.com).
16 | It's opened under license described by NOTICE file. It's owned by the MiCode community (www.micode.net). In future,
17 | the MiCode community will release and maintain this project.
18 |
19 | 2. Regarding issue tracking, please visit Github,
20 | https://github.com/MiCode/SoundRecorder/issues?sort=created&direction=desc&state=open
21 |
22 | 3. Regarding feature request and general discussion, please visit Micode forum,
23 | http://micode.net/forum-39-1.html
24 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/background.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/background_key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/background_key.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/background_number.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/background_number.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/background_vumeter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/background_vumeter.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_delete_disabled.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_delete_disabled.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_delete_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_delete_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_delete_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_delete_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_finish_disabled.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_finish_disabled.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_finish_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_finish_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_finish_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_finish_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_new_disabled.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_new_disabled.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_new_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_new_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_new_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_new_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_pause_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_pause_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_pause_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_pause_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_play_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_play_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_play_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_play_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_record_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_record_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_record_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_record_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_stop_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_stop_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_stop_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/btn_stop_pressed.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/colon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/colon.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/empty_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/empty_icon.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/folder.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher_soundrecorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/ic_launcher_soundrecorder.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_fm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/ic_menu_fm.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_view_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/ic_menu_view_list.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon_vumeter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/icon_vumeter.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_0.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_1.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_2.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_3.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_4.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_5.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_6.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_7.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_8.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/number_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/number_9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/play.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/record.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/stat_sys_call_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/stat_sys_call_record.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/stat_sys_call_record_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/stat_sys_call_record_full.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/stop.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/tape_bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/tape_bottom.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/tape_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/tape_top.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/wheel_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/wheel_left.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/wheel_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/wheel_right.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/wheel_small_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/wheel_small_left.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/wheel_small_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MiCode/SoundRecorder/16efc01ecd11e3927d1915be0789856f96483881/res/drawable-hdpi/wheel_small_right.png
--------------------------------------------------------------------------------
/res/drawable/btn_delete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
29 |
31 |
32 |
--------------------------------------------------------------------------------
/res/drawable/btn_finish.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
29 |
31 |
32 |
--------------------------------------------------------------------------------
/res/drawable/btn_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
29 |
31 |
--------------------------------------------------------------------------------
/res/drawable/btn_pause.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
28 |
29 |
--------------------------------------------------------------------------------
/res/drawable/btn_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
28 |
29 |
--------------------------------------------------------------------------------
/res/drawable/btn_record.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
28 |
29 |
--------------------------------------------------------------------------------
/res/drawable/btn_stop.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
23 |
26 |
28 |
29 |
--------------------------------------------------------------------------------
/res/layout/context_menu_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
24 |
25 |
33 |
34 |
42 |
43 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
29 |
30 |
38 |
39 |
40 |
44 |
45 |
49 |
50 |
58 |
59 |
67 |
68 |
76 |
77 |
85 |
86 |
90 |
91 |
98 |
99 |
104 |
105 |
112 |
113 |
114 |
115 |
121 |
122 |
129 |
130 |
131 |
137 |
138 |
145 |
146 |
154 |
155 |
162 |
163 |
164 |
165 |
166 |
171 |
172 |
176 |
177 |
182 |
183 |
189 |
190 |
195 |
196 |
202 |
203 |
209 |
210 |
216 |
217 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/res/layout/view_list_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
--------------------------------------------------------------------------------
/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 | "录音机"
21 | "录音"
22 | "录音停止"
23 | "存储空间已满"
24 | "已达到长度上限"
25 | "需要SD卡才可使用"
26 | "确定"
27 |
28 |
29 | "您的录音"
30 | "录音"
31 | "我的录音"
32 | "无法访问存储器"
33 | "内部应用程序错误"
34 | "无法保存录制的音频"
35 |
36 | 未命名录音
37 | 查看录音列表
38 | 设置
39 | 您确定要删除这段录音吗?
40 | %s已存在。\n确定要覆盖这段录音吗?
41 | 设置
42 | 录音文件类型
43 | 请选择录音文件类型
44 | 使用高质量录音
45 | 高质量录音会增大录音文件
46 | 打开音效
47 | 操作录音机时播放音效
48 | 正在录音...
49 | 录音已终止
50 | 存储空间不足(小于%d分钟)
51 | SD卡不存在
52 | 打开文件夹
53 | 确认要删除所选的录音吗?
54 |
55 |
--------------------------------------------------------------------------------
/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
20 | - 0
21 | - 0
22 | - 5
23 | - 120
24 | - 8
25 | - -5
26 | - 5
27 | - 0
28 | - 5
29 |
30 |
31 |
32 | - 0
33 | - 0
34 | - 5
35 | - 5
36 | - 10
37 | - -3
38 | - 10
39 | - 15
40 | - 10
41 | - -3
42 | - 10
43 | - 25
44 | - 10
45 | - -3
46 | - 10
47 | - 35
48 | - 10
49 | - -3
50 | - 10
51 | - 40
52 | - 10
53 | - -3
54 | - 30
55 | - 120
56 | - 8
57 | - -5
58 | - 5
59 | - 0
60 | - 150
61 | - 50
62 | - 8
63 | - -5
64 | - 5
65 | - 0
66 | - 5
67 |
68 |
69 |
70 | - 0
71 | - 0
72 | - 5
73 | - 5
74 | - 50
75 | - -3
76 | - 5
77 | - 10
78 | - 50
79 | - -3
80 | - 5
81 | - 5
82 | - 50
83 | - -3
84 | - 5
85 | - 10
86 | - 50
87 | - -3
88 | - 5
89 | - 5
90 | - 50
91 | - -3
92 | - 5
93 | - 10
94 | - 50
95 | - -3
96 | - 5
97 | - 5
98 | - 50
99 | - -3
100 | - 5
101 | - 10
102 | - 50
103 | - -3
104 | - 5
105 | - 5
106 | - 50
107 | - -3
108 | - 5
109 | - 10
110 | - 50
111 | - -3
112 | - 5
113 | - 5
114 | - 50
115 | - -3
116 | - 5
117 | - 10
118 | - 50
119 | - -3
120 | - 5
121 | - 5
122 | - 50
123 | - -3
124 | - 5
125 | - 10
126 | - 50
127 | - -3
128 | - 5
129 | - 120
130 | - 8
131 | - -5
132 | - 5
133 | - 0
134 | - 150
135 | - 60
136 | - 8
137 | - -5
138 | - 5
139 | - 0
140 | - 5
141 | - 0
142 | - 10
143 |
144 |
145 |
146 | - 0
147 | - 0
148 | - 5
149 | - 120
150 | - 8
151 | - -5
152 | - 5
153 | - 0
154 | - 150
155 | - 60
156 | - 8
157 | - -5
158 | - 5
159 | - 0
160 | - 5
161 |
162 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
20 | Sound Recorder
21 |
22 |
23 |
24 |
25 |
26 | Recording
27 |
28 | Recording stopped
29 |
30 | Storage is full
31 |
32 | Maximum length reached
33 |
34 | SD card is required
35 |
36 |
37 |
38 |
39 |
40 |
41 | OK
42 |
43 | %02d:%02d
44 |
45 |
46 |
47 | yyyy-MM-dd HH:mm:ss
48 |
49 | Your recordings
50 |
51 | Audio recordings
52 |
53 | My recordings
54 |
55 | Unable to access external or internal storage
56 |
57 | Internal application error
58 |
59 | Unable to save recorded audio
60 |
61 |
62 | untitled record
63 | View record list
64 | Settings
65 | Do you want to delete current record?
66 | %s has already existed. Do you want to overwrite it?
67 | Settings
68 | Record file type
69 | Please select file type
70 | audio/3gpp
71 | Enable high quality
72 | The audio file with high quality can be larger
73 | Enable sound effect
74 | Playing sound effect when the state is changed
75 | Recording...
76 | Recording is stopped
77 | Low storage(less than %d minutes)...
78 | SD card is not available
79 | Open file explorer
80 | Do you want to delete selected record?
81 |
82 |
83 | - amr
84 | - 3gpp
85 |
86 |
87 | - audio/amr
88 | - audio/3gpp
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
20 |
21 |
28 |
33 |
34 |
35 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/RecordNameEditText.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.content.Context;
20 | import android.graphics.Rect;
21 | import android.text.TextUtils;
22 | import android.util.AttributeSet;
23 | import android.view.KeyEvent;
24 | import android.view.inputmethod.InputMethodManager;
25 | import android.widget.EditText;
26 |
27 | import java.io.File;
28 | import java.text.SimpleDateFormat;
29 | import java.util.Calendar;
30 |
31 | public class RecordNameEditText extends EditText {
32 |
33 | private Context mContext;
34 |
35 | private InputMethodManager mInputMethodManager;
36 |
37 | private OnNameChangeListener mNameChangeListener;
38 |
39 | private String mDir;
40 |
41 | private String mExtension;
42 |
43 | private String mOriginalName;
44 |
45 | public interface OnNameChangeListener {
46 |
47 | void onNameChanged(String name);
48 | }
49 |
50 | public RecordNameEditText(Context context) {
51 | super(context, null);
52 | mContext = context;
53 | mInputMethodManager = (InputMethodManager) context
54 | .getSystemService(Context.INPUT_METHOD_SERVICE);
55 | mNameChangeListener = null;
56 | }
57 |
58 | public RecordNameEditText(Context context, AttributeSet attrs) {
59 | super(context, attrs);
60 | mContext = context;
61 | mInputMethodManager = (InputMethodManager) context
62 | .getSystemService(Context.INPUT_METHOD_SERVICE);
63 | mNameChangeListener = null;
64 | }
65 |
66 | public RecordNameEditText(Context context, AttributeSet attrs, int defStyle) {
67 | super(context, attrs, defStyle);
68 | mContext = context;
69 | mInputMethodManager = (InputMethodManager) context
70 | .getSystemService(Context.INPUT_METHOD_SERVICE);
71 | mNameChangeListener = null;
72 | }
73 |
74 | public void setNameChangeListener(OnNameChangeListener listener) {
75 | mNameChangeListener = listener;
76 | }
77 |
78 | public void initFileName(String dir, String extension, boolean englishOnly) {
79 | mDir = dir;
80 | mExtension = extension;
81 |
82 | // initialize the default name
83 | if (!englishOnly) {
84 | setText(getProperFileName(mContext.getString(R.string.default_record_name)));
85 | } else {
86 | SimpleDateFormat dataFormat = new SimpleDateFormat("MMddHHmmss");
87 | setText(getProperFileName("rec_" + dataFormat.format(Calendar.getInstance().getTime())));
88 | }
89 | }
90 |
91 | private String getProperFileName(String name) {
92 | String uniqueName = name;
93 |
94 | if (isFileExisted(uniqueName)) {
95 | int i = 2;
96 | while (true) {
97 | String temp = uniqueName + "(" + i + ")";
98 | if (!isFileExisted(temp)) {
99 | uniqueName = temp;
100 | break;
101 | }
102 | i++;
103 | }
104 | }
105 | return uniqueName;
106 | }
107 |
108 | private boolean isFileExisted(String name) {
109 | String fullName = mDir + "/" + name.trim() + mExtension;
110 | File file = new File(fullName);
111 | return file.exists();
112 | }
113 |
114 | @Override
115 | public boolean onKeyUp(int keyCode, KeyEvent event) {
116 | switch (keyCode) {
117 | case KeyEvent.KEYCODE_ENTER:
118 | if (mNameChangeListener != null) {
119 | String name = getText().toString().trim();
120 | if (!TextUtils.isEmpty(name)) {
121 | // use new name
122 | setText(name);
123 | mNameChangeListener.onNameChanged(name);
124 |
125 | } else {
126 | // use original name
127 | setText(mOriginalName);
128 | }
129 | clearFocus();
130 |
131 | // hide the keyboard
132 | mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
133 | return true;
134 | }
135 | break;
136 | default:
137 | break;
138 | }
139 | return super.onKeyUp(keyCode, event);
140 | }
141 |
142 | @Override
143 | protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
144 | super.onFocusChanged(focused, direction, previouslyFocusedRect);
145 | if (!focused && mNameChangeListener != null) {
146 | String name = getText().toString().trim();
147 | if (!TextUtils.isEmpty(name)) {
148 | // use new name
149 | setText(name);
150 | mNameChangeListener.onNameChanged(name);
151 |
152 | } else {
153 | // use original name
154 | setText(mOriginalName);
155 | }
156 |
157 | // hide the keyboard
158 | mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
159 | } else if (focused) {
160 | mOriginalName = getText().toString();
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/Recorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 |
22 | import android.content.Context;
23 | import android.media.MediaPlayer;
24 | import android.media.MediaPlayer.OnCompletionListener;
25 | import android.media.MediaPlayer.OnErrorListener;
26 | import android.os.Bundle;
27 | import android.os.Environment;
28 | import android.text.TextUtils;
29 |
30 | public class Recorder implements OnCompletionListener, OnErrorListener {
31 | private static final String SAMPLE_PREFIX = "recording";
32 |
33 | private static final String SAMPLE_PATH_KEY = "sample_path";
34 |
35 | private static final String SAMPLE_LENGTH_KEY = "sample_length";
36 |
37 | public static final String SAMPLE_DEFAULT_DIR = "/sound_recorder";
38 |
39 | public static final int IDLE_STATE = 0;
40 |
41 | public static final int RECORDING_STATE = 1;
42 |
43 | public static final int PLAYING_STATE = 2;
44 |
45 | public static final int PLAYING_PAUSED_STATE = 3;
46 |
47 | private int mState = IDLE_STATE;
48 |
49 | public static final int NO_ERROR = 0;
50 |
51 | public static final int STORAGE_ACCESS_ERROR = 1;
52 |
53 | public static final int INTERNAL_ERROR = 2;
54 |
55 | public static final int IN_CALL_RECORD_ERROR = 3;
56 |
57 | public interface OnStateChangedListener {
58 | public void onStateChanged(int state);
59 |
60 | public void onError(int error);
61 | }
62 |
63 | private Context mContext;
64 |
65 | private OnStateChangedListener mOnStateChangedListener = null;
66 |
67 | private long mSampleStart = 0; // time at which latest record or play
68 | // operation started
69 |
70 | private int mSampleLength = 0; // length of current sample
71 |
72 | private File mSampleFile = null;
73 |
74 | private File mSampleDir = null;
75 |
76 | private MediaPlayer mPlayer = null;
77 |
78 | public Recorder(Context context) {
79 | mContext = context;
80 | File sampleDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
81 | + SAMPLE_DEFAULT_DIR);
82 | if (!sampleDir.exists()) {
83 | sampleDir.mkdirs();
84 | }
85 | mSampleDir = sampleDir;
86 |
87 | syncStateWithService();
88 | }
89 |
90 | public boolean syncStateWithService() {
91 | if (RecorderService.isRecording()) {
92 | mState = RECORDING_STATE;
93 | mSampleStart = RecorderService.getStartTime();
94 | mSampleFile = new File(RecorderService.getFilePath());
95 | return true;
96 | } else if (mState == RECORDING_STATE) {
97 | // service is idle but local state is recording
98 | return false;
99 | } else if (mSampleFile != null && mSampleLength == 0) {
100 | // this state can be reached if there is an incoming call
101 | // the record service is stopped by incoming call without notifying
102 | // the UI
103 | return false;
104 | }
105 | return true;
106 | }
107 |
108 | public void saveState(Bundle recorderState) {
109 | recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath());
110 | recorderState.putInt(SAMPLE_LENGTH_KEY, mSampleLength);
111 | }
112 |
113 | public String getRecordDir() {
114 | return mSampleDir.getAbsolutePath();
115 | }
116 |
117 | public int getMaxAmplitude() {
118 | if (mState != RECORDING_STATE)
119 | return 0;
120 | return RecorderService.getMaxAmplitude();
121 | }
122 |
123 | public void restoreState(Bundle recorderState) {
124 | String samplePath = recorderState.getString(SAMPLE_PATH_KEY);
125 | if (samplePath == null)
126 | return;
127 | int sampleLength = recorderState.getInt(SAMPLE_LENGTH_KEY, -1);
128 | if (sampleLength == -1)
129 | return;
130 |
131 | File file = new File(samplePath);
132 | if (!file.exists())
133 | return;
134 | if (mSampleFile != null
135 | && mSampleFile.getAbsolutePath().compareTo(file.getAbsolutePath()) == 0)
136 | return;
137 |
138 | delete();
139 | mSampleFile = file;
140 | mSampleLength = sampleLength;
141 |
142 | signalStateChanged(IDLE_STATE);
143 | }
144 |
145 | public void setOnStateChangedListener(OnStateChangedListener listener) {
146 | mOnStateChangedListener = listener;
147 | }
148 |
149 | public int state() {
150 | return mState;
151 | }
152 |
153 | public int progress() {
154 | if (mState == RECORDING_STATE) {
155 | return (int) ((System.currentTimeMillis() - mSampleStart) / 1000);
156 | } else if (mState == PLAYING_STATE || mState == PLAYING_PAUSED_STATE) {
157 | if (mPlayer != null) {
158 | return (int) (mPlayer.getCurrentPosition() / 1000);
159 | }
160 | }
161 |
162 | return 0;
163 | }
164 |
165 | public float playProgress() {
166 | if (mPlayer != null) {
167 | return ((float) mPlayer.getCurrentPosition()) / mPlayer.getDuration();
168 | }
169 | return 0.0f;
170 | }
171 |
172 | public int sampleLength() {
173 | return mSampleLength;
174 | }
175 |
176 | public File sampleFile() {
177 | return mSampleFile;
178 | }
179 |
180 | public void renameSampleFile(String name) {
181 | if (mSampleFile != null && mState != RECORDING_STATE && mState != PLAYING_STATE) {
182 | if (!TextUtils.isEmpty(name)) {
183 | String oldName = mSampleFile.getAbsolutePath();
184 | String extension = oldName.substring(oldName.lastIndexOf('.'));
185 | File newFile = new File(mSampleFile.getParent() + "/" + name + extension);
186 | if (!TextUtils.equals(oldName, newFile.getAbsolutePath())) {
187 | if (mSampleFile.renameTo(newFile)) {
188 | mSampleFile = newFile;
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | /**
196 | * Resets the recorder state. If a sample was recorded, the file is deleted.
197 | */
198 | public void delete() {
199 | stop();
200 |
201 | if (mSampleFile != null)
202 | mSampleFile.delete();
203 |
204 | mSampleFile = null;
205 | mSampleLength = 0;
206 |
207 | signalStateChanged(IDLE_STATE);
208 | }
209 |
210 | /**
211 | * Resets the recorder state. If a sample was recorded, the file is left on
212 | * disk and will be reused for a new recording.
213 | */
214 | public void clear() {
215 | stop();
216 | mSampleLength = 0;
217 | signalStateChanged(IDLE_STATE);
218 | }
219 |
220 | public void reset() {
221 | stop();
222 |
223 | mSampleLength = 0;
224 | mSampleFile = null;
225 | mState = IDLE_STATE;
226 |
227 | File sampleDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
228 | + SAMPLE_DEFAULT_DIR);
229 | if (!sampleDir.exists()) {
230 | sampleDir.mkdirs();
231 | }
232 | mSampleDir = sampleDir;
233 |
234 | signalStateChanged(IDLE_STATE);
235 | }
236 |
237 | public boolean isRecordExisted(String path) {
238 | if (!TextUtils.isEmpty(path)) {
239 | File file = new File(mSampleDir.getAbsolutePath() + "/" + path);
240 | return file.exists();
241 | }
242 | return false;
243 | }
244 |
245 | public void startRecording(int outputfileformat, String name, String extension,
246 | boolean highQuality, long maxFileSize) {
247 | stop();
248 |
249 | if (mSampleFile == null) {
250 | try {
251 | mSampleFile = File.createTempFile(SAMPLE_PREFIX, extension, mSampleDir);
252 | renameSampleFile(name);
253 | } catch (IOException e) {
254 | setError(STORAGE_ACCESS_ERROR);
255 | return;
256 | }
257 | }
258 |
259 | RecorderService.startRecording(mContext, outputfileformat, mSampleFile.getAbsolutePath(),
260 | highQuality, maxFileSize);
261 | mSampleStart = System.currentTimeMillis();
262 | }
263 |
264 | public void stopRecording() {
265 | if (RecorderService.isRecording()) {
266 | RecorderService.stopRecording(mContext);
267 | mSampleLength = (int) ((System.currentTimeMillis() - mSampleStart) / 1000);
268 | if (mSampleLength == 0) {
269 | // round up to 1 second if it's too short
270 | mSampleLength = 1;
271 | }
272 | }
273 | }
274 |
275 | public void startPlayback(float percentage) {
276 | if (state() == PLAYING_PAUSED_STATE) {
277 | mSampleStart = System.currentTimeMillis() - mPlayer.getCurrentPosition();
278 | mPlayer.seekTo((int) (percentage * mPlayer.getDuration()));
279 | mPlayer.start();
280 | setState(PLAYING_STATE);
281 | } else {
282 | stop();
283 |
284 | mPlayer = new MediaPlayer();
285 | try {
286 | mPlayer.setDataSource(mSampleFile.getAbsolutePath());
287 | mPlayer.setOnCompletionListener(this);
288 | mPlayer.setOnErrorListener(this);
289 | mPlayer.prepare();
290 | mPlayer.seekTo((int) (percentage * mPlayer.getDuration()));
291 | mPlayer.start();
292 | } catch (IllegalArgumentException e) {
293 | setError(INTERNAL_ERROR);
294 | mPlayer = null;
295 | return;
296 | } catch (IOException e) {
297 | setError(STORAGE_ACCESS_ERROR);
298 | mPlayer = null;
299 | return;
300 | }
301 |
302 | mSampleStart = System.currentTimeMillis();
303 | setState(PLAYING_STATE);
304 | }
305 | }
306 |
307 | public void pausePlayback() {
308 | if (mPlayer == null) {
309 | return;
310 | }
311 |
312 | mPlayer.pause();
313 | setState(PLAYING_PAUSED_STATE);
314 | }
315 |
316 | public void stopPlayback() {
317 | if (mPlayer == null) // we were not in playback
318 | return;
319 |
320 | mPlayer.stop();
321 | mPlayer.release();
322 | mPlayer = null;
323 | setState(IDLE_STATE);
324 | }
325 |
326 | public void stop() {
327 | stopRecording();
328 | stopPlayback();
329 | }
330 |
331 | public boolean onError(MediaPlayer mp, int what, int extra) {
332 | stop();
333 | setError(STORAGE_ACCESS_ERROR);
334 | return true;
335 | }
336 |
337 | public void onCompletion(MediaPlayer mp) {
338 | stop();
339 | }
340 |
341 | public void setState(int state) {
342 | if (state == mState)
343 | return;
344 |
345 | mState = state;
346 | signalStateChanged(mState);
347 | }
348 |
349 | private void signalStateChanged(int state) {
350 | if (mOnStateChangedListener != null)
351 | mOnStateChangedListener.onStateChanged(state);
352 | }
353 |
354 | public void setError(int error) {
355 | if (mOnStateChangedListener != null)
356 | mOnStateChangedListener.onError(error);
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/RecorderService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.app.KeyguardManager;
20 | import android.app.Notification;
21 | import android.app.NotificationManager;
22 | import android.app.PendingIntent;
23 | import android.app.Service;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.media.AudioManager;
27 | import android.media.MediaRecorder;
28 | import android.net.Uri;
29 | import android.os.Bundle;
30 | import android.os.Handler;
31 | import android.os.IBinder;
32 | import android.os.PowerManager;
33 | import android.os.PowerManager.WakeLock;
34 | import android.telephony.PhoneStateListener;
35 | import android.telephony.TelephonyManager;
36 |
37 | import java.io.File;
38 | import java.io.IOException;
39 |
40 | public class RecorderService extends Service implements MediaRecorder.OnErrorListener {
41 |
42 | public final static String ACTION_NAME = "action_type";
43 |
44 | public final static int ACTION_INVALID = 0;
45 |
46 | public final static int ACTION_START_RECORDING = 1;
47 |
48 | public final static int ACTION_STOP_RECORDING = 2;
49 |
50 | public final static int ACTION_ENABLE_MONITOR_REMAIN_TIME = 3;
51 |
52 | public final static int ACTION_DISABLE_MONITOR_REMAIN_TIME = 4;
53 |
54 | public final static String ACTION_PARAM_FORMAT = "format";
55 |
56 | public final static String ACTION_PARAM_PATH = "path";
57 |
58 | public final static String ACTION_PARAM_HIGH_QUALITY = "high_quality";
59 |
60 | public final static String ACTION_PARAM_MAX_FILE_SIZE = "max_file_size";
61 |
62 | public final static String RECORDER_SERVICE_BROADCAST_NAME = "com.android.soundrecorder.broadcast";
63 |
64 | public final static String RECORDER_SERVICE_BROADCAST_STATE = "is_recording";
65 |
66 | public final static String RECORDER_SERVICE_BROADCAST_ERROR = "error_code";
67 |
68 | public final static int NOTIFICATION_ID = 62343234;
69 |
70 | private static MediaRecorder mRecorder = null;
71 |
72 | private static String mFilePath = null;
73 |
74 | private static long mStartTime = 0;
75 |
76 | private RemainingTimeCalculator mRemainingTimeCalculator;
77 |
78 | private NotificationManager mNotifiManager;
79 |
80 | private Notification mLowStorageNotification;
81 |
82 | private TelephonyManager mTeleManager;
83 |
84 | private WakeLock mWakeLock;
85 |
86 | private KeyguardManager mKeyguardManager;
87 |
88 | private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
89 | @Override
90 | public void onCallStateChanged(int state, String incomingNumber) {
91 | if (state != TelephonyManager.CALL_STATE_IDLE) {
92 | localStopRecording();
93 | }
94 | }
95 | };
96 |
97 | private final Handler mHandler = new Handler();
98 |
99 | private Runnable mUpdateRemainingTime = new Runnable() {
100 | public void run() {
101 | if (mRecorder != null && mNeedUpdateRemainingTime) {
102 | updateRemainingTime();
103 | }
104 | }
105 | };
106 |
107 | private boolean mNeedUpdateRemainingTime;
108 |
109 | @Override
110 | public void onCreate() {
111 | super.onCreate();
112 | mRecorder = null;
113 | mLowStorageNotification = null;
114 | mRemainingTimeCalculator = new RemainingTimeCalculator();
115 | mNeedUpdateRemainingTime = false;
116 | mNotifiManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
117 | mTeleManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
118 | mTeleManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
119 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
120 | mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SoundRecorder");
121 | mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
122 | }
123 |
124 | @Override
125 | public int onStartCommand(Intent intent, int flags, int startId) {
126 | Bundle bundle = intent.getExtras();
127 | if (bundle != null && bundle.containsKey(ACTION_NAME)) {
128 | switch (bundle.getInt(ACTION_NAME, ACTION_INVALID)) {
129 | case ACTION_START_RECORDING:
130 | localStartRecording(bundle.getInt(ACTION_PARAM_FORMAT),
131 | bundle.getString(ACTION_PARAM_PATH),
132 | bundle.getBoolean(ACTION_PARAM_HIGH_QUALITY),
133 | bundle.getLong(ACTION_PARAM_MAX_FILE_SIZE));
134 | break;
135 | case ACTION_STOP_RECORDING:
136 | localStopRecording();
137 | break;
138 | case ACTION_ENABLE_MONITOR_REMAIN_TIME:
139 | if (mRecorder != null) {
140 | mNeedUpdateRemainingTime = true;
141 | mHandler.post(mUpdateRemainingTime);
142 | }
143 | break;
144 | case ACTION_DISABLE_MONITOR_REMAIN_TIME:
145 | mNeedUpdateRemainingTime = false;
146 | if (mRecorder != null) {
147 | showRecordingNotification();
148 | }
149 | break;
150 | default:
151 | break;
152 | }
153 | return START_STICKY;
154 | }
155 | return super.onStartCommand(intent, flags, startId);
156 | }
157 |
158 | @Override
159 | public void onDestroy() {
160 | mTeleManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
161 | if (mWakeLock.isHeld()) {
162 | mWakeLock.release();
163 | }
164 | super.onDestroy();
165 | }
166 |
167 | @Override
168 | public IBinder onBind(Intent intent) {
169 | return null;
170 | }
171 |
172 | @Override
173 | public void onLowMemory() {
174 | localStopRecording();
175 | super.onLowMemory();
176 | }
177 |
178 | private void localStartRecording(int outputfileformat, String path, boolean highQuality,
179 | long maxFileSize) {
180 | if (mRecorder == null) {
181 | mRemainingTimeCalculator.reset();
182 | if (maxFileSize != -1) {
183 | mRemainingTimeCalculator.setFileSizeLimit(new File(path), maxFileSize);
184 | }
185 |
186 | mRecorder = new MediaRecorder();
187 | mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
188 | if (outputfileformat == MediaRecorder.OutputFormat.THREE_GPP) {
189 | mRemainingTimeCalculator.setBitRate(SoundRecorder.BITRATE_3GPP);
190 | mRecorder.setAudioSamplingRate(highQuality ? 44100 : 22050);
191 | mRecorder.setOutputFormat(outputfileformat);
192 | mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
193 | } else {
194 | mRemainingTimeCalculator.setBitRate(SoundRecorder.BITRATE_AMR);
195 | mRecorder.setAudioSamplingRate(highQuality ? 16000 : 8000);
196 | mRecorder.setOutputFormat(outputfileformat);
197 | mRecorder.setAudioEncoder(highQuality ? MediaRecorder.AudioEncoder.AMR_WB
198 | : MediaRecorder.AudioEncoder.AMR_NB);
199 | }
200 | mRecorder.setOutputFile(path);
201 | mRecorder.setOnErrorListener(this);
202 |
203 | // Handle IOException
204 | try {
205 | mRecorder.prepare();
206 | } catch (IOException exception) {
207 | sendErrorBroadcast(Recorder.INTERNAL_ERROR);
208 | mRecorder.reset();
209 | mRecorder.release();
210 | mRecorder = null;
211 | return;
212 | }
213 | // Handle RuntimeException if the recording couldn't start
214 | try {
215 | mRecorder.start();
216 | } catch (RuntimeException exception) {
217 | AudioManager audioMngr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
218 | boolean isInCall = (audioMngr.getMode() == AudioManager.MODE_IN_CALL);
219 | if (isInCall) {
220 | sendErrorBroadcast(Recorder.IN_CALL_RECORD_ERROR);
221 | } else {
222 | sendErrorBroadcast(Recorder.INTERNAL_ERROR);
223 | }
224 | mRecorder.reset();
225 | mRecorder.release();
226 | mRecorder = null;
227 | return;
228 | }
229 | mFilePath = path;
230 | mStartTime = System.currentTimeMillis();
231 | mWakeLock.acquire();
232 | mNeedUpdateRemainingTime = false;
233 | sendStateBroadcast();
234 | showRecordingNotification();
235 | }
236 | }
237 |
238 | private void localStopRecording() {
239 | if (mRecorder != null) {
240 | mNeedUpdateRemainingTime = false;
241 | try {
242 | mRecorder.stop();
243 | } catch (RuntimeException e) {
244 | }
245 | mRecorder.release();
246 | mRecorder = null;
247 |
248 | sendStateBroadcast();
249 | showStoppedNotification();
250 | }
251 | stopSelf();
252 | }
253 |
254 | private void showRecordingNotification() {
255 | Notification notification = new Notification(R.drawable.stat_sys_call_record,
256 | getString(R.string.notification_recording), System.currentTimeMillis());
257 | notification.flags = Notification.FLAG_ONGOING_EVENT;
258 | PendingIntent pendingIntent;
259 | pendingIntent = PendingIntent
260 | .getActivity(this, 0, new Intent(this, SoundRecorder.class), 0);
261 |
262 | notification.setLatestEventInfo(this, getString(R.string.app_name),
263 | getString(R.string.notification_recording), pendingIntent);
264 |
265 | startForeground(NOTIFICATION_ID, notification);
266 | }
267 |
268 | private void showLowStorageNotification(int minutes) {
269 | if (mKeyguardManager.inKeyguardRestrictedInputMode()) {
270 | // it's not necessary to show this notification in lock-screen
271 | return;
272 | }
273 |
274 | if (mLowStorageNotification == null) {
275 | mLowStorageNotification = new Notification(R.drawable.stat_sys_call_record_full,
276 | getString(R.string.notification_recording), System.currentTimeMillis());
277 | mLowStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
278 | }
279 |
280 | PendingIntent pendingIntent;
281 | pendingIntent = PendingIntent
282 | .getActivity(this, 0, new Intent(this, SoundRecorder.class), 0);
283 |
284 | mLowStorageNotification.setLatestEventInfo(this, getString(R.string.app_name),
285 | getString(R.string.notification_warning, minutes), pendingIntent);
286 | startForeground(NOTIFICATION_ID, mLowStorageNotification);
287 | }
288 |
289 | private void showStoppedNotification() {
290 | stopForeground(true);
291 | mLowStorageNotification = null;
292 |
293 | Notification notification = new Notification(R.drawable.stat_sys_call_record,
294 | getString(R.string.notification_stopped), System.currentTimeMillis());
295 | notification.flags = Notification.FLAG_AUTO_CANCEL;
296 | Intent intent = new Intent(Intent.ACTION_VIEW);
297 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
298 | intent.setType("audio/*");
299 | intent.setDataAndType(Uri.fromFile(new File(mFilePath)), "audio/*");
300 |
301 | PendingIntent pendingIntent;
302 | pendingIntent = PendingIntent.getActivity(this, 0, intent,
303 | PendingIntent.FLAG_UPDATE_CURRENT);
304 |
305 | notification.setLatestEventInfo(this, getString(R.string.app_name),
306 | getString(R.string.notification_stopped), pendingIntent);
307 | mNotifiManager.notify(NOTIFICATION_ID, notification);
308 | }
309 |
310 | private void sendStateBroadcast() {
311 | Intent intent = new Intent(RECORDER_SERVICE_BROADCAST_NAME);
312 | intent.putExtra(RECORDER_SERVICE_BROADCAST_STATE, mRecorder != null);
313 | sendBroadcast(intent);
314 | }
315 |
316 | private void sendErrorBroadcast(int error) {
317 | Intent intent = new Intent(RECORDER_SERVICE_BROADCAST_NAME);
318 | intent.putExtra(RECORDER_SERVICE_BROADCAST_ERROR, error);
319 | sendBroadcast(intent);
320 | }
321 |
322 | private void updateRemainingTime() {
323 | long t = mRemainingTimeCalculator.timeRemaining();
324 | if (t <= 0) {
325 | localStopRecording();
326 | return;
327 | } else if (t <= 1800
328 | && mRemainingTimeCalculator.currentLowerLimit() != RemainingTimeCalculator.FILE_SIZE_LIMIT) {
329 | // less than half one hour
330 | showLowStorageNotification((int) Math.ceil(t / 60.0));
331 | }
332 |
333 | if (mRecorder != null && mNeedUpdateRemainingTime) {
334 | mHandler.postDelayed(mUpdateRemainingTime, 500);
335 | }
336 | }
337 |
338 | public static boolean isRecording() {
339 | return mRecorder != null;
340 | }
341 |
342 | public static String getFilePath() {
343 | return mFilePath;
344 | }
345 |
346 | public static long getStartTime() {
347 | return mStartTime;
348 | }
349 |
350 | public static void startRecording(Context context, int outputfileformat, String path,
351 | boolean highQuality, long maxFileSize) {
352 | Intent intent = new Intent(context, RecorderService.class);
353 | intent.putExtra(ACTION_NAME, ACTION_START_RECORDING);
354 | intent.putExtra(ACTION_PARAM_FORMAT, outputfileformat);
355 | intent.putExtra(ACTION_PARAM_PATH, path);
356 | intent.putExtra(ACTION_PARAM_HIGH_QUALITY, highQuality);
357 | intent.putExtra(ACTION_PARAM_MAX_FILE_SIZE, maxFileSize);
358 | context.startService(intent);
359 | }
360 |
361 | public static void stopRecording(Context context) {
362 | Intent intent = new Intent(context, RecorderService.class);
363 | intent.putExtra(ACTION_NAME, ACTION_STOP_RECORDING);
364 | context.startService(intent);
365 | }
366 |
367 | public static int getMaxAmplitude() {
368 | return mRecorder == null ? 0 : mRecorder.getMaxAmplitude();
369 | }
370 |
371 | @Override
372 | public void onError(MediaRecorder mr, int what, int extra) {
373 | sendErrorBroadcast(Recorder.INTERNAL_ERROR);
374 | localStopRecording();
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/RemainingTimeCalculator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.os.Environment;
20 | import android.os.StatFs;
21 |
22 | import java.io.File;
23 |
24 | /**
25 | * Calculates remaining recording time based on available disk space and
26 | * optionally a maximum recording file size. The reason why this is not trivial
27 | * is that the file grows in blocks every few seconds or so, while we want a
28 | * smooth countdown.
29 | */
30 |
31 | public class RemainingTimeCalculator {
32 | public static final int UNKNOWN_LIMIT = 0;
33 |
34 | public static final int FILE_SIZE_LIMIT = 1;
35 |
36 | public static final int DISK_SPACE_LIMIT = 2;
37 |
38 | private static final int EXTERNAL_STORAGE_BLOCK_THREADHOLD = 32;
39 |
40 | // which of the two limits we will hit (or have fit) first
41 | private int mCurrentLowerLimit = UNKNOWN_LIMIT;
42 |
43 | // State for tracking file size of recording.
44 | private File mRecordingFile;
45 |
46 | private long mMaxBytes;
47 |
48 | // Rate at which the file grows
49 | private int mBytesPerSecond;
50 |
51 | // time at which number of free blocks last changed
52 | private long mBlocksChangedTime;
53 |
54 | // number of available blocks at that time
55 | private long mLastBlocks;
56 |
57 | // time at which the size of the file has last changed
58 | private long mFileSizeChangedTime;
59 |
60 | // size of the file at that time
61 | private long mLastFileSize;
62 |
63 | public RemainingTimeCalculator() {
64 | }
65 |
66 | /**
67 | * If called, the calculator will return the minimum of two estimates: how
68 | * long until we run out of disk space and how long until the file reaches
69 | * the specified size.
70 | *
71 | * @param file the file to watch
72 | * @param maxBytes the limit
73 | */
74 |
75 | public void setFileSizeLimit(File file, long maxBytes) {
76 | mRecordingFile = file;
77 | mMaxBytes = maxBytes;
78 | }
79 |
80 | /**
81 | * Resets the interpolation.
82 | */
83 | public void reset() {
84 | mCurrentLowerLimit = UNKNOWN_LIMIT;
85 | mBlocksChangedTime = -1;
86 | mFileSizeChangedTime = -1;
87 | }
88 |
89 | /**
90 | * Returns how long (in seconds) we can continue recording.
91 | */
92 | public long timeRemaining() {
93 | // Calculate how long we can record based on free disk space
94 | StatFs fs = null;
95 | long blocks = -1;
96 | long blockSize = -1;
97 | long now = System.currentTimeMillis();
98 |
99 | fs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
100 | blocks = fs.getAvailableBlocks() - EXTERNAL_STORAGE_BLOCK_THREADHOLD;
101 | blockSize = fs.getBlockSize();
102 | if (blocks < 0) {
103 | blocks = 0;
104 | }
105 |
106 | if (mBlocksChangedTime == -1 || blocks != mLastBlocks) {
107 | mBlocksChangedTime = now;
108 | mLastBlocks = blocks;
109 | }
110 |
111 | /*
112 | * The calculation below always leaves one free block, since free space
113 | * in the block we're currently writing to is not added. This last block
114 | * might get nibbled when we close and flush the file, but we won't run
115 | * out of disk.
116 | */
117 |
118 | // at mBlocksChangedTime we had this much time
119 | long result = mLastBlocks * blockSize / mBytesPerSecond;
120 | // so now we have this much time
121 | result -= (now - mBlocksChangedTime) / 1000;
122 |
123 | if (mRecordingFile == null) {
124 | mCurrentLowerLimit = DISK_SPACE_LIMIT;
125 | return result;
126 | }
127 |
128 | // If we have a recording file set, we calculate a second estimate
129 | // based on how long it will take us to reach mMaxBytes.
130 |
131 | mRecordingFile = new File(mRecordingFile.getAbsolutePath());
132 | long fileSize = mRecordingFile.length();
133 | if (mFileSizeChangedTime == -1 || fileSize != mLastFileSize) {
134 | mFileSizeChangedTime = now;
135 | mLastFileSize = fileSize;
136 | }
137 |
138 | long result2 = (mMaxBytes - fileSize) / mBytesPerSecond;
139 | result2 -= (now - mFileSizeChangedTime) / 1000;
140 | result2 -= 1; // just for safety
141 |
142 | mCurrentLowerLimit = result < result2 ? DISK_SPACE_LIMIT : FILE_SIZE_LIMIT;
143 |
144 | return Math.min(result, result2);
145 | }
146 |
147 | /**
148 | * Indicates which limit we will hit (or have hit) first, by returning one
149 | * of FILE_SIZE_LIMIT or DISK_SPACE_LIMIT or UNKNOWN_LIMIT. We need this to
150 | * display the correct message to the user when we hit one of the limits.
151 | */
152 | public int currentLowerLimit() {
153 | return mCurrentLowerLimit;
154 | }
155 |
156 | /**
157 | * Is there any point of trying to start recording?
158 | */
159 | public boolean diskSpaceAvailable() {
160 | StatFs fs = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
161 | // keep one free block
162 | return fs.getAvailableBlocks() > EXTERNAL_STORAGE_BLOCK_THREADHOLD;
163 | }
164 |
165 | /**
166 | * Sets the bit rate used in the interpolation.
167 | *
168 | * @param bitRate the bit rate to set in bits/sec.
169 | */
170 | public void setBitRate(int bitRate) {
171 | mBytesPerSecond = bitRate / 8;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/SeamlessAnimation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.view.animation.Animation;
20 | import android.view.animation.Transformation;
21 |
22 | public class SeamlessAnimation extends Animation {
23 |
24 | private float mFromDegrees;
25 |
26 | private float mToDegrees;
27 |
28 | private float mPivotX;
29 |
30 | private float mPivotY;
31 |
32 | private int mPivotXType;
33 |
34 | private float mPivotXValue;
35 |
36 | private int mPivotYType;
37 |
38 | private float mPivotYValue;
39 |
40 | private boolean mCancelled;
41 |
42 | private float mDegree;
43 |
44 | public SeamlessAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
45 | int pivotYType, float pivotYValue) {
46 | mFromDegrees = fromDegrees;
47 | mToDegrees = toDegrees;
48 | mPivotXType = pivotXType;
49 | mPivotXValue = pivotXValue;
50 | mPivotYType = pivotYType;
51 | mPivotYValue = pivotYValue;
52 | mCancelled = false;
53 | mDegree = fromDegrees;
54 | }
55 |
56 | public void initialize(int width, int height, int parentWidth, int parentHeight) {
57 | super.initialize(width, height, parentWidth, parentHeight);
58 | mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
59 | mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
60 | }
61 |
62 | public float getDegree() {
63 | return mDegree;
64 | }
65 |
66 | @Override
67 | public void cancel() {
68 | super.cancel();
69 | mCancelled = true;
70 | }
71 |
72 | @Override
73 | protected void applyTransformation(float interpolatedTime, Transformation t) {
74 | if (!mCancelled) {
75 | mDegree = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
76 | }
77 | t.getMatrix().setRotate(mDegree, mPivotX, mPivotY);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/SoundRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.app.Activity;
20 | import android.app.AlertDialog;
21 | import android.app.NotificationManager;
22 | import android.content.BroadcastReceiver;
23 | import android.content.ContentResolver;
24 | import android.content.ContentValues;
25 | import android.content.Context;
26 | import android.content.DialogInterface;
27 | import android.content.Intent;
28 | import android.content.IntentFilter;
29 | import android.content.res.Configuration;
30 | import android.content.res.Resources;
31 | import android.database.Cursor;
32 | import android.media.AudioManager;
33 | import android.media.MediaRecorder;
34 | import android.media.SoundPool;
35 | import android.net.Uri;
36 | import android.os.Build;
37 | import android.os.Bundle;
38 | import android.os.Environment;
39 | import android.os.Handler;
40 | import android.provider.MediaStore;
41 | import android.text.TextUtils;
42 | import android.util.Log;
43 | import android.view.KeyEvent;
44 | import android.view.Menu;
45 | import android.view.MenuItem;
46 | import android.view.View;
47 | import android.view.ViewGroup.LayoutParams;
48 | import android.widget.Button;
49 | import android.widget.ImageButton;
50 | import android.widget.ImageView;
51 | import android.widget.LinearLayout;
52 | import android.widget.SeekBar;
53 | import android.widget.TextView;
54 | import android.widget.Toast;
55 |
56 | import java.io.File;
57 | import java.text.SimpleDateFormat;
58 | import java.util.Date;
59 | import java.util.HashSet;
60 |
61 | public class SoundRecorder extends Activity implements Button.OnClickListener,
62 | Recorder.OnStateChangedListener {
63 | private static final String TAG = "SoundRecorder";
64 |
65 | private static final String RECORDER_STATE_KEY = "recorder_state";
66 |
67 | private static final String SAMPLE_INTERRUPTED_KEY = "sample_interrupted";
68 |
69 | private static final String MAX_FILE_SIZE_KEY = "max_file_size";
70 |
71 | private static final String AUDIO_3GPP = "audio/3gpp";
72 |
73 | private static final String AUDIO_AMR = "audio/amr";
74 |
75 | private static final String AUDIO_ANY = "audio/*";
76 |
77 | private static final String ANY_ANY = "*/*";
78 |
79 | private static final String FILE_EXTENSION_AMR = ".amr";
80 |
81 | private static final String FILE_EXTENSION_3GPP = ".3gpp";
82 |
83 | public static final int BITRATE_AMR = 2 * 1024 * 8; // bits/sec
84 |
85 | public static final int BITRATE_3GPP = 20 * 1024 * 8; // bits/sec
86 |
87 | private static final int SEEK_BAR_MAX = 10000;
88 |
89 | private static final long WHEEL_SPEED_NORMAL = 1800;
90 |
91 | private static final long WHEEL_SPEED_FAST = 300;
92 |
93 | private static final long WHEEL_SPEED_SUPER_FAST = 100;
94 |
95 | private static final long SMALL_WHEEL_SPEED_NORMAL = 900;
96 |
97 | private static final long SMALL_WHEEL_SPEED_FAST = 200;
98 |
99 | private static final long SMALL_WHEEL_SPEED_SUPER_FAST = 200;
100 |
101 | private String mRequestedType = AUDIO_ANY;
102 |
103 | private boolean mCanRequestChanged = false;
104 |
105 | private Recorder mRecorder;
106 |
107 | private RecorderReceiver mReceiver;
108 |
109 | private boolean mSampleInterrupted = false;
110 |
111 | private boolean mShowFinishButton = false;
112 |
113 | private String mErrorUiMessage = null; // Some error messages are displayed
114 | // in the UI, not a dialog. This
115 | // happens when a recording
116 | // is interrupted for some reason.
117 |
118 | private long mMaxFileSize = -1; // can be specified in the intent
119 |
120 | private RemainingTimeCalculator mRemainingTimeCalculator;
121 |
122 | private String mTimerFormat;
123 |
124 | private SoundPool mSoundPool;
125 |
126 | private int mPlaySound;
127 |
128 | private int mPauseSound;
129 |
130 | private HashSet mSavedRecord;
131 |
132 | private long mLastClickTime;
133 |
134 | private int mLastButtonId;
135 |
136 | private final Handler mHandler = new Handler();
137 |
138 | private Runnable mUpdateTimer = new Runnable() {
139 | public void run() {
140 | if (!mStopUiUpdate) {
141 | updateTimerView();
142 | }
143 | }
144 | };
145 |
146 | private Runnable mUpdateSeekBar = new Runnable() {
147 | @Override
148 | public void run() {
149 | if (!mStopUiUpdate) {
150 | updateSeekBar();
151 | }
152 | }
153 | };
154 |
155 | private Runnable mUpdateVUMetur = new Runnable() {
156 | @Override
157 | public void run() {
158 | if (!mStopUiUpdate) {
159 | updateVUMeterView();
160 | }
161 | }
162 | };
163 |
164 | private ImageButton mNewButton;
165 |
166 | private ImageButton mFinishButton;
167 |
168 | private ImageButton mRecordButton;
169 |
170 | private ImageButton mStopButton;
171 |
172 | private ImageButton mPlayButton;
173 |
174 | private ImageButton mPauseButton;
175 |
176 | private ImageButton mDeleteButton;
177 |
178 | private WheelImageView mWheelLeft;
179 |
180 | private WheelImageView mWheelRight;
181 |
182 | private WheelImageView mSmallWheelLeft;
183 |
184 | private WheelImageView mSmallWheelRight;
185 |
186 | private RecordNameEditText mFileNameEditText;
187 |
188 | private LinearLayout mTimerLayout;
189 |
190 | private LinearLayout mVUMeterLayout;
191 |
192 | private LinearLayout mSeekBarLayout;
193 |
194 | private TextView mStartTime;
195 |
196 | private TextView mTotalTime;
197 |
198 | private SeekBar mPlaySeekBar;
199 |
200 | private BroadcastReceiver mSDCardMountEventReceiver = null;
201 |
202 | private int mPreviousVUMax;
203 |
204 | private boolean mStopUiUpdate;
205 |
206 | @Override
207 | public void onCreate(Bundle icycle) {
208 | super.onCreate(icycle);
209 | initInternalState(getIntent());
210 | setContentView(R.layout.main);
211 |
212 | mRecorder = new Recorder(this);
213 | mRecorder.setOnStateChangedListener(this);
214 | mReceiver = new RecorderReceiver();
215 | mRemainingTimeCalculator = new RemainingTimeCalculator();
216 | mSavedRecord = new HashSet();
217 |
218 | initResourceRefs();
219 |
220 | setResult(RESULT_CANCELED);
221 | registerExternalStorageListener();
222 | if (icycle != null) {
223 | Bundle recorderState = icycle.getBundle(RECORDER_STATE_KEY);
224 | if (recorderState != null) {
225 | mRecorder.restoreState(recorderState);
226 | mSampleInterrupted = recorderState.getBoolean(SAMPLE_INTERRUPTED_KEY, false);
227 | mMaxFileSize = recorderState.getLong(MAX_FILE_SIZE_KEY, -1);
228 | }
229 | }
230 |
231 | setVolumeControlStream(AudioManager.STREAM_MUSIC);
232 |
233 | if (mShowFinishButton) {
234 | // reset state if it is a recording request
235 | mRecorder.reset();
236 | resetFileNameEditText();
237 | }
238 | }
239 |
240 | @Override
241 | protected void onNewIntent(Intent intent) {
242 | super.onNewIntent(intent);
243 |
244 | boolean preShowFinishButton = mShowFinishButton;
245 | initInternalState(intent);
246 |
247 | if (mShowFinishButton || preShowFinishButton != mShowFinishButton) {
248 | // reset state if it is a recording request or state is changed
249 | mRecorder.reset();
250 | resetFileNameEditText();
251 | }
252 | }
253 |
254 | private void initInternalState(Intent i) {
255 | mRequestedType = AUDIO_ANY;
256 | mShowFinishButton = false;
257 | if (i != null) {
258 | String s = i.getType();
259 | if (AUDIO_AMR.equals(s) || AUDIO_3GPP.equals(s) || AUDIO_ANY.equals(s)
260 | || ANY_ANY.equals(s)) {
261 | mRequestedType = s;
262 | mShowFinishButton = true;
263 | } else if (s != null) {
264 | // we only support amr and 3gpp formats right now
265 | setResult(RESULT_CANCELED);
266 | finish();
267 | return;
268 | }
269 |
270 | final String EXTRA_MAX_BYTES = android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES;
271 | mMaxFileSize = i.getLongExtra(EXTRA_MAX_BYTES, -1);
272 | }
273 |
274 | if (AUDIO_ANY.equals(mRequestedType)) {
275 | mRequestedType = SoundRecorderPreferenceActivity.getRecordType(this);
276 | } else if (ANY_ANY.equals(mRequestedType)) {
277 | mRequestedType = AUDIO_3GPP;
278 | }
279 | }
280 |
281 | @Override
282 | public void onConfigurationChanged(Configuration newConfig) {
283 | super.onConfigurationChanged(newConfig);
284 |
285 | setContentView(R.layout.main);
286 | initResourceRefs();
287 | updateUi(false);
288 | }
289 |
290 | @Override
291 | protected void onSaveInstanceState(Bundle outState) {
292 | super.onSaveInstanceState(outState);
293 |
294 | if (mRecorder.sampleLength() == 0)
295 | return;
296 |
297 | Bundle recorderState = new Bundle();
298 |
299 | if (mRecorder.state() != Recorder.RECORDING_STATE) {
300 | mRecorder.saveState(recorderState);
301 | }
302 | recorderState.putBoolean(SAMPLE_INTERRUPTED_KEY, mSampleInterrupted);
303 | recorderState.putLong(MAX_FILE_SIZE_KEY, mMaxFileSize);
304 |
305 | outState.putBundle(RECORDER_STATE_KEY, recorderState);
306 | }
307 |
308 | /*
309 | * Whenever the UI is re-created (due f.ex. to orientation change) we have
310 | * to reinitialize references to the views.
311 | */
312 | private void initResourceRefs() {
313 | mNewButton = (ImageButton) findViewById(R.id.newButton);
314 | mFinishButton = (ImageButton) findViewById(R.id.finishButton);
315 | mRecordButton = (ImageButton) findViewById(R.id.recordButton);
316 | mStopButton = (ImageButton) findViewById(R.id.stopButton);
317 | mPlayButton = (ImageButton) findViewById(R.id.playButton);
318 | mPauseButton = (ImageButton) findViewById(R.id.pauseButton);
319 | mDeleteButton = (ImageButton) findViewById(R.id.deleteButton);
320 | mNewButton.setOnClickListener(this);
321 | mFinishButton.setOnClickListener(this);
322 | mRecordButton.setOnClickListener(this);
323 | mStopButton.setOnClickListener(this);
324 | mPlayButton.setOnClickListener(this);
325 | mPauseButton.setOnClickListener(this);
326 | mDeleteButton.setOnClickListener(this);
327 |
328 | mWheelLeft = (WheelImageView) findViewById(R.id.wheel_left);
329 | mWheelRight = (WheelImageView) findViewById(R.id.wheel_right);
330 | mSmallWheelLeft = (WheelImageView) findViewById(R.id.wheel_small_left);
331 | mSmallWheelRight = (WheelImageView) findViewById(R.id.wheel_small_right);
332 | mFileNameEditText = (RecordNameEditText) findViewById(R.id.file_name);
333 |
334 | resetFileNameEditText();
335 | mFileNameEditText.setNameChangeListener(new RecordNameEditText.OnNameChangeListener() {
336 | @Override
337 | public void onNameChanged(String name) {
338 | if (!TextUtils.isEmpty(name)) {
339 | mRecorder.renameSampleFile(name);
340 | }
341 | }
342 | });
343 |
344 | mTimerLayout = (LinearLayout) findViewById(R.id.time_calculator);
345 | mVUMeterLayout = (LinearLayout) findViewById(R.id.vumeter_layout);
346 | mSeekBarLayout = (LinearLayout) findViewById(R.id.play_seek_bar_layout);
347 | mStartTime = (TextView) findViewById(R.id.starttime);
348 | mTotalTime = (TextView) findViewById(R.id.totaltime);
349 | mPlaySeekBar = (SeekBar) findViewById(R.id.play_seek_bar);
350 | mPlaySeekBar.setMax(SEEK_BAR_MAX);
351 | mPlaySeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
352 |
353 | mTimerFormat = getResources().getString(R.string.timer_format);
354 |
355 | if (mShowFinishButton) {
356 | mNewButton.setVisibility(View.GONE);
357 | mFinishButton.setVisibility(View.VISIBLE);
358 | mNewButton = mFinishButton; // use mNewButon variable for left
359 | // button in the control panel
360 | }
361 |
362 | mSoundPool = new SoundPool(5, AudioManager.STREAM_SYSTEM, 5);
363 | mPlaySound = mSoundPool.load("/system/media/audio/ui/SoundRecorderPlay.ogg", 1);
364 | mPauseSound = mSoundPool.load("/system/media/audio/ui/SoundRecorderPause.ogg", 1);
365 |
366 | mLastClickTime = 0;
367 | mLastButtonId = 0;
368 | }
369 |
370 | private void resetFileNameEditText() {
371 | String extension = "";
372 | if (AUDIO_AMR.equals(mRequestedType)) {
373 | extension = FILE_EXTENSION_AMR;
374 | } else if (AUDIO_3GPP.equals(mRequestedType)) {
375 | extension = FILE_EXTENSION_3GPP;
376 | }
377 |
378 | // for audio which is used for mms, we can only use english file name
379 | // mShowFinishButon indicates whether this is an audio for mms
380 | mFileNameEditText.initFileName(mRecorder.getRecordDir(), extension, mShowFinishButton);
381 | }
382 |
383 | private void startRecordPlayingAnimation() {
384 | mWheelLeft.startAnimation(WHEEL_SPEED_NORMAL, true);
385 | mWheelRight.startAnimation(WHEEL_SPEED_NORMAL, true);
386 | mSmallWheelLeft.startAnimation(SMALL_WHEEL_SPEED_NORMAL, true);
387 | mSmallWheelRight.startAnimation(SMALL_WHEEL_SPEED_NORMAL, true);
388 | }
389 |
390 | private void stopRecordPlayingAnimation() {
391 | stopAnimation();
392 | startRecordPlayingDoneAnimation();
393 | }
394 |
395 | private void startRecordPlayingDoneAnimation() {
396 | mWheelLeft.startAnimation(WHEEL_SPEED_SUPER_FAST, false, 4);
397 | mWheelRight.startAnimation(WHEEL_SPEED_SUPER_FAST, false, 4);
398 | mSmallWheelLeft.startAnimation(SMALL_WHEEL_SPEED_SUPER_FAST, false, 2);
399 | mSmallWheelRight.startAnimation(SMALL_WHEEL_SPEED_SUPER_FAST, false, 2);
400 | }
401 |
402 | private void startForwardAnimation() {
403 | mWheelLeft.startAnimation(WHEEL_SPEED_FAST, true);
404 | mWheelRight.startAnimation(WHEEL_SPEED_FAST, true);
405 | mSmallWheelLeft.startAnimation(SMALL_WHEEL_SPEED_FAST, true);
406 | mSmallWheelRight.startAnimation(SMALL_WHEEL_SPEED_FAST, true);
407 | }
408 |
409 | private void startBackwardAnimation() {
410 | mWheelLeft.startAnimation(WHEEL_SPEED_FAST, false);
411 | mWheelRight.startAnimation(WHEEL_SPEED_FAST, false);
412 | mSmallWheelLeft.startAnimation(SMALL_WHEEL_SPEED_FAST, false);
413 | mSmallWheelRight.startAnimation(SMALL_WHEEL_SPEED_FAST, false);
414 | }
415 |
416 | private void stopAnimation() {
417 | mWheelLeft.stopAnimation();
418 | mWheelRight.stopAnimation();
419 | mSmallWheelLeft.stopAnimation();
420 | mSmallWheelRight.stopAnimation();
421 | }
422 |
423 | /*
424 | * Make sure we're not recording music playing in the background, ask the
425 | * MediaPlaybackService to pause playback.
426 | */
427 | private void stopAudioPlayback() {
428 | // Shamelessly copied from MediaPlaybackService.java, which
429 | // should be public, but isn't.
430 | Intent i = new Intent("com.android.music.musicservicecommand");
431 | i.putExtra("command", "pause");
432 |
433 | sendBroadcast(i);
434 | }
435 |
436 | /*
437 | * Handle the buttons.
438 | */
439 | public void onClick(View button) {
440 | if (System.currentTimeMillis() - mLastClickTime < 300) {
441 | // in order to avoid user click bottom too quickly
442 | return;
443 | }
444 |
445 | if (!button.isEnabled())
446 | return;
447 |
448 | if (button.getId() == mLastButtonId && button.getId() != R.id.newButton) {
449 | // as the recorder state is async with the UI
450 | // we need to avoid launching the duplicated action
451 | return;
452 | }
453 |
454 | if (button.getId() == R.id.stopButton && System.currentTimeMillis() - mLastClickTime < 1500) {
455 | // it seems that the media recorder is not robust enough
456 | // sometime it crashes when stop recording right after starting
457 | return;
458 | }
459 |
460 | mLastClickTime = System.currentTimeMillis();
461 | mLastButtonId = button.getId();
462 |
463 | switch (button.getId()) {
464 | case R.id.newButton:
465 | mFileNameEditText.clearFocus();
466 | saveSample();
467 | mRecorder.reset();
468 | resetFileNameEditText();
469 | break;
470 | case R.id.recordButton:
471 | showOverwriteConfirmDialogIfConflicts();
472 | break;
473 | case R.id.stopButton:
474 | mRecorder.stop();
475 | break;
476 | case R.id.playButton:
477 | mRecorder.startPlayback(mRecorder.playProgress());
478 | break;
479 | case R.id.pauseButton:
480 | mRecorder.pausePlayback();
481 | break;
482 | case R.id.finishButton:
483 | mRecorder.stop();
484 | saveSample();
485 | finish();
486 | break;
487 | case R.id.deleteButton:
488 | showDeleteConfirmDialog();
489 | break;
490 | }
491 | }
492 |
493 | private void startRecording() {
494 | mRemainingTimeCalculator.reset();
495 | if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
496 | mSampleInterrupted = true;
497 | mErrorUiMessage = getResources().getString(R.string.insert_sd_card);
498 | updateUi(false);
499 | } else if (!mRemainingTimeCalculator.diskSpaceAvailable()) {
500 | mSampleInterrupted = true;
501 | mErrorUiMessage = getResources().getString(R.string.storage_is_full);
502 | updateUi(false);
503 | } else {
504 | stopAudioPlayback();
505 |
506 | boolean isHighQuality = SoundRecorderPreferenceActivity.isHighQuality(this);
507 | if (AUDIO_AMR.equals(mRequestedType)) {
508 | mRemainingTimeCalculator.setBitRate(BITRATE_AMR);
509 | int outputfileformat = isHighQuality ? MediaRecorder.OutputFormat.AMR_WB
510 | : MediaRecorder.OutputFormat.AMR_NB;
511 | mRecorder.startRecording(outputfileformat, mFileNameEditText.getText().toString(),
512 | FILE_EXTENSION_AMR, isHighQuality, mMaxFileSize);
513 | } else if (AUDIO_3GPP.equals(mRequestedType)) {
514 | // HACKME: for HD2, there is an issue with high quality 3gpp
515 | // use low quality instead
516 | if (Build.MODEL.equals("HTC HD2")) {
517 | isHighQuality = false;
518 | }
519 |
520 | mRemainingTimeCalculator.setBitRate(BITRATE_3GPP);
521 | mRecorder.startRecording(MediaRecorder.OutputFormat.THREE_GPP, mFileNameEditText
522 | .getText().toString(), FILE_EXTENSION_3GPP, isHighQuality, mMaxFileSize);
523 | } else {
524 | throw new IllegalArgumentException("Invalid output file type requested");
525 | }
526 |
527 | if (mMaxFileSize != -1) {
528 | mRemainingTimeCalculator.setFileSizeLimit(mRecorder.sampleFile(), mMaxFileSize);
529 | }
530 | }
531 | }
532 |
533 | /*
534 | * Handle the "back" hardware key.
535 | */
536 | @Override
537 | public boolean onKeyDown(int keyCode, KeyEvent event) {
538 | if (keyCode == KeyEvent.KEYCODE_BACK) {
539 | switch (mRecorder.state()) {
540 | case Recorder.IDLE_STATE:
541 | case Recorder.PLAYING_PAUSED_STATE:
542 | if (mRecorder.sampleLength() > 0)
543 | saveSample();
544 | finish();
545 | break;
546 | case Recorder.PLAYING_STATE:
547 | mRecorder.stop();
548 | saveSample();
549 | break;
550 | case Recorder.RECORDING_STATE:
551 | if (mShowFinishButton) {
552 | mRecorder.clear();
553 | } else {
554 | finish();
555 | }
556 | break;
557 | }
558 | return true;
559 | } else {
560 | return super.onKeyDown(keyCode, event);
561 | }
562 | }
563 |
564 | @Override
565 | protected void onResume() {
566 | super.onResume();
567 | String type = SoundRecorderPreferenceActivity.getRecordType(this);
568 | if (mCanRequestChanged && !TextUtils.equals(type, mRequestedType)) {
569 | saveSample();
570 | mRecorder.reset();
571 | mRequestedType = type;
572 | resetFileNameEditText();
573 | }
574 | mCanRequestChanged = false;
575 |
576 | if (!mRecorder.syncStateWithService()) {
577 | mRecorder.reset();
578 | resetFileNameEditText();
579 | }
580 |
581 | if (mRecorder.state() == Recorder.RECORDING_STATE) {
582 | String preExtension = AUDIO_AMR.equals(mRequestedType) ? FILE_EXTENSION_AMR
583 | : FILE_EXTENSION_3GPP;
584 | if (!mRecorder.sampleFile().getName().endsWith(preExtension)) {
585 | // the extension is changed need to stop current recording
586 | mRecorder.reset();
587 | resetFileNameEditText();
588 | } else {
589 | // restore state
590 | if (!mShowFinishButton) {
591 | String fileName = mRecorder.sampleFile().getName().replace(preExtension, "");
592 | mFileNameEditText.setText(fileName);
593 | }
594 |
595 | if (AUDIO_AMR.equals(mRequestedType)) {
596 | mRemainingTimeCalculator.setBitRate(BITRATE_AMR);
597 | } else if (AUDIO_3GPP.equals(mRequestedType)) {
598 | mRemainingTimeCalculator.setBitRate(BITRATE_3GPP);
599 | }
600 | }
601 | } else {
602 | File file = mRecorder.sampleFile();
603 | if (file != null && !file.exists()) {
604 | mRecorder.reset();
605 | resetFileNameEditText();
606 | }
607 | }
608 |
609 | IntentFilter filter = new IntentFilter();
610 | filter.addAction(RecorderService.RECORDER_SERVICE_BROADCAST_NAME);
611 | registerReceiver(mReceiver, filter);
612 |
613 | mStopUiUpdate = false;
614 | updateUi(true);
615 |
616 | if (RecorderService.isRecording()) {
617 | Intent intent = new Intent(this, RecorderService.class);
618 | intent.putExtra(RecorderService.ACTION_NAME,
619 | RecorderService.ACTION_DISABLE_MONITOR_REMAIN_TIME);
620 | startService(intent);
621 | }
622 | }
623 |
624 | @Override
625 | protected void onPause() {
626 | if (mRecorder.state() != Recorder.RECORDING_STATE || mShowFinishButton
627 | || mMaxFileSize != -1) {
628 | mRecorder.stop();
629 | saveSample();
630 | mFileNameEditText.clearFocus();
631 | ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
632 | .cancel(RecorderService.NOTIFICATION_ID);
633 | }
634 |
635 | if (mReceiver != null) {
636 | unregisterReceiver(mReceiver);
637 | }
638 |
639 | mCanRequestChanged = true;
640 | mStopUiUpdate = true;
641 | stopAnimation();
642 |
643 | if (RecorderService.isRecording()) {
644 | Intent intent = new Intent(this, RecorderService.class);
645 | intent.putExtra(RecorderService.ACTION_NAME,
646 | RecorderService.ACTION_ENABLE_MONITOR_REMAIN_TIME);
647 | startService(intent);
648 | }
649 |
650 | super.onPause();
651 | }
652 |
653 | @Override
654 | protected void onStop() {
655 | if (mShowFinishButton) {
656 | finish();
657 | }
658 | super.onStop();
659 | }
660 |
661 | /*
662 | * If we have just recorded a sample, this adds it to the media data base
663 | * and sets the result to the sample's URI.
664 | */
665 | private void saveSample() {
666 | if (mRecorder.sampleLength() == 0)
667 | return;
668 | if (!mSavedRecord.contains(mRecorder.sampleFile().getAbsolutePath())) {
669 | Uri uri = null;
670 | try {
671 | uri = this.addToMediaDB(mRecorder.sampleFile());
672 | } catch (UnsupportedOperationException ex) { // Database
673 | // manipulation
674 | // failure
675 | return;
676 | }
677 | if (uri == null) {
678 | return;
679 | }
680 | mSavedRecord.add(mRecorder.sampleFile().getAbsolutePath());
681 | setResult(RESULT_OK, new Intent().setData(uri));
682 | }
683 | }
684 |
685 | private void showDeleteConfirmDialog() {
686 | AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
687 | dialogBuilder.setIcon(android.R.drawable.ic_dialog_alert);
688 | dialogBuilder.setTitle(R.string.delete_dialog_title);
689 | dialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
690 | @Override
691 | public void onClick(DialogInterface dialog, int which) {
692 | mRecorder.delete();
693 | }
694 | });
695 | dialogBuilder.setNegativeButton(android.R.string.cancel,
696 | new DialogInterface.OnClickListener() {
697 | @Override
698 | public void onClick(DialogInterface dialog, int which) {
699 | mLastButtonId = 0;
700 | }
701 | });
702 | dialogBuilder.show();
703 | }
704 |
705 | private void showOverwriteConfirmDialogIfConflicts() {
706 | String fileName = mFileNameEditText.getText().toString()
707 | + (AUDIO_AMR.equals(mRequestedType) ? FILE_EXTENSION_AMR : FILE_EXTENSION_3GPP);
708 |
709 | if (mRecorder.isRecordExisted(fileName) && !mShowFinishButton) {
710 | // file already existed and it's not a recording request from other
711 | // app
712 | AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
713 | dialogBuilder.setIcon(android.R.drawable.ic_dialog_alert);
714 | dialogBuilder.setTitle(getString(R.string.overwrite_dialog_title, fileName));
715 | dialogBuilder.setPositiveButton(android.R.string.ok,
716 | new DialogInterface.OnClickListener() {
717 | @Override
718 | public void onClick(DialogInterface dialog, int which) {
719 | startRecording();
720 | }
721 | });
722 | dialogBuilder.setNegativeButton(android.R.string.cancel,
723 | new DialogInterface.OnClickListener() {
724 | @Override
725 | public void onClick(DialogInterface dialog, int which) {
726 | mLastButtonId = 0;
727 | }
728 | });
729 | dialogBuilder.show();
730 | } else {
731 | startRecording();
732 | }
733 | }
734 |
735 | /*
736 | * Called on destroy to unregister the SD card mount event receiver.
737 | */
738 | @Override
739 | public void onDestroy() {
740 | if (mSDCardMountEventReceiver != null) {
741 | unregisterReceiver(mSDCardMountEventReceiver);
742 | mSDCardMountEventReceiver = null;
743 | }
744 | mSoundPool.release();
745 |
746 | super.onDestroy();
747 | }
748 |
749 | /*
750 | * Registers an intent to listen for
751 | * ACTION_MEDIA_EJECT/ACTION_MEDIA_UNMOUNTED/ACTION_MEDIA_MOUNTED
752 | * notifications.
753 | */
754 | private void registerExternalStorageListener() {
755 | if (mSDCardMountEventReceiver == null) {
756 | mSDCardMountEventReceiver = new BroadcastReceiver() {
757 | @Override
758 | public void onReceive(Context context, Intent intent) {
759 | mSampleInterrupted = false;
760 | mRecorder.reset();
761 | resetFileNameEditText();
762 | updateUi(false);
763 | }
764 | };
765 | IntentFilter iFilter = new IntentFilter();
766 | iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
767 | iFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
768 | iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
769 | iFilter.addDataScheme("file");
770 | registerReceiver(mSDCardMountEventReceiver, iFilter);
771 | }
772 | }
773 |
774 | /*
775 | * A simple utility to do a query into the databases.
776 | */
777 | private Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
778 | String sortOrder) {
779 | try {
780 | ContentResolver resolver = getContentResolver();
781 | if (resolver == null) {
782 | return null;
783 | }
784 | return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
785 | } catch (UnsupportedOperationException ex) {
786 | return null;
787 | }
788 | }
789 |
790 | /*
791 | * Add the given audioId to the playlist with the given playlistId; and
792 | * maintain the play_order in the playlist.
793 | */
794 | private void addToPlaylist(ContentResolver resolver, int audioId, long playlistId) {
795 | String[] cols = new String[] {
796 | "count(*)"
797 | };
798 | Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
799 | Cursor cur = resolver.query(uri, cols, null, null, null);
800 | cur.moveToFirst();
801 | final int base = cur.getInt(0);
802 | cur.close();
803 | ContentValues values = new ContentValues();
804 | values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + audioId));
805 | values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audioId);
806 | resolver.insert(uri, values);
807 | }
808 |
809 | /*
810 | * Obtain the id for the default play list from the audio_playlists table.
811 | */
812 | private int getPlaylistId(Resources res) {
813 | Uri uri = MediaStore.Audio.Playlists.getContentUri("external");
814 | final String[] ids = new String[] {
815 | MediaStore.Audio.Playlists._ID
816 | };
817 | final String where = MediaStore.Audio.Playlists.NAME + "=?";
818 | final String[] args = new String[] {
819 | res.getString(R.string.audio_db_playlist_name)
820 | };
821 | Cursor cursor = query(uri, ids, where, args, null);
822 | if (cursor == null) {
823 | Log.v(TAG, "query returns null");
824 | }
825 | int id = -1;
826 | if (cursor != null) {
827 | cursor.moveToFirst();
828 | if (!cursor.isAfterLast()) {
829 | id = cursor.getInt(0);
830 | }
831 | cursor.close();
832 | }
833 | return id;
834 | }
835 |
836 | /*
837 | * Create a playlist with the given default playlist name, if no such
838 | * playlist exists.
839 | */
840 | private Uri createPlaylist(Resources res, ContentResolver resolver) {
841 | ContentValues cv = new ContentValues();
842 | cv.put(MediaStore.Audio.Playlists.NAME, res.getString(R.string.audio_db_playlist_name));
843 | Uri uri = resolver.insert(MediaStore.Audio.Playlists.getContentUri("external"), cv);
844 | if (uri == null) {
845 | new AlertDialog.Builder(this).setTitle(R.string.app_name)
846 | .setMessage(R.string.error_mediadb_new_record)
847 | .setPositiveButton(R.string.button_ok, null).setCancelable(false).show();
848 | }
849 | return uri;
850 | }
851 |
852 | /*
853 | * Adds file and returns content uri.
854 | */
855 | private Uri addToMediaDB(File file) {
856 | Resources res = getResources();
857 | ContentValues cv = new ContentValues();
858 | long current = System.currentTimeMillis();
859 | long modDate = file.lastModified();
860 | Date date = new Date(current);
861 | SimpleDateFormat formatter = new SimpleDateFormat(
862 | res.getString(R.string.audio_db_title_format));
863 | String title = formatter.format(date);
864 | long sampleLengthMillis = mRecorder.sampleLength() * 1000L;
865 |
866 | // Lets label the recorded audio file as NON-MUSIC so that the file
867 | // won't be displayed automatically, except for in the playlist.
868 | cv.put(MediaStore.Audio.Media.IS_MUSIC, "0");
869 |
870 | cv.put(MediaStore.Audio.Media.TITLE, title);
871 | cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
872 | cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
873 | cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000));
874 | cv.put(MediaStore.Audio.Media.DURATION, sampleLengthMillis);
875 | cv.put(MediaStore.Audio.Media.MIME_TYPE, mRequestedType);
876 | cv.put(MediaStore.Audio.Media.ARTIST, res.getString(R.string.audio_db_artist_name));
877 | cv.put(MediaStore.Audio.Media.ALBUM, res.getString(R.string.audio_db_album_name));
878 | Log.d(TAG, "Inserting audio record: " + cv.toString());
879 | ContentResolver resolver = getContentResolver();
880 | Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
881 | Log.d(TAG, "ContentURI: " + base);
882 | Uri result = resolver.insert(base, cv);
883 | if (result == null) {
884 | Log.w(TAG, getString(R.string.error_mediadb_new_record));
885 | return null;
886 | }
887 |
888 | if (getPlaylistId(res) == -1) {
889 | createPlaylist(res, resolver);
890 | }
891 | int audioId = Integer.valueOf(result.getLastPathSegment());
892 | addToPlaylist(resolver, audioId, getPlaylistId(res));
893 |
894 | // Notify those applications such as Music listening to the
895 | // scanner events that a recorded audio file just created.
896 | sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
897 | return result;
898 | }
899 |
900 | private ImageView getTimerImage(char number) {
901 | ImageView image = new ImageView(this);
902 | LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
903 | if (number != ':') {
904 | image.setBackgroundResource(R.drawable.background_number);
905 | }
906 | switch (number) {
907 | case '0':
908 | image.setImageResource(R.drawable.number_0);
909 | break;
910 | case '1':
911 | image.setImageResource(R.drawable.number_1);
912 | break;
913 | case '2':
914 | image.setImageResource(R.drawable.number_2);
915 | break;
916 | case '3':
917 | image.setImageResource(R.drawable.number_3);
918 | break;
919 | case '4':
920 | image.setImageResource(R.drawable.number_4);
921 | break;
922 | case '5':
923 | image.setImageResource(R.drawable.number_5);
924 | break;
925 | case '6':
926 | image.setImageResource(R.drawable.number_6);
927 | break;
928 | case '7':
929 | image.setImageResource(R.drawable.number_7);
930 | break;
931 | case '8':
932 | image.setImageResource(R.drawable.number_8);
933 | break;
934 | case '9':
935 | image.setImageResource(R.drawable.number_9);
936 | break;
937 | case ':':
938 | image.setImageResource(R.drawable.colon);
939 | break;
940 | }
941 | image.setLayoutParams(lp);
942 | return image;
943 | }
944 |
945 | /**
946 | * Update the big MM:SS timer. If we are in playback, also update the
947 | * progress bar.
948 | */
949 | private void updateTimerView() {
950 | int state = mRecorder.state();
951 |
952 | boolean ongoing = state == Recorder.RECORDING_STATE || state == Recorder.PLAYING_STATE;
953 |
954 | long time = mRecorder.progress();
955 | String timeStr = String.format(mTimerFormat, time / 60, time % 60);
956 | mTimerLayout.removeAllViews();
957 | for (int i = 0; i < timeStr.length(); i++) {
958 | mTimerLayout.addView(getTimerImage(timeStr.charAt(i)));
959 | }
960 |
961 | if (state == Recorder.RECORDING_STATE) {
962 | updateTimeRemaining();
963 | }
964 |
965 | if (ongoing) {
966 | mHandler.postDelayed(mUpdateTimer, 500);
967 | }
968 | }
969 |
970 | private void setTimerView(float progress) {
971 | long time = (long) (progress * mRecorder.sampleLength());
972 | String timeStr = String.format(mTimerFormat, time / 60, time % 60);
973 | mTimerLayout.removeAllViews();
974 | for (int i = 0; i < timeStr.length(); i++) {
975 | mTimerLayout.addView(getTimerImage(timeStr.charAt(i)));
976 | }
977 | }
978 |
979 | private void updateSeekBar() {
980 | if (mRecorder.state() == Recorder.PLAYING_STATE) {
981 | mPlaySeekBar.setProgress((int) (SEEK_BAR_MAX * mRecorder.playProgress()));
982 | mHandler.postDelayed(mUpdateSeekBar, 10);
983 | }
984 | }
985 |
986 | /*
987 | * Called when we're in recording state. Find out how much longer we can go
988 | * on recording. If it's under 5 minutes, we display a count-down in the UI.
989 | * If we've run out of time, stop the recording.
990 | */
991 | private void updateTimeRemaining() {
992 | long t = mRemainingTimeCalculator.timeRemaining();
993 |
994 | if (t <= 0) {
995 | mSampleInterrupted = true;
996 |
997 | int limit = mRemainingTimeCalculator.currentLowerLimit();
998 | switch (limit) {
999 | case RemainingTimeCalculator.DISK_SPACE_LIMIT:
1000 | mErrorUiMessage = getResources().getString(R.string.storage_is_full);
1001 | break;
1002 | case RemainingTimeCalculator.FILE_SIZE_LIMIT:
1003 | mErrorUiMessage = getResources().getString(R.string.max_length_reached);
1004 | break;
1005 | default:
1006 | mErrorUiMessage = null;
1007 | break;
1008 | }
1009 |
1010 | mRecorder.stop();
1011 | return;
1012 | }
1013 | }
1014 |
1015 | private void updateVUMeterView() {
1016 | final int MAX_VU_SIZE = 11;
1017 | boolean showVUArray[] = new boolean[MAX_VU_SIZE];
1018 |
1019 | if (mVUMeterLayout.getVisibility() == View.VISIBLE
1020 | && mRecorder.state() == Recorder.RECORDING_STATE) {
1021 | int vuSize = MAX_VU_SIZE * mRecorder.getMaxAmplitude() / 32768;
1022 | if (vuSize >= MAX_VU_SIZE) {
1023 | vuSize = MAX_VU_SIZE - 1;
1024 | }
1025 |
1026 | if (vuSize >= mPreviousVUMax) {
1027 | mPreviousVUMax = vuSize;
1028 | } else if (mPreviousVUMax > 0) {
1029 | mPreviousVUMax--;
1030 | }
1031 |
1032 | for (int i = 0; i < MAX_VU_SIZE; i++) {
1033 | if (i <= vuSize) {
1034 | showVUArray[i] = true;
1035 | } else if (i == mPreviousVUMax) {
1036 | showVUArray[i] = true;
1037 | } else {
1038 | showVUArray[i] = false;
1039 | }
1040 | }
1041 |
1042 | mHandler.postDelayed(mUpdateVUMetur, 100);
1043 | } else if (mVUMeterLayout.getVisibility() == View.VISIBLE) {
1044 | mPreviousVUMax = 0;
1045 | for (int i = 0; i < MAX_VU_SIZE; i++) {
1046 | showVUArray[i] = false;
1047 | }
1048 | }
1049 |
1050 | if (mVUMeterLayout.getVisibility() == View.VISIBLE) {
1051 | mVUMeterLayout.removeAllViews();
1052 | for (boolean show : showVUArray) {
1053 | ImageView imageView = new ImageView(this);
1054 | imageView.setBackgroundResource(R.drawable.background_vumeter);
1055 | if (show) {
1056 | imageView.setImageResource(R.drawable.icon_vumeter);
1057 | }
1058 | imageView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
1059 | LayoutParams.WRAP_CONTENT));
1060 | mVUMeterLayout.addView(imageView);
1061 | }
1062 | }
1063 | }
1064 |
1065 | /**
1066 | * Shows/hides the appropriate child views for the new state.
1067 | */
1068 | private void updateUi(boolean skipRewindAnimation) {
1069 | switch (mRecorder.state()) {
1070 | case Recorder.IDLE_STATE:
1071 | mLastButtonId = 0;
1072 | case Recorder.PLAYING_PAUSED_STATE:
1073 | if (mRecorder.sampleLength() == 0) {
1074 | mNewButton.setEnabled(true);
1075 | mNewButton.setVisibility(View.VISIBLE);
1076 | mRecordButton.setVisibility(View.VISIBLE);
1077 | mStopButton.setVisibility(View.GONE);
1078 | mPlayButton.setVisibility(View.GONE);
1079 | mPauseButton.setVisibility(View.GONE);
1080 | mDeleteButton.setEnabled(false);
1081 | mRecordButton.requestFocus();
1082 |
1083 | mVUMeterLayout.setVisibility(View.VISIBLE);
1084 | mSeekBarLayout.setVisibility(View.GONE);
1085 | } else {
1086 | mNewButton.setEnabled(true);
1087 | mNewButton.setVisibility(View.VISIBLE);
1088 | mRecordButton.setVisibility(View.GONE);
1089 | mStopButton.setVisibility(View.GONE);
1090 | mPlayButton.setVisibility(View.VISIBLE);
1091 | mPauseButton.setVisibility(View.GONE);
1092 | mDeleteButton.setEnabled(true);
1093 | mPauseButton.requestFocus();
1094 |
1095 | mVUMeterLayout.setVisibility(View.GONE);
1096 | mSeekBarLayout.setVisibility(View.VISIBLE);
1097 | mStartTime.setText(String.format(mTimerFormat, 0, 0));
1098 | mTotalTime.setText(String.format(mTimerFormat, mRecorder.sampleLength() / 60,
1099 | mRecorder.sampleLength() % 60));
1100 | }
1101 | mFileNameEditText.setEnabled(true);
1102 | mFileNameEditText.clearFocus();
1103 |
1104 | if (mRecorder.sampleLength() > 0) {
1105 | if (mRecorder.state() == Recorder.PLAYING_PAUSED_STATE) {
1106 | stopAnimation();
1107 | if (SoundRecorderPreferenceActivity.isEnabledSoundEffect(this)) {
1108 | mSoundPool.play(mPauseSound, 1.0f, 1.0f, 0, 0, 1);
1109 | }
1110 | } else {
1111 | mPlaySeekBar.setProgress(0);
1112 | if (!skipRewindAnimation) {
1113 | stopRecordPlayingAnimation();
1114 | } else {
1115 | stopAnimation();
1116 | }
1117 | }
1118 | } else {
1119 | stopAnimation();
1120 | }
1121 |
1122 | // we allow only one toast at one time
1123 | if (mSampleInterrupted && mErrorUiMessage == null) {
1124 | Toast.makeText(this, R.string.recording_stopped, Toast.LENGTH_SHORT).show();
1125 | }
1126 |
1127 | if (mErrorUiMessage != null) {
1128 | Toast.makeText(this, mErrorUiMessage, Toast.LENGTH_SHORT).show();
1129 | }
1130 |
1131 | break;
1132 | case Recorder.RECORDING_STATE:
1133 | mNewButton.setEnabled(false);
1134 | mNewButton.setVisibility(View.VISIBLE);
1135 | mRecordButton.setVisibility(View.GONE);
1136 | mStopButton.setVisibility(View.VISIBLE);
1137 | mPlayButton.setVisibility(View.GONE);
1138 | mPauseButton.setVisibility(View.GONE);
1139 | mDeleteButton.setEnabled(false);
1140 | mStopButton.requestFocus();
1141 |
1142 | mVUMeterLayout.setVisibility(View.VISIBLE);
1143 | mSeekBarLayout.setVisibility(View.GONE);
1144 |
1145 | mFileNameEditText.setEnabled(false);
1146 |
1147 | startRecordPlayingAnimation();
1148 | mPreviousVUMax = 0;
1149 | break;
1150 |
1151 | case Recorder.PLAYING_STATE:
1152 | mNewButton.setEnabled(false);
1153 | mNewButton.setVisibility(View.VISIBLE);
1154 | mRecordButton.setVisibility(View.GONE);
1155 | mStopButton.setVisibility(View.GONE);
1156 | mPlayButton.setVisibility(View.GONE);
1157 | mPauseButton.setVisibility(View.VISIBLE);
1158 | mDeleteButton.setEnabled(false);
1159 | mPauseButton.requestFocus();
1160 |
1161 | mVUMeterLayout.setVisibility(View.GONE);
1162 | mSeekBarLayout.setVisibility(View.VISIBLE);
1163 |
1164 | mFileNameEditText.setEnabled(false);
1165 |
1166 | if (SoundRecorderPreferenceActivity.isEnabledSoundEffect(this)) {
1167 | mSoundPool.play(mPlaySound, 1.0f, 1.0f, 0, 0, 1);
1168 | }
1169 | startRecordPlayingAnimation();
1170 | break;
1171 | }
1172 |
1173 | updateTimerView();
1174 | updateSeekBar();
1175 | updateVUMeterView();
1176 |
1177 | }
1178 |
1179 | /*
1180 | * Called when Recorder changed it's state.
1181 | */
1182 | public void onStateChanged(int state) {
1183 | if (state == Recorder.PLAYING_STATE || state == Recorder.RECORDING_STATE) {
1184 | mSampleInterrupted = false;
1185 | mErrorUiMessage = null;
1186 | }
1187 |
1188 | updateUi(false);
1189 | }
1190 |
1191 | /*
1192 | * Called when MediaPlayer encounters an error.
1193 | */
1194 | public void onError(int error) {
1195 | Resources res = getResources();
1196 |
1197 | String message = null;
1198 | switch (error) {
1199 | case Recorder.STORAGE_ACCESS_ERROR:
1200 | message = res.getString(R.string.error_sdcard_access);
1201 | break;
1202 | case Recorder.IN_CALL_RECORD_ERROR:
1203 | // TODO: update error message to reflect that the recording
1204 | // could not be
1205 | // performed during a call.
1206 | case Recorder.INTERNAL_ERROR:
1207 | message = res.getString(R.string.error_app_internal);
1208 | break;
1209 | }
1210 | if (message != null) {
1211 | new AlertDialog.Builder(this).setTitle(R.string.app_name).setMessage(message)
1212 | .setPositiveButton(R.string.button_ok, null).setCancelable(false).show();
1213 | }
1214 | }
1215 |
1216 | @Override
1217 | public boolean onPrepareOptionsMenu(Menu menu) {
1218 | menu.clear();
1219 | if (mRecorder.state() == Recorder.RECORDING_STATE
1220 | || mRecorder.state() == Recorder.PLAYING_STATE) {
1221 | return false;
1222 | } else {
1223 | getMenuInflater().inflate(R.layout.view_list_menu, menu);
1224 | return true;
1225 | }
1226 | }
1227 |
1228 | @Override
1229 | public boolean onOptionsItemSelected(MenuItem item) {
1230 | Intent intent;
1231 | switch (item.getItemId()) {
1232 | case R.id.menu_fm:
1233 | saveSample();
1234 | intent = new Intent();
1235 | intent.addCategory(Intent.CATEGORY_DEFAULT);
1236 | intent.setData(Uri.parse("file://" + mRecorder.getRecordDir()));
1237 | startActivity(intent);
1238 | break;
1239 | case R.id.menu_setting:
1240 | intent = new Intent(this, SoundRecorderPreferenceActivity.class);
1241 | startActivity(intent);
1242 | break;
1243 | default:
1244 | break;
1245 | }
1246 | return true;
1247 | }
1248 |
1249 | private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
1250 | private final int DELTA = SEEK_BAR_MAX / 20;
1251 |
1252 | private int mProgress = 0;
1253 |
1254 | private boolean mPlayingAnimation = false;
1255 |
1256 | private boolean mForwardAnimation = true;
1257 |
1258 | @Override
1259 | public void onStopTrackingTouch(SeekBar seekBar) {
1260 | stopAnimation();
1261 | mRecorder.startPlayback((float) seekBar.getProgress() / SEEK_BAR_MAX);
1262 | }
1263 |
1264 | @Override
1265 | public void onStartTrackingTouch(SeekBar seekBar) {
1266 | mRecorder.pausePlayback();
1267 | mPlayingAnimation = false;
1268 | }
1269 |
1270 | @Override
1271 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
1272 | if (fromUser) {
1273 | if (!mPlayingAnimation) {
1274 | mForwardAnimation = true;
1275 | startForwardAnimation();
1276 | mPlayingAnimation = true;
1277 | mProgress = progress;
1278 | }
1279 |
1280 | if (progress >= mProgress + DELTA) {
1281 | if (!mForwardAnimation) {
1282 | mForwardAnimation = true;
1283 | stopAnimation();
1284 | startForwardAnimation();
1285 | }
1286 | mProgress = progress;
1287 | } else if (progress < mProgress - DELTA) {
1288 | if (mForwardAnimation) {
1289 | mForwardAnimation = false;
1290 | stopAnimation();
1291 | startBackwardAnimation();
1292 | }
1293 | mProgress = progress;
1294 | }
1295 |
1296 | setTimerView(((float) progress) / SEEK_BAR_MAX);
1297 | mLastButtonId = 0;
1298 | }
1299 | }
1300 | };
1301 |
1302 | private class RecorderReceiver extends BroadcastReceiver {
1303 |
1304 | @Override
1305 | public void onReceive(Context context, Intent intent) {
1306 | if (intent.hasExtra(RecorderService.RECORDER_SERVICE_BROADCAST_STATE)) {
1307 | boolean isRecording = intent.getBooleanExtra(
1308 | RecorderService.RECORDER_SERVICE_BROADCAST_STATE, false);
1309 | mRecorder.setState(isRecording ? Recorder.RECORDING_STATE : Recorder.IDLE_STATE);
1310 | } else if (intent.hasExtra(RecorderService.RECORDER_SERVICE_BROADCAST_ERROR)) {
1311 | int error = intent.getIntExtra(RecorderService.RECORDER_SERVICE_BROADCAST_ERROR, 0);
1312 | mRecorder.setError(error);
1313 | }
1314 | }
1315 | }
1316 | }
1317 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/SoundRecorderPreferenceActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.content.Context;
20 | import android.content.SharedPreferences;
21 | import android.os.Bundle;
22 | import android.preference.PreferenceActivity;
23 | import android.preference.PreferenceManager;
24 |
25 | public class SoundRecorderPreferenceActivity extends PreferenceActivity {
26 | private static final String RECORD_TYPE = "pref_key_record_type";
27 |
28 | private static final String ENABLE_HIGH_QUALITY = "pref_key_enable_high_quality";
29 |
30 | private static final String ENABLE_SOUND_EFFECT = "pref_key_enable_sound_effect";
31 |
32 | @Override
33 | protected void onCreate(Bundle icicle) {
34 | super.onCreate(icicle);
35 | addPreferencesFromResource(R.xml.preferences);
36 | }
37 |
38 | public static String getRecordType(Context context) {
39 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
40 | return settings.getString(RECORD_TYPE, context.getString(R.string.prefDefault_recordType));
41 | }
42 |
43 | public static boolean isHighQuality(Context context) {
44 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
45 | return settings.getBoolean(ENABLE_HIGH_QUALITY, true);
46 | }
47 |
48 | public static boolean isEnabledSoundEffect(Context context) {
49 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
50 | return settings.getBoolean(ENABLE_SOUND_EFFECT, true);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/net/micode/soundrecorder/WheelImageView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.micode.soundrecorder;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.animation.Animation;
22 | import android.view.animation.LinearInterpolator;
23 | import android.widget.ImageView;
24 |
25 | public class WheelImageView extends ImageView {
26 |
27 | SeamlessAnimation mAnimation;
28 |
29 | public WheelImageView(Context context) {
30 | super(context);
31 | mAnimation = null;
32 | }
33 |
34 | public WheelImageView(Context context, AttributeSet attrs) {
35 | super(context, attrs);
36 | mAnimation = null;
37 | }
38 |
39 | public WheelImageView(Context context, AttributeSet attrs, int defStyle) {
40 | super(context, attrs, defStyle);
41 | mAnimation = null;
42 | }
43 |
44 | private void initAnimation(long duration, boolean isForward, int repeatCount) {
45 | LinearInterpolator lir = new LinearInterpolator();
46 | float from;
47 | float to;
48 | if (isForward) {
49 | from = (mAnimation == null || mAnimation.getRepeatCount() != Animation.INFINITE) ? 0.0f
50 | : mAnimation.getDegree();
51 | to = from + 360.0f;
52 | } else {
53 | from = (mAnimation == null || mAnimation.getRepeatCount() != Animation.INFINITE) ? 360.0f
54 | : mAnimation.getDegree();
55 | to = from - 360.0f;
56 | }
57 |
58 | if (isForward) {
59 | mAnimation = new SeamlessAnimation(from, to, Animation.RELATIVE_TO_SELF, 0.5f,
60 | Animation.RELATIVE_TO_SELF, 0.5f);
61 | } else {
62 | mAnimation = new SeamlessAnimation(from, to, Animation.RELATIVE_TO_SELF, 0.5f,
63 | Animation.RELATIVE_TO_SELF, 0.5f);
64 | }
65 | mAnimation.setDuration(duration);
66 | mAnimation.setRepeatMode(Animation.RESTART);
67 | mAnimation.setRepeatCount(repeatCount);
68 | mAnimation.setInterpolator(lir);
69 | }
70 |
71 | public void startAnimation(long duration, boolean isForward) {
72 | startAnimation(duration, isForward, Animation.INFINITE);
73 | }
74 |
75 | public void startAnimation(long duration, boolean isForward, int repeatCount) {
76 | initAnimation(duration, isForward, repeatCount);
77 | startAnimation(mAnimation);
78 | }
79 |
80 | public void stopAnimation() {
81 | if (mAnimation != null) {
82 | mAnimation.cancel();
83 | mAnimation = null;
84 | clearAnimation();
85 | }
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------