diff --git a/Android.mk b/Android.mk deleted file mode 100644 index 725c89d..0000000 --- a/Android.mk +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -ifeq ($(TARGET_BUILD_APPS),) - LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res - LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res -else - LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res - LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/recyclerview/res -endif -LOCAL_RESOURCE_DIR += frameworks/opt/chips/res -LOCAL_RESOURCE_DIR += frameworks/opt/colorpicker/res -LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/res -LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/activity/res - -LOCAL_STATIC_JAVA_LIBRARIES := android-common -LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-palette -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13 -LOCAL_STATIC_JAVA_LIBRARIES += com.android.vcard -LOCAL_STATIC_JAVA_LIBRARIES += guava -LOCAL_STATIC_JAVA_LIBRARIES += libchips -LOCAL_STATIC_JAVA_LIBRARIES += libphotoviewer -LOCAL_STATIC_JAVA_LIBRARIES += libphonenumber -LOCAL_STATIC_JAVA_LIBRARIES += colorpicker - -include $(LOCAL_PATH)/version.mk - -LOCAL_AAPT_FLAGS := --auto-add-overlay -LOCAL_AAPT_FLAGS += --version-name "$(version_name_package)" -LOCAL_AAPT_FLAGS += --version-code $(version_code_package) -LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat -LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview -LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips -LOCAL_AAPT_FLAGS += --extra-packages com.android.vcard -LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.photo -LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker - -ifdef TARGET_BUILD_APPS - LOCAL_JNI_SHARED_LIBRARIES := libframesequence libgiftranscode -else - LOCAL_REQUIRED_MODULES:= libframesequence libgiftranscode -endif - -LOCAL_PROGUARD_FLAGS := -ignorewarnings -include build/core/proguard_basic_keeps.flags - -LOCAL_PROGUARD_ENABLED := nosystem - -LOCAL_PROGUARD_FLAG_FILES := proguard.flags -ifeq (eng,$(TARGET_BUILD_VARIANT)) - LOCAL_PROGUARD_FLAG_FILES += proguard-test.flags -else - LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags -endif - -LOCAL_JACK_ENABLED := disabled - -LOCAL_PACKAGE_NAME := messaging - -LOCAL_CERTIFICATE := platform - -LOCAL_SDK_VERSION := current - -include $(BUILD_PACKAGE) - -include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index 8fe8fae..0000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,524 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ForceProguard.mk b/ForceProguard.mk deleted file mode 100755 index e6f4fea..0000000 --- a/ForceProguard.mk +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Check to see if we need to force proguard to re-run, typically after using tapas to -# switch to/from eng builds. This is determined by comparing the flag files used in the previous -# build. If the flag files have changed, proguard is rerun. - -# If the LOCAL_MODULE being setup isn't a build target, then don't run ForceProguard. -ifneq (,$(findstring $(LOCAL_MODULE), $(TARGET_BUILD_APPS))) - -PREVIOUS_FLAG_FILES_USED_DIR := $(call local-intermediates-dir,1) - -# If the local intermediates dir doesn't exist, ForceProguard won't work. -ifneq ($(wildcard $(PREVIOUS_FLAG_FILES_USED_DIR)),) -PREVIOUS_FLAG_FILES_USED_FILE := $(PREVIOUS_FLAG_FILES_USED_DIR)/previous_proguard_flag_files -PREVIOUS_FLAG_FILES_USED := $(if $(wildcard $(PREVIOUS_FLAG_FILES_USED_FILE)), \ - $(shell cat $(PREVIOUS_FLAG_FILES_USED_FILE))) - -ifneq ($(strip $(PREVIOUS_FLAG_FILES_USED)), $(strip $(LOCAL_PROGUARD_FLAG_FILES))) -$(info *** Flag files used for proguard have changed; forcing proguard to rerun.) -$(shell touch $(LOCAL_PATH)/proguard.flags) -$(shell echo $(LOCAL_PROGUARD_FLAG_FILES) > $(PREVIOUS_FLAG_FILES_USED_FILE)) -endif - -endif -# End local intermediates directory existence check - -endif -# End LOCAL_MODULE is a build target check diff --git a/assets/licenses.html b/assets/licenses.html deleted file mode 100644 index aa9c3b7..0000000 --- a/assets/licenses.html +++ /dev/null @@ -1,666 +0,0 @@ - - - - - - - - -

Notice for AOSP:

-
-    Copyright (c) 2005-2008, The Android Open Source Project
-
-       Licensed under the Apache License, Version 2.0 (the "License");
-       you may not use this file except in compliance with the License.
-
-       Unless required by applicable law or agreed to in writing, software
-       distributed under the License is distributed on an "AS IS" BASIS,
-       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-       See the License for the specific language governing permissions and
-       limitations under the License.
-
-       Apache License
-       Version 2.0, January 2004
-       http://www.apache.org/licenses/
-
-       TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-       1. Definitions.
-
-          "License" shall mean the terms and conditions for use, reproduction,
-          and distribution as defined by Sections 1 through 9 of this document.
-
-          "Licensor" shall mean the copyright owner or entity authorized by
-          the copyright owner that is granting the License.
-
-          "Legal Entity" shall mean the union of the acting entity and all
-          other entities that control, are controlled by, or are under common
-          control with that entity. For the purposes of this definition,
-          "control" means (i) the power, direct or indirect, to cause the
-          direction or management of such entity, whether by contract or
-          otherwise, or (ii) ownership of fifty percent (50%) or more of the
-          outstanding shares, or (iii) beneficial ownership of such entity.
-
-          "You" (or "Your") shall mean an individual or Legal Entity
-          exercising permissions granted by this License.
-
-          "Source" form shall mean the preferred form for making modifications,
-          including but not limited to software source code, documentation
-          source, and configuration files.
-
-          "Object" form shall mean any form resulting from mechanical
-          transformation or translation of a Source form, including but
-          not limited to compiled object code, generated documentation,
-          and conversions to other media types.
-
-          "Work" shall mean the work of authorship, whether in Source or
-          Object form, made available under the License, as indicated by a
-          copyright notice that is included in or attached to the work
-          (an example is provided in the Appendix below).
-
-          "Derivative Works" shall mean any work, whether in Source or Object
-          form, that is based on (or derived from) the Work and for which the
-          editorial revisions, annotations, elaborations, or other modifications
-          represent, as a whole, an original work of authorship. For the purposes
-          of this License, Derivative Works shall not include works that remain
-          separable from, or merely link (or bind by name) to the interfaces of,
-          the Work and Derivative Works thereof.
-
-          "Contribution" shall mean any work of authorship, including
-          the original version of the Work and any modifications or additions
-          to that Work or Derivative Works thereof, that is intentionally
-          submitted to Licensor for inclusion in the Work by the copyright owner
-          or by an individual or Legal Entity authorized to submit on behalf of
-          the copyright owner. For the purposes of this definition, "submitted"
-          means any form of electronic, verbal, or written communication sent
-          to the Licensor or its representatives, including but not limited to
-          communication on electronic mailing lists, source code control systems,
-          and issue tracking systems that are managed by, or on behalf of, the
-          Licensor for the purpose of discussing and improving the Work, but
-          excluding communication that is conspicuously marked or otherwise
-          designated in writing by the copyright owner as "Not a Contribution."
-
-          "Contributor" shall mean Licensor and any individual or Legal Entity
-          on behalf of whom a Contribution has been received by Licensor and
-          subsequently incorporated within the Work.
-
-       2. Grant of Copyright License. Subject to the terms and conditions of
-          this License, each Contributor hereby grants to You a perpetual,
-          worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-          copyright license to reproduce, prepare Derivative Works of,
-          publicly display, publicly perform, sublicense, and distribute the
-          Work and such Derivative Works in Source or Object form.
-
-       3. Grant of Patent License. Subject to the terms and conditions of
-          this License, each Contributor hereby grants to You a perpetual,
-          worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-          (except as stated in this section) patent license to make, have made,
-          use, offer to sell, sell, import, and otherwise transfer the Work,
-          where such license applies only to those patent claims licensable
-          by such Contributor that are necessarily infringed by their
-          Contribution(s) alone or by combination of their Contribution(s)
-          with the Work to which such Contribution(s) was submitted. If You
-          institute patent litigation against any entity (including a
-          cross-claim or counterclaim in a lawsuit) alleging that the Work
-          or a Contribution incorporated within the Work constitutes direct
-          or contributory patent infringement, then any patent licenses
-          granted to You under this License for that Work shall terminate
-          as of the date such litigation is filed.
-
-       4. Redistribution. You may reproduce and distribute copies of the
-          Work or Derivative Works thereof in any medium, with or without
-          modifications, and in Source or Object form, provided that You
-          meet the following conditions:
-
-          (a) You must give any other recipients of the Work or
-              Derivative Works a copy of this License; and
-
-          (b) You must cause any modified files to carry prominent notices
-              stating that You changed the files; and
-
-          (c) You must retain, in the Source form of any Derivative Works
-              that You distribute, all copyright, patent, trademark, and
-              attribution notices from the Source form of the Work,
-              excluding those notices that do not pertain to any part of
-              the Derivative Works; and
-
-          (d) If the Work includes a "NOTICE" text file as part of its
-              distribution, then any Derivative Works that You distribute must
-              include a readable copy of the attribution notices contained
-              within such NOTICE file, excluding those notices that do not
-              pertain to any part of the Derivative Works, in at least one
-              of the following places: within a NOTICE text file distributed
-              as part of the Derivative Works; within the Source form or
-              documentation, if provided along with the Derivative Works; or,
-              within a display generated by the Derivative Works, if and
-              wherever such third-party notices normally appear. The contents
-              of the NOTICE file are for informational purposes only and
-              do not modify the License. You may add Your own attribution
-              notices within Derivative Works that You distribute, alongside
-              or as an addendum to the NOTICE text from the Work, provided
-              that such additional attribution notices cannot be construed
-              as modifying the License.
-
-          You may add Your own copyright statement to Your modifications and
-          may provide additional or different license terms and conditions
-          for use, reproduction, or distribution of Your modifications, or
-          for any such Derivative Works as a whole, provided Your use,
-          reproduction, and distribution of the Work otherwise complies with
-          the conditions stated in this License.
-
-       5. Submission of Contributions. Unless You explicitly state otherwise,
-          any Contribution intentionally submitted for inclusion in the Work
-          by You to the Licensor shall be under the terms and conditions of
-          this License, without any additional terms or conditions.
-          Notwithstanding the above, nothing herein shall supersede or modify
-          the terms of any separate license agreement you may have executed
-          with Licensor regarding such Contributions.
-
-       6. Trademarks. This License does not grant permission to use the trade
-          names, trademarks, service marks, or product names of the Licensor,
-          except as required for reasonable and customary use in describing the
-          origin of the Work and reproducing the content of the NOTICE file.
-
-       7. Disclaimer of Warranty. Unless required by applicable law or
-          agreed to in writing, Licensor provides the Work (and each
-          Contributor provides its Contributions) on an "AS IS" BASIS,
-          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-          implied, including, without limitation, any warranties or conditions
-          of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-          PARTICULAR PURPOSE. You are solely responsible for determining the
-          appropriateness of using or redistributing the Work and assume any
-          risks associated with Your exercise of permissions under this License.
-
-       8. Limitation of Liability. In no event and under no legal theory,
-          whether in tort (including negligence), contract, or otherwise,
-          unless required by applicable law (such as deliberate and grossly
-          negligent acts) or agreed to in writing, shall any Contributor be
-          liable to You for damages, including any direct, indirect, special,
-          incidental, or consequential damages of any character arising as a
-          result of this License or out of the use or inability to use the
-          Work (including but not limited to damages for loss of goodwill,
-          work stoppage, computer failure or malfunction, or any and all
-          other commercial damages or losses), even if such Contributor
-          has been advised of the possibility of such damages.
-
-       9. Accepting Warranty or Additional Liability. While redistributing
-          the Work or Derivative Works thereof, You may choose to offer,
-          and charge a fee for, acceptance of support, warranty, indemnity,
-          or other liability obligations and/or rights consistent with this
-          License. However, in accepting such obligations, You may act only
-          on Your own behalf and on Your sole responsibility, not on behalf
-          of any other Contributor, and only if You agree to indemnify,
-          defend, and hold each Contributor harmless for any liability
-          incurred by, or claims asserted against, such Contributor by reason
-          of your accepting any such warranty or additional liability.
-
- -

Notices for Guava: Google Core Libraries for Java

-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
- -

Notices for JSR 305

-
-Copyright (c) 2007-2009, JSR305 expert group
-All rights reserved.
-
-http://www.opensource.org/licenses/bsd-license.php
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of the JSR305 expert group nor the names of its
-      contributors may be used to endorse or promote products derived from
-      this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-
- -

Notices for libphonenumber

-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
- -

Notices for GIFLIB

-
-The GIFLIB distribution is Copyright (c) 1997  Eric S. Raymond
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
- diff --git a/build/README b/build/README deleted file mode 100644 index 3398014..0000000 --- a/build/README +++ /dev/null @@ -1,8 +0,0 @@ -Files in this folder are build extensions specific to the Bugle project, but currently could be merged into the core Android build. - -gcheckstyle is a tool from google3 to validate java from google3. The config file is baked into the jar, but can be updated with a little hackery. - -Files last pulled around CL 66008148: -gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml is the config file which came from /home/build/nonconf/google3/tools/java/checkstyle and was modified to use Android specific styles. It must keep the same name and path in order to correctly update the jar - -google-style-checker_deploy.jar is pulled from the build share at /google/data/ro/teams/devtools/glint/linters/live/google-style-checker_deploy.jar and can be pulled from there as needed. diff --git a/build/android_lint.mk b/build/android_lint.mk deleted file mode 100644 index 3a9a3d7..0000000 --- a/build/android_lint.mk +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Android lint checks for common code/resource errors specific to Android - -# Lint tool expects the intermediates dir to match the project name Bugle rather -# than $(LOCAL_PACKAGE_NAME) which is messaging -# Create a symbolic link to redirect the tool -$(LOCAL_PACKAGE_NAME)BUGLE_RENAME := $(subst $(LOCAL_PACKAGE_NAME)_intermediates,Bugle_intermediates,$(intermediates.COMMON)) -$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME): SOURCE_PATH := $(abspath $(intermediates.COMMON)/) -$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME): DST_PATH := $($(LOCAL_PACKAGE_NAME)BUGLE_RENAME) -$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME) : - ln -f -s $(SOURCE_PATH) $(DST_PATH) - -# Lint tool expects api-versions.xml from the SDK to be in development/sdk but -# it's not. Create a symbolic link to the android SDK to fix it -API_VERSIONS_XML := $(abspath development/sdk/api-versions.xml) -$(API_VERSIONS_XML): - ln -f -s $(abspath prebuilts/fullsdk/linux/platform-tools/api/api-versions.xml) $(API_VERSIONS_XML) - -# The output xml file from the lint tool -$(LOCAL_PACKAGE_NAME)LINT_XML := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_android_lint.xml - -# The transformed text file from the output xml -$(LOCAL_PACKAGE_NAME)LINT_TXT := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_android_lint.txt - -# Creates the output xml from the lint tool by running the linting tool if the -# package has been updated -$($(LOCAL_PACKAGE_NAME)LINT_XML): PRIVATE_PATH := $(LOCAL_PATH) -$($(LOCAL_PACKAGE_NAME)LINT_XML): LINT_CMD = $(LINT) --quiet -Wall --disable UnusedIds,UnusedResources,MissingTranslation $(PRIVATE_PATH) -$($(LOCAL_PACKAGE_NAME)LINT_XML) : $(LOCAL_BUILT_MODULE) $($(LOCAL_PACKAGE_NAME)BUGLE_RENAME) $(API_VERSIONS_XML) - $(LINT_CMD) --xml $@ > /dev/null - -# Creates the transformed text file from the output xml by running an xslt on it -# which filters out issues from the support library and formats it for console -# output -$($(LOCAL_PACKAGE_NAME)LINT_TXT): PRIVATE_PATH := $(LOCAL_PATH) -$($(LOCAL_PACKAGE_NAME)LINT_TXT): INPUT := $($(LOCAL_PACKAGE_NAME)LINT_XML) -$($(LOCAL_PACKAGE_NAME)LINT_TXT): XSLT_CMD = xsltproc $(PRIVATE_PATH)/build/android_lint.xslt $(INPUT) -$($(LOCAL_PACKAGE_NAME)LINT_TXT) : $($(LOCAL_PACKAGE_NAME)LINT_XML) - $(hide) $(XSLT_CMD) > $@ - -# The root of the lint rule which just prints the lint errors txt file to the -# console -$(LOCAL_PACKAGE_NAME)lint: PRIVATE_PATH := $(LOCAL_PATH) -$(LOCAL_PACKAGE_NAME)lint :: $($(LOCAL_PACKAGE_NAME)LINT_TXT) - $(hide) $(PRIVATE_PATH)/build/colorize_errors.py < $< - diff --git a/build/android_lint.xslt b/build/android_lint.xslt deleted file mode 100644 index d0ddc1c..0000000 --- a/build/android_lint.xslt +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - : [] - - - - : - - , - - - - - - - - diff --git a/build/colorize_errors.py b/build/colorize_errors.py deleted file mode 100755 index e33edc0..0000000 --- a/build/colorize_errors.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Given an input stream look for Error: and Warning: lines and colorize those to -# the output - -import fileinput -import re -import sys - -RED = "\033[31m" -YELLOW = "\033[33m" -RESET = "\033[0m" - -ERROR = re.compile(r"^Error:") -WARNING = re.compile(r"^Warning:") -STARTS_WITH_WS = re.compile(r"^\s") - -for line in fileinput.input(): - if ERROR.match(line): - print RED + line, - elif WARNING.match(line): - print YELLOW + line, - elif STARTS_WITH_WS.match(line): - # If the line starts with a space use the same coloring as the previous line - print line, - else: - print RESET + line, -print RESET - diff --git a/build/gcheckstyle.mk b/build/gcheckstyle.mk deleted file mode 100644 index 61890ee..0000000 --- a/build/gcheckstyle.mk +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# The config file to use when checking style issues -STYLE_CONFIG := $(LOCAL_PATH)/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml - -# The jar file to use to perform the checking -STYLE_JAR := $(LOCAL_PATH)/build/gcheckstyle/google-style-checker_deploy.jar - -# The output file to cache the results of style error checking -$(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_style_errors.txt - -# The set of input files to check -$(LOCAL_PACKAGE_NAME)JAVA_FILES = $(shell find $(LOCAL_PATH)/src/ -name '*.java') - -# Updates the JAR file with the new config if the config has changed -# The config file has to be packaged into the jar, and jar uf command expects the current working directory -# to match the jar structure -$(STYLE_JAR): PRIVATE_PATH := $(LOCAL_PATH) -$(STYLE_JAR) : $(STYLE_CONFIG) - $(hide) pushd $(PRIVATE_PATH)/build/gcheckstyle && \ - jar uf google-style-checker_deploy.jar tools/java/checkstyle/googlestyle-5.0.xml && \ - popd - -# Rebuilds the style errors text if the style checker or any of the java files have changed -# FLAG: It may be more efficient to cache individual file results rather than grouping them all together -$($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT): SOURCES := $($(LOCAL_PACKAGE_NAME)JAVA_FILES) -$($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT) : $($(LOCAL_PACKAGE_NAME)JAVA_FILES) $(STYLE_JAR) - $(hide) -/usr/local/buildtools/java/jdk/bin/java -jar $(STYLE_JAR) $(SOURCES) > $@ - -# The root of the lint rule which just prints the style errors txt file to the console -$(LOCAL_PACKAGE_NAME)lint :: $($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT) - $(hide) $(PRIVATE_PATH)/build/process_style_output.py -omit $(abspath $(PRIVATE_PATH))/ < $< | $(PRIVATE_PATH)/build/colorize_errors.py - diff --git a/build/gcheckstyle/google-style-checker_deploy.jar b/build/gcheckstyle/google-style-checker_deploy.jar deleted file mode 100644 index 86afcbc..0000000 Binary files a/build/gcheckstyle/google-style-checker_deploy.jar and /dev/null differ diff --git a/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml b/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml deleted file mode 100644 index 6960849..0000000 --- a/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/process_style_output.py b/build/process_style_output.py deleted file mode 100755 index 63ca9a0..0000000 --- a/build/process_style_output.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Take the output from gstylechecker and clean up into a more friendly format - -import argparse -import fileinput -import re - -parser = argparse.ArgumentParser() -parser.add_argument('-omit') -args = parser.parse_args() - -PATTERN = re.compile(r"(?P[^:]+):(?P\d+)(?P:\d+)?:\s(?P.*)") - -for line in fileinput.input([]): - match = PATTERN.match(line) - if match: - filename = match.group("file"); - if args.omit: - filename = filename.replace(args.omit, "") - message = match.group("message") - message = message[0].upper() + message[1:] - print message - print " " + filename + ':' + match.group("line") + (match.group("column") if match.group("column") else "") - diff --git a/jni/Android.mk b/jni/Android.mk deleted file mode 100644 index cd89b8f..0000000 --- a/jni/Android.mk +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_STATIC_LIBRARIES += libgif - -# Link to Android logging (liblog.so) and dynamic linker (libdl.so) libraries -LOCAL_LDFLAGS := -llog -ldl - -LOCAL_C_INCLUDES := \ - external/giflib - -LOCAL_MODULE := libgiftranscode -LOCAL_SRC_FILES := GifTranscoder.cpp - -LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-switch - -LOCAL_SDK_VERSION := 8 -LOCAL_NDK_STL_VARIANT := c++_static # LLVM libc++ - -include $(BUILD_SHARED_LIBRARY) diff --git a/jni/GifTranscoder.cpp b/jni/GifTranscoder.cpp deleted file mode 100644 index 44fa30c..0000000 --- a/jni/GifTranscoder.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include - -#include - -#include "GifTranscoder.h" - -#define SQUARE(a) (a)*(a) - -// GIF does not support partial transparency, so our alpha channels are always 0x0 or 0xff. -static const ColorARGB TRANSPARENT = 0x0; - -#define ALPHA(color) (((color) >> 24) & 0xff) -#define RED(color) (((color) >> 16) & 0xff) -#define GREEN(color) (((color) >> 8) & 0xff) -#define BLUE(color) (((color) >> 0) & 0xff) - -#define MAKE_COLOR_ARGB(a, r, g, b) \ - ((a) << 24 | (r) << 16 | (g) << 8 | (b)) - -#define MAX_COLOR_DISTANCE 255 * 255 * 255 - -#define TAG "GifTranscoder.cpp" -#define LOGD_ENABLED 0 -#if LOGD_ENABLED -#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)) -#else -#define LOGD(...) ((void)0) -#endif -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)) -#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)) -#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)) - -// This macro expects the assertion to pass, but logs a FATAL if not. -#define ASSERT(cond, ...) \ - ( (__builtin_expect((cond) == 0, 0)) \ - ? ((void)__android_log_assert(#cond, TAG, ## __VA_ARGS__)) \ - : (void) 0 ) -#define ASSERT_ENABLED 1 - -namespace { - -// Current time in milliseconds since Unix epoch. -double now(void) { - struct timespec res; - clock_gettime(CLOCK_REALTIME, &res); - return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; -} - -// Gets the pixel at position (x,y) from a buffer that uses row-major order to store an image with -// the specified width. -template -T* getPixel(T* buffer, int width, int x, int y) { - return buffer + (y * width + x); -} - -} // namespace - -int GifTranscoder::transcode(const char* pathIn, const char* pathOut) { - int error; - double t0; - GifFileType* gifIn; - GifFileType* gifOut; - - // Automatically closes the GIF files when this method returns - GifFilesCloser closer; - - gifIn = DGifOpenFileName(pathIn, &error); - if (gifIn) { - closer.setGifIn(gifIn); - LOGD("Opened input GIF: %s", pathIn); - } else { - LOGE("Could not open input GIF: %s, error = %d", pathIn, error); - return GIF_ERROR; - } - - gifOut = EGifOpenFileName(pathOut, false, &error); - if (gifOut) { - closer.setGifOut(gifOut); - LOGD("Opened output GIF: %s", pathOut); - } else { - LOGE("Could not open output GIF: %s, error = %d", pathOut, error); - return GIF_ERROR; - } - - t0 = now(); - if (resizeBoxFilter(gifIn, gifOut)) { - LOGD("Resized GIF in %.2f ms", now() - t0); - } else { - LOGE("Could not resize GIF"); - return GIF_ERROR; - } - - return GIF_OK; -} - -bool GifTranscoder::resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut) { - ASSERT(gifIn != NULL, "gifIn cannot be NULL"); - ASSERT(gifOut != NULL, "gifOut cannot be NULL"); - - if (gifIn->SWidth < 0 || gifIn->SHeight < 0) { - LOGE("Input GIF has invalid size: %d x %d", gifIn->SWidth, gifIn->SHeight); - return false; - } - - // Output GIF will be 50% the size of the original. - if (EGifPutScreenDesc(gifOut, - gifIn->SWidth / 2, - gifIn->SHeight / 2, - gifIn->SColorResolution, - gifIn->SBackGroundColor, - gifIn->SColorMap) == GIF_ERROR) { - LOGE("Could not write screen descriptor"); - return false; - } - LOGD("Wrote screen descriptor"); - - // Index of the current image. - int imageIndex = 0; - - // Transparent color of the current image. - int transparentColor = NO_TRANSPARENT_COLOR; - - // Buffer for reading raw images from the input GIF. - std::vector srcBuffer(gifIn->SWidth * gifIn->SHeight); - - // Buffer for rendering images from the input GIF. - std::unique_ptr renderBuffer(new ColorARGB[gifIn->SWidth * gifIn->SHeight]); - - // Buffer for writing new images to output GIF (one row at a time). - std::unique_ptr dstRowBuffer(new GifByteType[gifOut->SWidth]); - - // Many GIFs use DISPOSE_DO_NOT to make images draw on top of previous images. They can also - // use DISPOSE_BACKGROUND to clear the last image region before drawing the next one. We need - // to keep track of the disposal mode as we go along to properly render the GIF. - int disposalMode = DISPOSAL_UNSPECIFIED; - int prevImageDisposalMode = DISPOSAL_UNSPECIFIED; - GifImageDesc prevImageDimens; - - // Background color (applies to entire GIF). - ColorARGB bgColor = TRANSPARENT; - - GifRecordType recordType; - do { - if (DGifGetRecordType(gifIn, &recordType) == GIF_ERROR) { - LOGE("Could not get record type"); - return false; - } - LOGD("Read record type: %d", recordType); - switch (recordType) { - case IMAGE_DESC_RECORD_TYPE: { - if (DGifGetImageDesc(gifIn) == GIF_ERROR) { - LOGE("Could not read image descriptor (%d)", imageIndex); - return false; - } - - // Sanity-check the current image position. - if (gifIn->Image.Left < 0 || - gifIn->Image.Top < 0 || - gifIn->Image.Left + gifIn->Image.Width > gifIn->SWidth || - gifIn->Image.Top + gifIn->Image.Height > gifIn->SHeight) { - LOGE("GIF image extends beyond logical screen"); - return false; - } - - // Write the new image descriptor. - if (EGifPutImageDesc(gifOut, - 0, // Left - 0, // Top - gifOut->SWidth, - gifOut->SHeight, - false, // Interlace - gifIn->Image.ColorMap) == GIF_ERROR) { - LOGE("Could not write image descriptor (%d)", imageIndex); - return false; - } - - // Read the image from the input GIF. The buffer is already initialized to the - // size of the GIF, which is usually equal to the size of all the images inside it. - // If not, the call to resize below ensures that the buffer is the right size. - srcBuffer.resize(gifIn->Image.Width * gifIn->Image.Height); - if (readImage(gifIn, srcBuffer.data()) == false) { - LOGE("Could not read image data (%d)", imageIndex); - return false; - } - LOGD("Read image data (%d)", imageIndex); - // Render the image from the input GIF. - if (renderImage(gifIn, - srcBuffer.data(), - imageIndex, - transparentColor, - renderBuffer.get(), - bgColor, - prevImageDimens, - prevImageDisposalMode) == false) { - LOGE("Could not render %d", imageIndex); - return false; - } - LOGD("Rendered image (%d)", imageIndex); - - // Generate the image in the output GIF. - for (int y = 0; y < gifOut->SHeight; y++) { - for (int x = 0; x < gifOut->SWidth; x++) { - const GifByteType dstColorIndex = computeNewColorIndex( - gifIn, transparentColor, renderBuffer.get(), x, y); - *(dstRowBuffer.get() + x) = dstColorIndex; - } - if (EGifPutLine(gifOut, dstRowBuffer.get(), gifOut->SWidth) == GIF_ERROR) { - LOGE("Could not write raster data (%d)", imageIndex); - return false; - } - } - LOGD("Wrote raster data (%d)", imageIndex); - - // Save the disposal mode for rendering the next image. - // We only support DISPOSE_DO_NOT and DISPOSE_BACKGROUND. - prevImageDisposalMode = disposalMode; - if (prevImageDisposalMode == DISPOSAL_UNSPECIFIED) { - prevImageDisposalMode = DISPOSE_DO_NOT; - } else if (prevImageDisposalMode == DISPOSE_PREVIOUS) { - prevImageDisposalMode = DISPOSE_BACKGROUND; - } - if (prevImageDisposalMode == DISPOSE_BACKGROUND) { - prevImageDimens.Left = gifIn->Image.Left; - prevImageDimens.Top = gifIn->Image.Top; - prevImageDimens.Width = gifIn->Image.Width; - prevImageDimens.Height = gifIn->Image.Height; - } - - if (gifOut->Image.ColorMap) { - GifFreeMapObject(gifOut->Image.ColorMap); - gifOut->Image.ColorMap = NULL; - } - - imageIndex++; - } break; - case EXTENSION_RECORD_TYPE: { - int extCode; - GifByteType* ext; - if (DGifGetExtension(gifIn, &extCode, &ext) == GIF_ERROR) { - LOGE("Could not read extension block"); - return false; - } - LOGD("Read extension block, code: %d", extCode); - if (extCode == GRAPHICS_EXT_FUNC_CODE) { - GraphicsControlBlock gcb; - if (DGifExtensionToGCB(ext[0], ext + 1, &gcb) == GIF_ERROR) { - LOGE("Could not interpret GCB extension"); - return false; - } - transparentColor = gcb.TransparentColor; - - // This logic for setting the background color based on the first GCB - // doesn't quite match the GIF spec, but empirically it seems to work and it - // matches what libframesequence (Rastermill) does. - if (imageIndex == 0 && gifIn->SColorMap) { - if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) { - GifColorType bgColorIndex = - gifIn->SColorMap->Colors[gifIn->SBackGroundColor]; - bgColor = gifColorToColorARGB(bgColorIndex); - LOGD("Set background color based on first GCB"); - } - } - - // Record the original disposal mode and then update it. - disposalMode = gcb.DisposalMode; - gcb.DisposalMode = DISPOSE_BACKGROUND; - EGifGCBToExtension(&gcb, ext + 1); - } - if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) { - LOGE("Could not write extension leader"); - return false; - } - if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) { - LOGE("Could not write extension block"); - return false; - } - LOGD("Wrote extension block"); - while (ext != NULL) { - if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) { - LOGE("Could not read extension continuation"); - return false; - } - if (ext != NULL) { - LOGD("Read extension continuation"); - if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) { - LOGE("Could not write extension continuation"); - return false; - } - LOGD("Wrote extension continuation"); - } - } - if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) { - LOGE("Could not write extension trailer"); - return false; - } - } break; - } - - } while (recordType != TERMINATE_RECORD_TYPE); - LOGD("No more records"); - - return true; -} - -bool GifTranscoder::readImage(GifFileType* gifIn, GifByteType* rasterBits) { - if (gifIn->Image.Interlace) { - int interlacedOffset[] = { 0, 4, 2, 1 }; - int interlacedJumps[] = { 8, 8, 4, 2 }; - - // Need to perform 4 passes on the image - for (int i = 0; i < 4; i++) { - for (int j = interlacedOffset[i]; j < gifIn->Image.Height; j += interlacedJumps[i]) { - if (DGifGetLine(gifIn, - rasterBits + j * gifIn->Image.Width, - gifIn->Image.Width) == GIF_ERROR) { - LOGE("Could not read interlaced raster data"); - return false; - } - } - } - } else { - if (DGifGetLine(gifIn, rasterBits, gifIn->Image.Width * gifIn->Image.Height) == GIF_ERROR) { - LOGE("Could not read raster data"); - return false; - } - } - return true; -} - -bool GifTranscoder::renderImage(GifFileType* gifIn, - GifByteType* rasterBits, - int imageIndex, - int transparentColorIndex, - ColorARGB* renderBuffer, - ColorARGB bgColor, - GifImageDesc prevImageDimens, - int prevImageDisposalMode) { - ASSERT(imageIndex < gifIn->ImageCount, - "Image index %d is out of bounds (count=%d)", imageIndex, gifIn->ImageCount); - - ColorMapObject* colorMap = getColorMap(gifIn); - if (colorMap == NULL) { - LOGE("No GIF color map found"); - return false; - } - - // Clear all or part of the background, before drawing the first image and maybe before drawing - // subsequent images (depending on the DisposalMode). - if (imageIndex == 0) { - fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight, - 0, 0, gifIn->SWidth, gifIn->SHeight, bgColor); - } else if (prevImageDisposalMode == DISPOSE_BACKGROUND) { - fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight, - prevImageDimens.Left, prevImageDimens.Top, - prevImageDimens.Width, prevImageDimens.Height, TRANSPARENT); - } - - // Paint this image onto the canvas - for (int y = 0; y < gifIn->Image.Height; y++) { - for (int x = 0; x < gifIn->Image.Width; x++) { - GifByteType colorIndex = *getPixel(rasterBits, gifIn->Image.Width, x, y); - - // This image may be smaller than the GIF's "logical screen" - int renderX = x + gifIn->Image.Left; - int renderY = y + gifIn->Image.Top; - - // Skip drawing transparent pixels if this image renders on top of the last one - if (imageIndex > 0 && prevImageDisposalMode == DISPOSE_DO_NOT && - colorIndex == transparentColorIndex) { - continue; - } - - ColorARGB* renderPixel = getPixel(renderBuffer, gifIn->SWidth, renderX, renderY); - *renderPixel = getColorARGB(colorMap, transparentColorIndex, colorIndex); - } - } - return true; -} - -void GifTranscoder::fillRect(ColorARGB* renderBuffer, - int imageWidth, - int imageHeight, - int left, - int top, - int width, - int height, - ColorARGB color) { - ASSERT(left + width <= imageWidth, "Rectangle is outside image bounds"); - ASSERT(top + height <= imageHeight, "Rectangle is outside image bounds"); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - ColorARGB* renderPixel = getPixel(renderBuffer, imageWidth, x + left, y + top); - *renderPixel = color; - } - } -} - -GifByteType GifTranscoder::computeNewColorIndex(GifFileType* gifIn, - int transparentColorIndex, - ColorARGB* renderBuffer, - int x, - int y) { - ColorMapObject* colorMap = getColorMap(gifIn); - - // Compute the average color of 4 adjacent pixels from the input image. - ColorARGB c1 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2); - ColorARGB c2 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2); - ColorARGB c3 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2 + 1); - ColorARGB c4 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2 + 1); - ColorARGB avgColor = computeAverage(c1, c2, c3, c4); - - // Search the color map for the best match. - return findBestColor(colorMap, transparentColorIndex, avgColor); -} - -ColorARGB GifTranscoder::computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4) { - char avgAlpha = (char)(((int) ALPHA(c1) + (int) ALPHA(c2) + - (int) ALPHA(c3) + (int) ALPHA(c4)) / 4); - char avgRed = (char)(((int) RED(c1) + (int) RED(c2) + - (int) RED(c3) + (int) RED(c4)) / 4); - char avgGreen = (char)(((int) GREEN(c1) + (int) GREEN(c2) + - (int) GREEN(c3) + (int) GREEN(c4)) / 4); - char avgBlue = (char)(((int) BLUE(c1) + (int) BLUE(c2) + - (int) BLUE(c3) + (int) BLUE(c4)) / 4); - return MAKE_COLOR_ARGB(avgAlpha, avgRed, avgGreen, avgBlue); -} - -GifByteType GifTranscoder::findBestColor(ColorMapObject* colorMap, int transparentColorIndex, - ColorARGB targetColor) { - // Return the transparent color if the average alpha is zero. - char alpha = ALPHA(targetColor); - if (alpha == 0 && transparentColorIndex != NO_TRANSPARENT_COLOR) { - return transparentColorIndex; - } - - GifByteType closestColorIndex = 0; - int closestColorDistance = MAX_COLOR_DISTANCE; - for (int i = 0; i < colorMap->ColorCount; i++) { - // Skip the transparent color (we've already eliminated that option). - if (i == transparentColorIndex) { - continue; - } - ColorARGB indexedColor = gifColorToColorARGB(colorMap->Colors[i]); - int distance = computeDistance(targetColor, indexedColor); - if (distance < closestColorDistance) { - closestColorIndex = i; - closestColorDistance = distance; - } - } - return closestColorIndex; -} - -int GifTranscoder::computeDistance(ColorARGB c1, ColorARGB c2) { - return SQUARE(RED(c1) - RED(c2)) + - SQUARE(GREEN(c1) - GREEN(c2)) + - SQUARE(BLUE(c1) - BLUE(c2)); -} - -ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) { - if (gifIn->Image.ColorMap) { - return gifIn->Image.ColorMap; - } - return gifIn->SColorMap; -} - -ColorARGB GifTranscoder::getColorARGB(ColorMapObject* colorMap, int transparentColorIndex, - GifByteType colorIndex) { - if (colorIndex == transparentColorIndex) { - return TRANSPARENT; - } - return gifColorToColorARGB(colorMap->Colors[colorIndex]); -} - -ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) { - return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue); -} - -GifFilesCloser::~GifFilesCloser() { - if (mGifIn) { - DGifCloseFile(mGifIn); - mGifIn = NULL; - } - if (mGifOut) { - EGifCloseFile(mGifOut); - mGifOut = NULL; - } -} - -void GifFilesCloser::setGifIn(GifFileType* gifIn) { - ASSERT(mGifIn == NULL, "mGifIn is already set"); - mGifIn = gifIn; -} - -void GifFilesCloser::releaseGifIn() { - ASSERT(mGifIn != NULL, "mGifIn is already NULL"); - mGifIn = NULL; -} - -void GifFilesCloser::setGifOut(GifFileType* gifOut) { - ASSERT(mGifOut == NULL, "mGifOut is already set"); - mGifOut = gifOut; -} - -void GifFilesCloser::releaseGifOut() { - ASSERT(mGifOut != NULL, "mGifOut is already NULL"); - mGifOut = NULL; -} - -// JNI stuff - -jboolean transcode(JNIEnv* env, jobject clazz, jstring filePath, jstring outFilePath) { - const char* pathIn = env->GetStringUTFChars(filePath, JNI_FALSE); - const char* pathOut = env->GetStringUTFChars(outFilePath, JNI_FALSE); - - GifTranscoder transcoder; - int gifCode = transcoder.transcode(pathIn, pathOut); - - env->ReleaseStringUTFChars(filePath, pathIn); - env->ReleaseStringUTFChars(outFilePath, pathOut); - - return (gifCode == GIF_OK); -} - -const char *kClassPathName = "com/android/messaging/util/GifTranscoder"; - -JNINativeMethod kMethods[] = { - { "transcodeInternal", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)transcode }, -}; - -int registerNativeMethods(JNIEnv* env, const char* className, - JNINativeMethod* gMethods, int numMethods) { - jclass clazz = env->FindClass(className); - if (clazz == NULL) { - return JNI_FALSE; - } - if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { - return JNI_FALSE; - } - return JNI_TRUE; -} - -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - return -1; - } - if (!registerNativeMethods(env, kClassPathName, - kMethods, sizeof(kMethods) / sizeof(kMethods[0]))) { - return -1; - } - return JNI_VERSION_1_6; -} diff --git a/jni/GifTranscoder.h b/jni/GifTranscoder.h deleted file mode 100644 index 39ecc24..0000000 --- a/jni/GifTranscoder.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef GIF_TRANSCODER_H -#define GIF_TRANSCODER_H - -#include - -#include "gif_lib.h" - -// 24-bit color with alpha, stored in order: A, R, G, B. -// The internal GIF render buffer stores pixels using this format. -typedef uint32_t ColorARGB; - -// Compresses a GIF (probably animated) so it can be sent via MMS, which generally has a 1 MB limit -// on attachments. GIF image data is already compressed (LZW), so to achieve further reduction in -// file size, we reduce the image dimensions. -// -// Helpful GIF references: -// GIF89A spec: http://www.w3.org/Graphics/GIF/spec-gif89a.txt -// What's in a GIF: http://giflib.sourceforge.net/whatsinagif/index.html -// -class GifTranscoder { -public: - GifTranscoder() {} - ~GifTranscoder() {} - - // Resizes a GIF's width and height to 50% of their original dimensions. The new file is - // written to pathOut. - // - // The image is resized using a box filter, which averages the colors in each 2x2 box of pixels - // in the source to generate the color of the pixel in the destination. - // - // Returns GIF_OK (1) on success, or GIF_ERROR (0) on failure. - int transcode(const char* pathIn, const char* pathOut); - -private: - // Implementation of the box filter algorithm. - static bool resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut); - - // Reads the raster data for the current image of the GIF. - static bool readImage(GifFileType* gifIn, GifByteType* rasterBits); - - // Renders the current image of the GIF into the supplied render buffer. - static bool renderImage(GifFileType* gifIn, - GifByteType* rasterBits, - int imageIndex, - int transparentColorIndex, - ColorARGB* renderBuffer, - ColorARGB bgColor, - GifImageDesc prevImageDimens, - int prevImageDisposalMode); - - // Fills a rectangle in the buffer with a solid color. - static void fillRect(ColorARGB* renderBuffer, - int imageWidth, - int imageHeight, - int left, - int top, - int width, - int height, - ColorARGB color); - - // Computes the color for the pixel (x,y) in the current image in the output GIF. - static GifByteType computeNewColorIndex(GifFileType* gifIn, - int transparentColorIndex, - ColorARGB* renderBuffer, - int x, - int y); - - // Computes the average color (by averaging the per-channel (ARGB) values). - static ColorARGB computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4); - - // Searches a color map for the color closest (Euclidean distance) to the target color. - static GifByteType findBestColor(ColorMapObject* colorMap, int transparentColorIndex, - ColorARGB targetColor); - - // Computes distance (squared) between 2 colors, considering each channel a separate dimension. - static int computeDistance(ColorARGB c1, ColorARGB c2); - - // Returns the local color map of the current image (if any), or else the global color map. - static ColorMapObject* getColorMap(GifFileType* gifIn); - - // Returns an indexed color from the color map. - static ColorARGB getColorARGB(ColorMapObject* colorMap, int transparentColorIndex, - GifByteType colorIndex); - - // Converts a 24-bit GIF color (RGB) to a 32-bit ARGB color. - static ColorARGB gifColorToColorARGB(const GifColorType& color); -}; - -// Wrapper class that automatically closes the GIF files when the wrapper goes out of scope. -class GifFilesCloser { -public: - GifFilesCloser() {} - ~GifFilesCloser(); - - void setGifIn(GifFileType* gifIn); - void releaseGifIn(); - - void setGifOut(GifFileType* gifOut); - void releaseGifOut(); - -private: - GifFileType* mGifIn = NULL; - GifFileType* mGifOut = NULL; -}; - -#endif // GIF_TRANSCODER_H diff --git a/proguard-release.flags b/proguard-release.flags deleted file mode 100644 index 36b6720..0000000 --- a/proguard-release.flags +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - --assumenosideeffects public class com.android.messaging.util.Trace { - public void beginSection(...); - public void endSection(...); -} --assumenosideeffects public class com.android.messaging.util.Assert { - private void setIfEngBuild(); -} --assumenosideeffects public class com.android.messaging.util.DebugUtils { - public void logCurrentMethod(...); -} diff --git a/proguard-test.flags b/proguard-test.flags deleted file mode 100755 index 46e569f..0000000 --- a/proguard-test.flags +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# These flags are in addition to the one in proguard.flags, which is included by the build -# directives in Android.mk. - --dontobfuscate --dontoptimize - - - -# FLAG(dnotario): Until we rationalize how to handle tests (extensive unit tests will want similar -# settings to these, but maybe we want to require VisibleForTesting attribute), just expose all -# non-private methods. This means we cannot run tests on user builds for the moment. --keep class com.android.messaging.* { - !private *; -} - --keep class com.android.messaging.*.* { - !private *; -} - --keep class com.android.messaging.*.*.* { - !private *; -} - -# Keep the classes needed by emma --keep class com.vladium.** { *; } - -# End of Test Rules diff --git a/proguard.flags b/proguard.flags deleted file mode 100644 index 759f2d4..0000000 --- a/proguard.flags +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Keep enough data for stack traces --renamesourcefileattribute SourceFile --keepattributes SourceFile,LineNumberTable,*Annotation* - -# Keep classes and methods that have the guava @VisibleForTesting annotation --keep @com.google.common.annotations.VisibleForTesting class * --keepclassmembers class * { - @com.google.common.annotations.VisibleForTesting *; -} - -# Keep methods that have the @VisibleForAnimation annotation --keep @interface com.android.messaging.annotation.VisibleForAnimation --keepclassmembers class * { - @com.android.messaging.annotation.VisibleForAnimation *; -} - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class * extends android.support.v4.app.Fragment --keep public class com.android.vcard.* { *; } - --keep class android.support.v4.* { *; } --keep class android.support.v4.*.* { *; } --keep class android.support.v7.* { *; } --keep class android.support.v7.*.* { *; } - -# Keep rastermill classes that need to be accessed from native code (JNI) --keep class android.support.rastermill.** { *; } - -# Preserve the name of the getCaller method so it can find itself in the stack trace it generates --keepclassmembers public class com.android.messaging.util.DebugUtils { - public static java.lang.StackTraceElement getCaller(...); -} - -# Keep the static fields of referenced inner classes of auto-generated R classes, in case we -# access those fields by reflection (e.g. EmojiMarkup) --keepclassmembers class **.R$* { - public static ; -} diff --git a/res/animator/fab_anim.xml b/res/animator/fab_anim.xml deleted file mode 100644 index e06416e..0000000 --- a/res/animator/fab_anim.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - diff --git a/res/color/tab_text_color.xml b/res/color/tab_text_color.xml deleted file mode 100644 index dec20dd..0000000 --- a/res/color/tab_text_color.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png deleted file mode 100644 index e5f8942..0000000 Binary files a/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png deleted file mode 100644 index 11ef458..0000000 Binary files a/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png deleted file mode 100644 index eb948c1..0000000 Binary files a/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png deleted file mode 100644 index b90188e..0000000 Binary files a/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/cab_bg.9.png b/res/drawable-hdpi/cab_bg.9.png deleted file mode 100644 index 1adfbc6..0000000 Binary files a/res/drawable-hdpi/cab_bg.9.png and /dev/null differ diff --git a/res/drawable-hdpi/contact_popup_background.9.png b/res/drawable-hdpi/contact_popup_background.9.png deleted file mode 100644 index 060ffad..0000000 Binary files a/res/drawable-hdpi/contact_popup_background.9.png and /dev/null differ diff --git a/res/drawable-hdpi/fab_new_message_static_shadow.png b/res/drawable-hdpi/fab_new_message_static_shadow.png deleted file mode 100644 index 22e1764..0000000 Binary files a/res/drawable-hdpi/fab_new_message_static_shadow.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_add_gray.png b/res/drawable-hdpi/ic_add_gray.png deleted file mode 100644 index dbbd78d..0000000 Binary files a/res/drawable-hdpi/ic_add_gray.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_add_white.png b/res/drawable-hdpi/ic_add_white.png deleted file mode 100644 index b52309f..0000000 Binary files a/res/drawable-hdpi/ic_add_white.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_archive_small_dark.png b/res/drawable-hdpi/ic_archive_small_dark.png deleted file mode 100644 index b679ebb..0000000 Binary files a/res/drawable-hdpi/ic_archive_small_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_archive_small_light.png b/res/drawable-hdpi/ic_archive_small_light.png deleted file mode 100644 index 783126c..0000000 Binary files a/res/drawable-hdpi/ic_archive_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_archive_undo_small_dark.png b/res/drawable-hdpi/ic_archive_undo_small_dark.png deleted file mode 100644 index 505988d..0000000 Binary files a/res/drawable-hdpi/ic_archive_undo_small_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_archive_undo_small_light.png b/res/drawable-hdpi/ic_archive_undo_small_light.png deleted file mode 100644 index 2c91e44..0000000 Binary files a/res/drawable-hdpi/ic_archive_undo_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_arrow_back_dark.png b/res/drawable-hdpi/ic_arrow_back_dark.png deleted file mode 100644 index 4e5dc89..0000000 Binary files a/res/drawable-hdpi/ic_arrow_back_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_arrow_back_light.png b/res/drawable-hdpi/ic_arrow_back_light.png deleted file mode 100644 index fa1f67b..0000000 Binary files a/res/drawable-hdpi/ic_arrow_back_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_attachment_dark.png b/res/drawable-hdpi/ic_attachment_dark.png deleted file mode 100644 index 10a1028..0000000 Binary files a/res/drawable-hdpi/ic_attachment_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_audio_light.png b/res/drawable-hdpi/ic_audio_light.png deleted file mode 100644 index 9605a5c..0000000 Binary files a/res/drawable-hdpi/ic_audio_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_audio_pause.png b/res/drawable-hdpi/ic_audio_pause.png deleted file mode 100644 index 8797ad0..0000000 Binary files a/res/drawable-hdpi/ic_audio_pause.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_audio_play.png b/res/drawable-hdpi/ic_audio_play.png deleted file mode 100644 index 552dc9c..0000000 Binary files a/res/drawable-hdpi/ic_audio_play.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_camera_front_light.png b/res/drawable-hdpi/ic_camera_front_light.png deleted file mode 100644 index c0ddaf6..0000000 Binary files a/res/drawable-hdpi/ic_camera_front_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_camera_light.png b/res/drawable-hdpi/ic_camera_light.png deleted file mode 100644 index d0406e4..0000000 Binary files a/res/drawable-hdpi/ic_camera_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_camera_rear_light.png b/res/drawable-hdpi/ic_camera_rear_light.png deleted file mode 100644 index 6624c55..0000000 Binary files a/res/drawable-hdpi/ic_camera_rear_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_cancel_small_dark.png b/res/drawable-hdpi/ic_cancel_small_dark.png deleted file mode 100644 index 5de5cfb..0000000 Binary files a/res/drawable-hdpi/ic_cancel_small_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_cancel_small_light.png b/res/drawable-hdpi/ic_cancel_small_light.png deleted file mode 100644 index 05c6b19..0000000 Binary files a/res/drawable-hdpi/ic_cancel_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkbox_blank_light.png b/res/drawable-hdpi/ic_checkbox_blank_light.png deleted file mode 100644 index 879a770..0000000 Binary files a/res/drawable-hdpi/ic_checkbox_blank_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkbox_light.png b/res/drawable-hdpi/ic_checkbox_light.png deleted file mode 100644 index 7aa9f5f..0000000 Binary files a/res/drawable-hdpi/ic_checkbox_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkbox_outline_light.png b/res/drawable-hdpi/ic_checkbox_outline_light.png deleted file mode 100644 index 44ed34e..0000000 Binary files a/res/drawable-hdpi/ic_checkbox_outline_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkmark_circle_blue.png b/res/drawable-hdpi/ic_checkmark_circle_blue.png deleted file mode 100644 index 55f277e..0000000 Binary files a/res/drawable-hdpi/ic_checkmark_circle_blue.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkmark_large.png b/res/drawable-hdpi/ic_checkmark_large.png deleted file mode 100644 index b8ecc06..0000000 Binary files a/res/drawable-hdpi/ic_checkmark_large.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkmark_large_light.png b/res/drawable-hdpi/ic_checkmark_large_light.png deleted file mode 100644 index 48bf283..0000000 Binary files a/res/drawable-hdpi/ic_checkmark_large_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkmark_small_blue.png b/res/drawable-hdpi/ic_checkmark_small_blue.png deleted file mode 100644 index 8661489..0000000 Binary files a/res/drawable-hdpi/ic_checkmark_small_blue.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_checkmark_small_light.png b/res/drawable-hdpi/ic_checkmark_small_light.png deleted file mode 100644 index ae0bf6d..0000000 Binary files a/res/drawable-hdpi/ic_checkmark_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_color_lens.png b/res/drawable-hdpi/ic_color_lens.png deleted file mode 100644 index 69a4287..0000000 Binary files a/res/drawable-hdpi/ic_color_lens.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_content_copy_dark.png b/res/drawable-hdpi/ic_content_copy_dark.png deleted file mode 100644 index 22d2516..0000000 Binary files a/res/drawable-hdpi/ic_content_copy_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_crying_hero.png b/res/drawable-hdpi/ic_crying_hero.png deleted file mode 100644 index 804b4e6..0000000 Binary files a/res/drawable-hdpi/ic_crying_hero.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_delete_small_dark.png b/res/drawable-hdpi/ic_delete_small_dark.png deleted file mode 100644 index 901f992..0000000 Binary files a/res/drawable-hdpi/ic_delete_small_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_delete_small_light.png b/res/drawable-hdpi/ic_delete_small_light.png deleted file mode 100644 index d2ed6d3..0000000 Binary files a/res/drawable-hdpi/ic_delete_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_dnd_on_dark.png b/res/drawable-hdpi/ic_dnd_on_dark.png deleted file mode 100644 index 181df02..0000000 Binary files a/res/drawable-hdpi/ic_dnd_on_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_dnd_on_light.png b/res/drawable-hdpi/ic_dnd_on_light.png deleted file mode 100644 index cadf5b5..0000000 Binary files a/res/drawable-hdpi/ic_dnd_on_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_failed_light.png b/res/drawable-hdpi/ic_failed_light.png deleted file mode 100644 index 3614fbe..0000000 Binary files a/res/drawable-hdpi/ic_failed_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_failed_status_red.png b/res/drawable-hdpi/ic_failed_status_red.png deleted file mode 100644 index aa2d138..0000000 Binary files a/res/drawable-hdpi/ic_failed_status_red.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_file_download_dark.png b/res/drawable-hdpi/ic_file_download_dark.png deleted file mode 100644 index 028c85e..0000000 Binary files a/res/drawable-hdpi/ic_file_download_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_file_download_light.png b/res/drawable-hdpi/ic_file_download_light.png deleted file mode 100644 index ffa5b82..0000000 Binary files a/res/drawable-hdpi/ic_file_download_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_forward_dark.png b/res/drawable-hdpi/ic_forward_dark.png deleted file mode 100644 index e8b718a..0000000 Binary files a/res/drawable-hdpi/ic_forward_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_history_light.png b/res/drawable-hdpi/ic_history_light.png deleted file mode 100644 index 4b512fe..0000000 Binary files a/res/drawable-hdpi/ic_history_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_image_light.png b/res/drawable-hdpi/ic_image_light.png deleted file mode 100644 index 744f920..0000000 Binary files a/res/drawable-hdpi/ic_image_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_ime_dark.png b/res/drawable-hdpi/ic_ime_dark.png deleted file mode 100644 index a539d28..0000000 Binary files a/res/drawable-hdpi/ic_ime_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_ime_light.png b/res/drawable-hdpi/ic_ime_light.png deleted file mode 100644 index da65039..0000000 Binary files a/res/drawable-hdpi/ic_ime_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_info_dark.png b/res/drawable-hdpi/ic_info_dark.png deleted file mode 100644 index 1453228..0000000 Binary files a/res/drawable-hdpi/ic_info_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_keyboard_arrow_left_light.png b/res/drawable-hdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 50f280e..0000000 Binary files a/res/drawable-hdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_keyboard_arrow_right_light.png b/res/drawable-hdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 3e31c08..0000000 Binary files a/res/drawable-hdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 2c52f06..0000000 Binary files a/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_audio_mic.png b/res/drawable-hdpi/ic_mp_audio_mic.png deleted file mode 100644 index 9c005d6..0000000 Binary files a/res/drawable-hdpi/ic_mp_audio_mic.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_camera_large_light.png b/res/drawable-hdpi/ic_mp_camera_large_light.png deleted file mode 100644 index 721b760..0000000 Binary files a/res/drawable-hdpi/ic_mp_camera_large_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_camera_small_light.png b/res/drawable-hdpi/ic_mp_camera_small_light.png deleted file mode 100644 index 6e3b09b..0000000 Binary files a/res/drawable-hdpi/ic_mp_camera_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_capture_stop_large_light.png b/res/drawable-hdpi/ic_mp_capture_stop_large_light.png deleted file mode 100644 index 5e29fdd..0000000 Binary files a/res/drawable-hdpi/ic_mp_capture_stop_large_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_full_screen_light.png b/res/drawable-hdpi/ic_mp_full_screen_light.png deleted file mode 100644 index 9c69eb8..0000000 Binary files a/res/drawable-hdpi/ic_mp_full_screen_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_video_large_light.png b/res/drawable-hdpi/ic_mp_video_large_light.png deleted file mode 100644 index d6db42c..0000000 Binary files a/res/drawable-hdpi/ic_mp_video_large_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mp_video_small_light.png b/res/drawable-hdpi/ic_mp_video_small_light.png deleted file mode 100644 index 9816af7..0000000 Binary files a/res/drawable-hdpi/ic_mp_video_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_notifications_off_dark.png b/res/drawable-hdpi/ic_notifications_off_dark.png deleted file mode 100644 index 9bcdc62..0000000 Binary files a/res/drawable-hdpi/ic_notifications_off_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_notifications_off_light.png b/res/drawable-hdpi/ic_notifications_off_light.png deleted file mode 100644 index 433b6a9..0000000 Binary files a/res/drawable-hdpi/ic_notifications_off_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_notifications_off_small_light.png b/res/drawable-hdpi/ic_notifications_off_small_light.png deleted file mode 100644 index fbd0ad5..0000000 Binary files a/res/drawable-hdpi/ic_notifications_off_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_notifications_on_dark.png b/res/drawable-hdpi/ic_notifications_on_dark.png deleted file mode 100644 index 21e48a5..0000000 Binary files a/res/drawable-hdpi/ic_notifications_on_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_notifications_on_light.png b/res/drawable-hdpi/ic_notifications_on_light.png deleted file mode 100644 index 6b39fec..0000000 Binary files a/res/drawable-hdpi/ic_notifications_on_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_numeric_dialpad.png b/res/drawable-hdpi/ic_numeric_dialpad.png deleted file mode 100644 index 5460fd7..0000000 Binary files a/res/drawable-hdpi/ic_numeric_dialpad.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_oobe_conv_list.png b/res/drawable-hdpi/ic_oobe_conv_list.png deleted file mode 100644 index 3b904ba..0000000 Binary files a/res/drawable-hdpi/ic_oobe_conv_list.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_oobe_freq_list.png b/res/drawable-hdpi/ic_oobe_freq_list.png deleted file mode 100644 index 148a9ea..0000000 Binary files a/res/drawable-hdpi/ic_oobe_freq_list.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_open_in_new.png b/res/drawable-hdpi/ic_open_in_new.png deleted file mode 100644 index 06e5f71..0000000 Binary files a/res/drawable-hdpi/ic_open_in_new.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_people_add_light.png b/res/drawable-hdpi/ic_people_add_light.png deleted file mode 100644 index c506e9a..0000000 Binary files a/res/drawable-hdpi/ic_people_add_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_person_add_blue.png b/res/drawable-hdpi/ic_person_add_blue.png deleted file mode 100644 index dfd2bc1..0000000 Binary files a/res/drawable-hdpi/ic_person_add_blue.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_person_add_dark.png b/res/drawable-hdpi/ic_person_add_dark.png deleted file mode 100644 index 71ca2a0..0000000 Binary files a/res/drawable-hdpi/ic_person_add_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_person_add_light.png b/res/drawable-hdpi/ic_person_add_light.png deleted file mode 100644 index bb8ad3e..0000000 Binary files a/res/drawable-hdpi/ic_person_add_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_person_light.png b/res/drawable-hdpi/ic_person_light.png deleted file mode 100644 index b439c93..0000000 Binary files a/res/drawable-hdpi/ic_person_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_person_light_large.png b/res/drawable-hdpi/ic_person_light_large.png deleted file mode 100644 index 5ddf265..0000000 Binary files a/res/drawable-hdpi/ic_person_light_large.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_phone_small_light.png b/res/drawable-hdpi/ic_phone_small_light.png deleted file mode 100644 index 35cf121..0000000 Binary files a/res/drawable-hdpi/ic_phone_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_photo_library_light.png b/res/drawable-hdpi/ic_photo_library_light.png deleted file mode 100644 index 1f71a62..0000000 Binary files a/res/drawable-hdpi/ic_photo_library_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_preview_pause.png b/res/drawable-hdpi/ic_preview_pause.png deleted file mode 100644 index fd49749..0000000 Binary files a/res/drawable-hdpi/ic_preview_pause.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_preview_play.png b/res/drawable-hdpi/ic_preview_play.png deleted file mode 100644 index db2b2ac..0000000 Binary files a/res/drawable-hdpi/ic_preview_play.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_remove_light.png b/res/drawable-hdpi/ic_remove_light.png deleted file mode 100644 index 3992231..0000000 Binary files a/res/drawable-hdpi/ic_remove_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_remove_small_light.png b/res/drawable-hdpi/ic_remove_small_light.png deleted file mode 100644 index 07e668c..0000000 Binary files a/res/drawable-hdpi/ic_remove_small_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_save_dark.png b/res/drawable-hdpi/ic_save_dark.png deleted file mode 100644 index 7cf01da..0000000 Binary files a/res/drawable-hdpi/ic_save_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_save_light.png b/res/drawable-hdpi/ic_save_light.png deleted file mode 100644 index b76adb2..0000000 Binary files a/res/drawable-hdpi/ic_save_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_search_light.png b/res/drawable-hdpi/ic_search_light.png deleted file mode 100644 index c35418e..0000000 Binary files a/res/drawable-hdpi/ic_search_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_send_dark.png b/res/drawable-hdpi/ic_send_dark.png deleted file mode 100644 index 62108ef..0000000 Binary files a/res/drawable-hdpi/ic_send_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_send_light.png b/res/drawable-hdpi/ic_send_light.png deleted file mode 100644 index 724783d..0000000 Binary files a/res/drawable-hdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_share_dark.png b/res/drawable-hdpi/ic_share_dark.png deleted file mode 100644 index bd27042..0000000 Binary files a/res/drawable-hdpi/ic_share_dark.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_share_light.png b/res/drawable-hdpi/ic_share_light.png deleted file mode 100644 index dadb3e8..0000000 Binary files a/res/drawable-hdpi/ic_share_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_sim_card_send.png b/res/drawable-hdpi/ic_sim_card_send.png deleted file mode 100644 index 426b153..0000000 Binary files a/res/drawable-hdpi/ic_sim_card_send.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_sms_delivery_ok.png b/res/drawable-hdpi/ic_sms_delivery_ok.png deleted file mode 100644 index e038563..0000000 Binary files a/res/drawable-hdpi/ic_sms_delivery_ok.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_sms_failed_light.png b/res/drawable-hdpi/ic_sms_failed_light.png deleted file mode 100644 index 4973427..0000000 Binary files a/res/drawable-hdpi/ic_sms_failed_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_sms_light.png b/res/drawable-hdpi/ic_sms_light.png deleted file mode 100644 index aaeec4e..0000000 Binary files a/res/drawable-hdpi/ic_sms_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_sms_multi_light.png b/res/drawable-hdpi/ic_sms_multi_light.png deleted file mode 100644 index 81ac2e7..0000000 Binary files a/res/drawable-hdpi/ic_sms_multi_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_tooltip_arrow.png b/res/drawable-hdpi/ic_tooltip_arrow.png deleted file mode 100644 index 870d502..0000000 Binary files a/res/drawable-hdpi/ic_tooltip_arrow.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_video_play_light.png b/res/drawable-hdpi/ic_video_play_light.png deleted file mode 100644 index 4a74f37..0000000 Binary files a/res/drawable-hdpi/ic_video_play_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_wear_reply.png b/res/drawable-hdpi/ic_wear_reply.png deleted file mode 100644 index 19ce472..0000000 Binary files a/res/drawable-hdpi/ic_wear_reply.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_widget_avatar_shadow.png b/res/drawable-hdpi/ic_widget_avatar_shadow.png deleted file mode 100644 index 9e0d519..0000000 Binary files a/res/drawable-hdpi/ic_widget_avatar_shadow.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_widget_list.png b/res/drawable-hdpi/ic_widget_list.png deleted file mode 100644 index 06f9732..0000000 Binary files a/res/drawable-hdpi/ic_widget_list.png and /dev/null differ diff --git a/res/drawable-hdpi/msg_bubble_error.9.png b/res/drawable-hdpi/msg_bubble_error.9.png deleted file mode 100644 index 6a26c30..0000000 Binary files a/res/drawable-hdpi/msg_bubble_error.9.png and /dev/null differ diff --git a/res/drawable-hdpi/msg_bubble_incoming.9.png b/res/drawable-hdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 7fed0a8..0000000 Binary files a/res/drawable-hdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-hdpi/msg_bubble_input.9.png b/res/drawable-hdpi/msg_bubble_input.9.png deleted file mode 100644 index 01076da..0000000 Binary files a/res/drawable-hdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-hdpi/msg_bubble_outgoing.9.png b/res/drawable-hdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 8e7ccc0..0000000 Binary files a/res/drawable-hdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-hdpi/permissions.png b/res/drawable-hdpi/permissions.png deleted file mode 100644 index 20697cf..0000000 Binary files a/res/drawable-hdpi/permissions.png and /dev/null differ diff --git a/res/drawable-hdpi/swipe_shadow.9.png b/res/drawable-hdpi/swipe_shadow.9.png deleted file mode 100644 index 8405721..0000000 Binary files a/res/drawable-hdpi/swipe_shadow.9.png and /dev/null differ diff --git a/res/drawable-hdpi/swipe_shadow_drag.9.png b/res/drawable-hdpi/swipe_shadow_drag.9.png deleted file mode 100644 index 950ec71..0000000 Binary files a/res/drawable-hdpi/swipe_shadow_drag.9.png and /dev/null differ diff --git a/res/drawable-hdpi/sym_keyboard_delete_holo.png b/res/drawable-hdpi/sym_keyboard_delete_holo.png deleted file mode 100644 index 9e74a64..0000000 Binary files a/res/drawable-hdpi/sym_keyboard_delete_holo.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_btn_bg_normal.9.png b/res/drawable-hdpi/tab_btn_bg_normal.9.png deleted file mode 100644 index 791bb40..0000000 Binary files a/res/drawable-hdpi/tab_btn_bg_normal.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_btn_bg_pressed.9.png b/res/drawable-hdpi/tab_btn_bg_pressed.9.png deleted file mode 100644 index b2b488c..0000000 Binary files a/res/drawable-hdpi/tab_btn_bg_pressed.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_selected.9.png b/res/drawable-hdpi/tab_selected.9.png deleted file mode 100644 index 84e63df..0000000 Binary files a/res/drawable-hdpi/tab_selected.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_selected_focused_holo.9.png b/res/drawable-hdpi/tab_selected_focused_holo.9.png deleted file mode 100644 index 5cdc5b4..0000000 Binary files a/res/drawable-hdpi/tab_selected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png deleted file mode 100644 index ca3eb7b..0000000 Binary files a/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_selected_pressed_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_holo.9.png deleted file mode 100644 index 08e5edb..0000000 Binary files a/res/drawable-hdpi/tab_selected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_unselected.9.png b/res/drawable-hdpi/tab_unselected.9.png deleted file mode 100644 index bbcfb2c..0000000 Binary files a/res/drawable-hdpi/tab_unselected.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_unselected_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_focused_holo.9.png deleted file mode 100644 index a0c8bc3..0000000 Binary files a/res/drawable-hdpi/tab_unselected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png deleted file mode 100644 index 6cf948e..0000000 Binary files a/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/tab_unselected_pressed_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png deleted file mode 100644 index a5dce50..0000000 Binary files a/res/drawable-hdpi/tab_unselected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-hdpi/widget_hr.9.png b/res/drawable-hdpi/widget_hr.9.png deleted file mode 100644 index 7590256..0000000 Binary files a/res/drawable-hdpi/widget_hr.9.png and /dev/null differ diff --git a/res/drawable-hdpi/widget_msg_bubble_incoming.9.png b/res/drawable-hdpi/widget_msg_bubble_incoming.9.png deleted file mode 100644 index c6e77a7..0000000 Binary files a/res/drawable-hdpi/widget_msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png deleted file mode 100644 index 476efae..0000000 Binary files a/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 3e31c08..0000000 Binary files a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 50f280e..0000000 Binary files a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/ic_send_light.png b/res/drawable-ldrtl-hdpi/ic_send_light.png deleted file mode 100644 index 4ee98a3..0000000 Binary files a/res/drawable-ldrtl-hdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 8e7ccc0..0000000 Binary files a/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png deleted file mode 100644 index c3f68b0..0000000 Binary files a/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 7fed0a8..0000000 Binary files a/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png deleted file mode 100644 index f0e1b08..0000000 Binary files a/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 9bd625d..0000000 Binary files a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 6004f37..0000000 Binary files a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/ic_send_light.png b/res/drawable-ldrtl-mdpi/ic_send_light.png deleted file mode 100644 index 1267d66..0000000 Binary files a/res/drawable-ldrtl-mdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 5ca9b14..0000000 Binary files a/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png deleted file mode 100644 index 6f0b4bc..0000000 Binary files a/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 4e7e464..0000000 Binary files a/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png deleted file mode 100644 index 346e754..0000000 Binary files a/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 51a1903..0000000 Binary files a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 464a0bc..0000000 Binary files a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/ic_send_light.png b/res/drawable-ldrtl-xhdpi/ic_send_light.png deleted file mode 100644 index 178c3cf..0000000 Binary files a/res/drawable-ldrtl-xhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 52cb936..0000000 Binary files a/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png deleted file mode 100644 index 4c5f45d..0000000 Binary files a/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 0661fdb..0000000 Binary files a/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png deleted file mode 100644 index a662fc2..0000000 Binary files a/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 62ca382..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 09536ed..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/ic_send_light.png b/res/drawable-ldrtl-xxhdpi/ic_send_light.png deleted file mode 100644 index 74ffc46..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 5d8fc74..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png deleted file mode 100644 index 6d59b8f..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 009c721..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png deleted file mode 100644 index 273efd6..0000000 Binary files a/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 85d005b..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 3737fec..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/ic_send_light.png b/res/drawable-ldrtl-xxxhdpi/ic_send_light.png deleted file mode 100644 index cd8e266..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 8378db5..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png deleted file mode 100644 index a707db0..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index f0d0d9e..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png deleted file mode 100644 index 9203260..0000000 Binary files a/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png deleted file mode 100644 index 577ab08..0000000 Binary files a/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png deleted file mode 100644 index d8525e4..0000000 Binary files a/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png deleted file mode 100644 index 1b029e1..0000000 Binary files a/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png deleted file mode 100644 index 4fa69f8..0000000 Binary files a/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/cab_bg.9.png b/res/drawable-mdpi/cab_bg.9.png deleted file mode 100644 index f43a85b..0000000 Binary files a/res/drawable-mdpi/cab_bg.9.png and /dev/null differ diff --git a/res/drawable-mdpi/contact_popup_background.9.png b/res/drawable-mdpi/contact_popup_background.9.png deleted file mode 100644 index 2989590..0000000 Binary files a/res/drawable-mdpi/contact_popup_background.9.png and /dev/null differ diff --git a/res/drawable-mdpi/fab_new_message_static_shadow.png b/res/drawable-mdpi/fab_new_message_static_shadow.png deleted file mode 100644 index 2e24f0a..0000000 Binary files a/res/drawable-mdpi/fab_new_message_static_shadow.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_add_gray.png b/res/drawable-mdpi/ic_add_gray.png deleted file mode 100644 index 806b6e1..0000000 Binary files a/res/drawable-mdpi/ic_add_gray.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_add_white.png b/res/drawable-mdpi/ic_add_white.png deleted file mode 100644 index 4a14229..0000000 Binary files a/res/drawable-mdpi/ic_add_white.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_archive_small_dark.png b/res/drawable-mdpi/ic_archive_small_dark.png deleted file mode 100644 index a733f56..0000000 Binary files a/res/drawable-mdpi/ic_archive_small_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_archive_small_light.png b/res/drawable-mdpi/ic_archive_small_light.png deleted file mode 100644 index fa3ffa9..0000000 Binary files a/res/drawable-mdpi/ic_archive_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_archive_undo_small_dark.png b/res/drawable-mdpi/ic_archive_undo_small_dark.png deleted file mode 100644 index bc9dead..0000000 Binary files a/res/drawable-mdpi/ic_archive_undo_small_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_archive_undo_small_light.png b/res/drawable-mdpi/ic_archive_undo_small_light.png deleted file mode 100644 index 08b2332..0000000 Binary files a/res/drawable-mdpi/ic_archive_undo_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_arrow_back_dark.png b/res/drawable-mdpi/ic_arrow_back_dark.png deleted file mode 100644 index 75ae1b1..0000000 Binary files a/res/drawable-mdpi/ic_arrow_back_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_arrow_back_light.png b/res/drawable-mdpi/ic_arrow_back_light.png deleted file mode 100644 index c59a517..0000000 Binary files a/res/drawable-mdpi/ic_arrow_back_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_attachment_dark.png b/res/drawable-mdpi/ic_attachment_dark.png deleted file mode 100644 index 2315e23..0000000 Binary files a/res/drawable-mdpi/ic_attachment_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_audio_light.png b/res/drawable-mdpi/ic_audio_light.png deleted file mode 100644 index 78bc072..0000000 Binary files a/res/drawable-mdpi/ic_audio_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_audio_pause.png b/res/drawable-mdpi/ic_audio_pause.png deleted file mode 100644 index e74d158..0000000 Binary files a/res/drawable-mdpi/ic_audio_pause.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_audio_play.png b/res/drawable-mdpi/ic_audio_play.png deleted file mode 100644 index 0d986a4..0000000 Binary files a/res/drawable-mdpi/ic_audio_play.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_camera_front_light.png b/res/drawable-mdpi/ic_camera_front_light.png deleted file mode 100644 index a537897..0000000 Binary files a/res/drawable-mdpi/ic_camera_front_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_camera_light.png b/res/drawable-mdpi/ic_camera_light.png deleted file mode 100644 index 429bef4..0000000 Binary files a/res/drawable-mdpi/ic_camera_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_camera_rear_light.png b/res/drawable-mdpi/ic_camera_rear_light.png deleted file mode 100644 index 4da3328..0000000 Binary files a/res/drawable-mdpi/ic_camera_rear_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_cancel_small_dark.png b/res/drawable-mdpi/ic_cancel_small_dark.png deleted file mode 100644 index f63f351..0000000 Binary files a/res/drawable-mdpi/ic_cancel_small_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_cancel_small_light.png b/res/drawable-mdpi/ic_cancel_small_light.png deleted file mode 100644 index 33fdf10..0000000 Binary files a/res/drawable-mdpi/ic_cancel_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkbox_blank_light.png b/res/drawable-mdpi/ic_checkbox_blank_light.png deleted file mode 100644 index baec804..0000000 Binary files a/res/drawable-mdpi/ic_checkbox_blank_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkbox_light.png b/res/drawable-mdpi/ic_checkbox_light.png deleted file mode 100644 index 3762dc4..0000000 Binary files a/res/drawable-mdpi/ic_checkbox_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkbox_outline_light.png b/res/drawable-mdpi/ic_checkbox_outline_light.png deleted file mode 100644 index 4c3c722..0000000 Binary files a/res/drawable-mdpi/ic_checkbox_outline_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkmark_circle_blue.png b/res/drawable-mdpi/ic_checkmark_circle_blue.png deleted file mode 100644 index 85735be..0000000 Binary files a/res/drawable-mdpi/ic_checkmark_circle_blue.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkmark_large.png b/res/drawable-mdpi/ic_checkmark_large.png deleted file mode 100644 index 95206d1..0000000 Binary files a/res/drawable-mdpi/ic_checkmark_large.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkmark_large_light.png b/res/drawable-mdpi/ic_checkmark_large_light.png deleted file mode 100644 index 4c33ce6..0000000 Binary files a/res/drawable-mdpi/ic_checkmark_large_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkmark_small_blue.png b/res/drawable-mdpi/ic_checkmark_small_blue.png deleted file mode 100644 index ec9dcb1..0000000 Binary files a/res/drawable-mdpi/ic_checkmark_small_blue.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_checkmark_small_light.png b/res/drawable-mdpi/ic_checkmark_small_light.png deleted file mode 100644 index 414fe8f..0000000 Binary files a/res/drawable-mdpi/ic_checkmark_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_color_lens.png b/res/drawable-mdpi/ic_color_lens.png deleted file mode 100644 index b33c8b6..0000000 Binary files a/res/drawable-mdpi/ic_color_lens.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_content_copy_dark.png b/res/drawable-mdpi/ic_content_copy_dark.png deleted file mode 100644 index 4b99a83..0000000 Binary files a/res/drawable-mdpi/ic_content_copy_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_crying_hero.png b/res/drawable-mdpi/ic_crying_hero.png deleted file mode 100644 index d2f5eef..0000000 Binary files a/res/drawable-mdpi/ic_crying_hero.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_delete_small_dark.png b/res/drawable-mdpi/ic_delete_small_dark.png deleted file mode 100644 index 86c0002..0000000 Binary files a/res/drawable-mdpi/ic_delete_small_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_delete_small_light.png b/res/drawable-mdpi/ic_delete_small_light.png deleted file mode 100644 index 641adc4..0000000 Binary files a/res/drawable-mdpi/ic_delete_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_dnd_on_dark.png b/res/drawable-mdpi/ic_dnd_on_dark.png deleted file mode 100644 index 4e911a9..0000000 Binary files a/res/drawable-mdpi/ic_dnd_on_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_dnd_on_light.png b/res/drawable-mdpi/ic_dnd_on_light.png deleted file mode 100644 index 4936c2f..0000000 Binary files a/res/drawable-mdpi/ic_dnd_on_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_failed_light.png b/res/drawable-mdpi/ic_failed_light.png deleted file mode 100644 index 02a4d0b..0000000 Binary files a/res/drawable-mdpi/ic_failed_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_failed_status_red.png b/res/drawable-mdpi/ic_failed_status_red.png deleted file mode 100644 index f6feafe..0000000 Binary files a/res/drawable-mdpi/ic_failed_status_red.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_file_download_dark.png b/res/drawable-mdpi/ic_file_download_dark.png deleted file mode 100644 index 8104657..0000000 Binary files a/res/drawable-mdpi/ic_file_download_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_file_download_light.png b/res/drawable-mdpi/ic_file_download_light.png deleted file mode 100644 index d066170..0000000 Binary files a/res/drawable-mdpi/ic_file_download_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_forward_dark.png b/res/drawable-mdpi/ic_forward_dark.png deleted file mode 100644 index 7930393..0000000 Binary files a/res/drawable-mdpi/ic_forward_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_history_light.png b/res/drawable-mdpi/ic_history_light.png deleted file mode 100644 index 234b2ce..0000000 Binary files a/res/drawable-mdpi/ic_history_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_image_light.png b/res/drawable-mdpi/ic_image_light.png deleted file mode 100644 index 3bd919e..0000000 Binary files a/res/drawable-mdpi/ic_image_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_ime_dark.png b/res/drawable-mdpi/ic_ime_dark.png deleted file mode 100644 index 8c0a156..0000000 Binary files a/res/drawable-mdpi/ic_ime_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_ime_light.png b/res/drawable-mdpi/ic_ime_light.png deleted file mode 100644 index fd58cb5..0000000 Binary files a/res/drawable-mdpi/ic_ime_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_info_dark.png b/res/drawable-mdpi/ic_info_dark.png deleted file mode 100644 index 67457a3..0000000 Binary files a/res/drawable-mdpi/ic_info_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_keyboard_arrow_left_light.png b/res/drawable-mdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 6004f37..0000000 Binary files a/res/drawable-mdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_keyboard_arrow_right_light.png b/res/drawable-mdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 9bd625d..0000000 Binary files a/res/drawable-mdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index a6d2f55..0000000 Binary files a/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_audio_mic.png b/res/drawable-mdpi/ic_mp_audio_mic.png deleted file mode 100644 index 918d799..0000000 Binary files a/res/drawable-mdpi/ic_mp_audio_mic.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_camera_large_light.png b/res/drawable-mdpi/ic_mp_camera_large_light.png deleted file mode 100644 index 3ec72f9..0000000 Binary files a/res/drawable-mdpi/ic_mp_camera_large_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_camera_small_light.png b/res/drawable-mdpi/ic_mp_camera_small_light.png deleted file mode 100644 index fe1dc7e..0000000 Binary files a/res/drawable-mdpi/ic_mp_camera_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_capture_stop_large_light.png b/res/drawable-mdpi/ic_mp_capture_stop_large_light.png deleted file mode 100644 index a9705c1..0000000 Binary files a/res/drawable-mdpi/ic_mp_capture_stop_large_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_full_screen_light.png b/res/drawable-mdpi/ic_mp_full_screen_light.png deleted file mode 100644 index 5462c59..0000000 Binary files a/res/drawable-mdpi/ic_mp_full_screen_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_video_large_light.png b/res/drawable-mdpi/ic_mp_video_large_light.png deleted file mode 100644 index fa8e3e2..0000000 Binary files a/res/drawable-mdpi/ic_mp_video_large_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mp_video_small_light.png b/res/drawable-mdpi/ic_mp_video_small_light.png deleted file mode 100644 index 9204827..0000000 Binary files a/res/drawable-mdpi/ic_mp_video_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_notifications_off_dark.png b/res/drawable-mdpi/ic_notifications_off_dark.png deleted file mode 100644 index 4a5a7e9..0000000 Binary files a/res/drawable-mdpi/ic_notifications_off_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_notifications_off_light.png b/res/drawable-mdpi/ic_notifications_off_light.png deleted file mode 100644 index 33e0b4a..0000000 Binary files a/res/drawable-mdpi/ic_notifications_off_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_notifications_off_small_light.png b/res/drawable-mdpi/ic_notifications_off_small_light.png deleted file mode 100644 index 4bd9563..0000000 Binary files a/res/drawable-mdpi/ic_notifications_off_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_notifications_on_dark.png b/res/drawable-mdpi/ic_notifications_on_dark.png deleted file mode 100644 index 77a2a7d..0000000 Binary files a/res/drawable-mdpi/ic_notifications_on_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_notifications_on_light.png b/res/drawable-mdpi/ic_notifications_on_light.png deleted file mode 100644 index eae03a5..0000000 Binary files a/res/drawable-mdpi/ic_notifications_on_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_numeric_dialpad.png b/res/drawable-mdpi/ic_numeric_dialpad.png deleted file mode 100644 index 5802167..0000000 Binary files a/res/drawable-mdpi/ic_numeric_dialpad.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_oobe_conv_list.png b/res/drawable-mdpi/ic_oobe_conv_list.png deleted file mode 100644 index 1648f2e..0000000 Binary files a/res/drawable-mdpi/ic_oobe_conv_list.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_oobe_freq_list.png b/res/drawable-mdpi/ic_oobe_freq_list.png deleted file mode 100644 index 8d7d47f..0000000 Binary files a/res/drawable-mdpi/ic_oobe_freq_list.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_open_in_new.png b/res/drawable-mdpi/ic_open_in_new.png deleted file mode 100644 index 44ef9d5..0000000 Binary files a/res/drawable-mdpi/ic_open_in_new.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_people_add_light.png b/res/drawable-mdpi/ic_people_add_light.png deleted file mode 100644 index 4bc974f..0000000 Binary files a/res/drawable-mdpi/ic_people_add_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_person_add_blue.png b/res/drawable-mdpi/ic_person_add_blue.png deleted file mode 100644 index 29f9066..0000000 Binary files a/res/drawable-mdpi/ic_person_add_blue.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_person_add_dark.png b/res/drawable-mdpi/ic_person_add_dark.png deleted file mode 100644 index 6f1fd49..0000000 Binary files a/res/drawable-mdpi/ic_person_add_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_person_add_light.png b/res/drawable-mdpi/ic_person_add_light.png deleted file mode 100644 index ba83709..0000000 Binary files a/res/drawable-mdpi/ic_person_add_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_person_light.png b/res/drawable-mdpi/ic_person_light.png deleted file mode 100644 index 5698835..0000000 Binary files a/res/drawable-mdpi/ic_person_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_person_light_large.png b/res/drawable-mdpi/ic_person_light_large.png deleted file mode 100644 index 7aa150b..0000000 Binary files a/res/drawable-mdpi/ic_person_light_large.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_phone_small_light.png b/res/drawable-mdpi/ic_phone_small_light.png deleted file mode 100644 index dfcbba0..0000000 Binary files a/res/drawable-mdpi/ic_phone_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_photo_library_light.png b/res/drawable-mdpi/ic_photo_library_light.png deleted file mode 100644 index 322d486..0000000 Binary files a/res/drawable-mdpi/ic_photo_library_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_preview_pause.png b/res/drawable-mdpi/ic_preview_pause.png deleted file mode 100644 index 14046e2..0000000 Binary files a/res/drawable-mdpi/ic_preview_pause.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_preview_play.png b/res/drawable-mdpi/ic_preview_play.png deleted file mode 100644 index 8ad0b02..0000000 Binary files a/res/drawable-mdpi/ic_preview_play.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_remove_light.png b/res/drawable-mdpi/ic_remove_light.png deleted file mode 100644 index 0464885..0000000 Binary files a/res/drawable-mdpi/ic_remove_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_remove_small_light.png b/res/drawable-mdpi/ic_remove_small_light.png deleted file mode 100644 index 2131b6e..0000000 Binary files a/res/drawable-mdpi/ic_remove_small_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_save_dark.png b/res/drawable-mdpi/ic_save_dark.png deleted file mode 100644 index f9b0de5..0000000 Binary files a/res/drawable-mdpi/ic_save_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_save_light.png b/res/drawable-mdpi/ic_save_light.png deleted file mode 100644 index 4faf5d5..0000000 Binary files a/res/drawable-mdpi/ic_save_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_search_light.png b/res/drawable-mdpi/ic_search_light.png deleted file mode 100644 index b7e1de0..0000000 Binary files a/res/drawable-mdpi/ic_search_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_send_dark.png b/res/drawable-mdpi/ic_send_dark.png deleted file mode 100644 index 965a9f0..0000000 Binary files a/res/drawable-mdpi/ic_send_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_send_light.png b/res/drawable-mdpi/ic_send_light.png deleted file mode 100644 index 46d2dcd..0000000 Binary files a/res/drawable-mdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_share_dark.png b/res/drawable-mdpi/ic_share_dark.png deleted file mode 100644 index c6ba4c9..0000000 Binary files a/res/drawable-mdpi/ic_share_dark.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_share_light.png b/res/drawable-mdpi/ic_share_light.png deleted file mode 100644 index 362c862..0000000 Binary files a/res/drawable-mdpi/ic_share_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_sim_card_send.png b/res/drawable-mdpi/ic_sim_card_send.png deleted file mode 100644 index 1f26017..0000000 Binary files a/res/drawable-mdpi/ic_sim_card_send.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_sms_delivery_ok.png b/res/drawable-mdpi/ic_sms_delivery_ok.png deleted file mode 100644 index ffccf64..0000000 Binary files a/res/drawable-mdpi/ic_sms_delivery_ok.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_sms_failed_light.png b/res/drawable-mdpi/ic_sms_failed_light.png deleted file mode 100644 index 9914e3f..0000000 Binary files a/res/drawable-mdpi/ic_sms_failed_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_sms_light.png b/res/drawable-mdpi/ic_sms_light.png deleted file mode 100644 index 518c989..0000000 Binary files a/res/drawable-mdpi/ic_sms_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_sms_multi_light.png b/res/drawable-mdpi/ic_sms_multi_light.png deleted file mode 100644 index bd6f4c6..0000000 Binary files a/res/drawable-mdpi/ic_sms_multi_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_tooltip_arrow.png b/res/drawable-mdpi/ic_tooltip_arrow.png deleted file mode 100644 index 51824f5..0000000 Binary files a/res/drawable-mdpi/ic_tooltip_arrow.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_video_play_light.png b/res/drawable-mdpi/ic_video_play_light.png deleted file mode 100644 index e0ac1e4..0000000 Binary files a/res/drawable-mdpi/ic_video_play_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_wear_reply.png b/res/drawable-mdpi/ic_wear_reply.png deleted file mode 100644 index 47bb396..0000000 Binary files a/res/drawable-mdpi/ic_wear_reply.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_widget_avatar_shadow.png b/res/drawable-mdpi/ic_widget_avatar_shadow.png deleted file mode 100644 index 1a8dc8e..0000000 Binary files a/res/drawable-mdpi/ic_widget_avatar_shadow.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_widget_list.png b/res/drawable-mdpi/ic_widget_list.png deleted file mode 100644 index 9bf4b11..0000000 Binary files a/res/drawable-mdpi/ic_widget_list.png and /dev/null differ diff --git a/res/drawable-mdpi/msg_bubble_error.9.png b/res/drawable-mdpi/msg_bubble_error.9.png deleted file mode 100644 index 694d117..0000000 Binary files a/res/drawable-mdpi/msg_bubble_error.9.png and /dev/null differ diff --git a/res/drawable-mdpi/msg_bubble_incoming.9.png b/res/drawable-mdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 4e7e464..0000000 Binary files a/res/drawable-mdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-mdpi/msg_bubble_input.9.png b/res/drawable-mdpi/msg_bubble_input.9.png deleted file mode 100644 index 1810ffb..0000000 Binary files a/res/drawable-mdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-mdpi/msg_bubble_outgoing.9.png b/res/drawable-mdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 5ca9b14..0000000 Binary files a/res/drawable-mdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-mdpi/permissions.png b/res/drawable-mdpi/permissions.png deleted file mode 100644 index 2d754ec..0000000 Binary files a/res/drawable-mdpi/permissions.png and /dev/null differ diff --git a/res/drawable-mdpi/swipe_shadow.9.png b/res/drawable-mdpi/swipe_shadow.9.png deleted file mode 100644 index e2ed6d4..0000000 Binary files a/res/drawable-mdpi/swipe_shadow.9.png and /dev/null differ diff --git a/res/drawable-mdpi/swipe_shadow_drag.9.png b/res/drawable-mdpi/swipe_shadow_drag.9.png deleted file mode 100644 index 94be3f9..0000000 Binary files a/res/drawable-mdpi/swipe_shadow_drag.9.png and /dev/null differ diff --git a/res/drawable-mdpi/sym_keyboard_delete_holo.png b/res/drawable-mdpi/sym_keyboard_delete_holo.png deleted file mode 100644 index 7b3103b..0000000 Binary files a/res/drawable-mdpi/sym_keyboard_delete_holo.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_btn_bg_normal.9.png b/res/drawable-mdpi/tab_btn_bg_normal.9.png deleted file mode 100644 index d56cd94..0000000 Binary files a/res/drawable-mdpi/tab_btn_bg_normal.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_btn_bg_pressed.9.png b/res/drawable-mdpi/tab_btn_bg_pressed.9.png deleted file mode 100644 index 4d4c932..0000000 Binary files a/res/drawable-mdpi/tab_btn_bg_pressed.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_selected.9.png b/res/drawable-mdpi/tab_selected.9.png deleted file mode 100644 index 4b00f35..0000000 Binary files a/res/drawable-mdpi/tab_selected.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_selected_focused_holo.9.png b/res/drawable-mdpi/tab_selected_focused_holo.9.png deleted file mode 100644 index c77808d..0000000 Binary files a/res/drawable-mdpi/tab_selected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png deleted file mode 100644 index 1e28bb6..0000000 Binary files a/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_selected_pressed_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_holo.9.png deleted file mode 100644 index 098bf7d..0000000 Binary files a/res/drawable-mdpi/tab_selected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_unselected.9.png b/res/drawable-mdpi/tab_unselected.9.png deleted file mode 100644 index bb45ab9..0000000 Binary files a/res/drawable-mdpi/tab_unselected.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_unselected_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_focused_holo.9.png deleted file mode 100644 index e00e256..0000000 Binary files a/res/drawable-mdpi/tab_unselected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png deleted file mode 100644 index 80e8f0c..0000000 Binary files a/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/tab_unselected_pressed_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png deleted file mode 100644 index eb9422e..0000000 Binary files a/res/drawable-mdpi/tab_unselected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-mdpi/widget_hr.9.png b/res/drawable-mdpi/widget_hr.9.png deleted file mode 100644 index 93f3417..0000000 Binary files a/res/drawable-mdpi/widget_hr.9.png and /dev/null differ diff --git a/res/drawable-mdpi/widget_msg_bubble_incoming.9.png b/res/drawable-mdpi/widget_msg_bubble_incoming.9.png deleted file mode 100644 index ddadf73..0000000 Binary files a/res/drawable-mdpi/widget_msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png deleted file mode 100644 index 00432c3..0000000 Binary files a/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-nodpi/bg_sms.jpg b/res/drawable-nodpi/bg_sms.jpg deleted file mode 100644 index d8711e7..0000000 Binary files a/res/drawable-nodpi/bg_sms.jpg and /dev/null differ diff --git a/res/drawable-nodpi/ic_person_wear.png b/res/drawable-nodpi/ic_person_wear.png deleted file mode 100644 index 97be017..0000000 Binary files a/res/drawable-nodpi/ic_person_wear.png and /dev/null differ diff --git a/res/drawable-nodpi/messenger_widget_conversation_preview.png b/res/drawable-nodpi/messenger_widget_conversation_preview.png deleted file mode 100644 index e26ae97..0000000 Binary files a/res/drawable-nodpi/messenger_widget_conversation_preview.png and /dev/null differ diff --git a/res/drawable-nodpi/messenger_widget_preview.png b/res/drawable-nodpi/messenger_widget_preview.png deleted file mode 100644 index a5d40b2..0000000 Binary files a/res/drawable-nodpi/messenger_widget_preview.png and /dev/null differ diff --git a/res/drawable-v21/contact_picker_tab_background_selector.xml b/res/drawable-v21/contact_picker_tab_background_selector.xml deleted file mode 100644 index 59bdf95..0000000 --- a/res/drawable-v21/contact_picker_tab_background_selector.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - \ No newline at end of file diff --git a/res/drawable-v21/fab_new_message_bg.xml b/res/drawable-v21/fab_new_message_bg.xml deleted file mode 100644 index 8043f0f..0000000 --- a/res/drawable-v21/fab_new_message_bg.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable-v21/gallery_image_background_selector.xml b/res/drawable-v21/gallery_image_background_selector.xml deleted file mode 100644 index 7486fad..0000000 --- a/res/drawable-v21/gallery_image_background_selector.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/res/drawable-v21/mediapicker_tab_button_background.xml b/res/drawable-v21/mediapicker_tab_button_background.xml deleted file mode 100644 index 10c4474..0000000 --- a/res/drawable-v21/mediapicker_tab_button_background.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/res/drawable-v21/transparent_button_background.xml b/res/drawable-v21/transparent_button_background.xml deleted file mode 100644 index 10c4474..0000000 --- a/res/drawable-v21/transparent_button_background.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/res/drawable-v21/widget_item_background.xml b/res/drawable-v21/widget_item_background.xml deleted file mode 100644 index eb20c06..0000000 --- a/res/drawable-v21/widget_item_background.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png deleted file mode 100644 index 96eb10e..0000000 Binary files a/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png deleted file mode 100644 index 10986ee..0000000 Binary files a/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png deleted file mode 100644 index 12f9a82..0000000 Binary files a/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png deleted file mode 100644 index e7590b0..0000000 Binary files a/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/cab_bg.9.png b/res/drawable-xhdpi/cab_bg.9.png deleted file mode 100644 index ba8c446..0000000 Binary files a/res/drawable-xhdpi/cab_bg.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/contact_popup_background.9.png b/res/drawable-xhdpi/contact_popup_background.9.png deleted file mode 100644 index f8b809a..0000000 Binary files a/res/drawable-xhdpi/contact_popup_background.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/fab_new_message_static_shadow.png b/res/drawable-xhdpi/fab_new_message_static_shadow.png deleted file mode 100644 index 7075cb9..0000000 Binary files a/res/drawable-xhdpi/fab_new_message_static_shadow.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_add_gray.png b/res/drawable-xhdpi/ic_add_gray.png deleted file mode 100644 index 8ce295d..0000000 Binary files a/res/drawable-xhdpi/ic_add_gray.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_add_white.png b/res/drawable-xhdpi/ic_add_white.png deleted file mode 100644 index 7ef4fc1..0000000 Binary files a/res/drawable-xhdpi/ic_add_white.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_archive_small_dark.png b/res/drawable-xhdpi/ic_archive_small_dark.png deleted file mode 100644 index ea31204..0000000 Binary files a/res/drawable-xhdpi/ic_archive_small_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_archive_small_light.png b/res/drawable-xhdpi/ic_archive_small_light.png deleted file mode 100644 index b3e628f..0000000 Binary files a/res/drawable-xhdpi/ic_archive_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_archive_undo_small_dark.png b/res/drawable-xhdpi/ic_archive_undo_small_dark.png deleted file mode 100644 index 7508484..0000000 Binary files a/res/drawable-xhdpi/ic_archive_undo_small_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_archive_undo_small_light.png b/res/drawable-xhdpi/ic_archive_undo_small_light.png deleted file mode 100644 index b7038d8..0000000 Binary files a/res/drawable-xhdpi/ic_archive_undo_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_arrow_back_dark.png b/res/drawable-xhdpi/ic_arrow_back_dark.png deleted file mode 100644 index c40b832..0000000 Binary files a/res/drawable-xhdpi/ic_arrow_back_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_arrow_back_light.png b/res/drawable-xhdpi/ic_arrow_back_light.png deleted file mode 100644 index fa3d493..0000000 Binary files a/res/drawable-xhdpi/ic_arrow_back_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_attachment_dark.png b/res/drawable-xhdpi/ic_attachment_dark.png deleted file mode 100644 index 61e565c..0000000 Binary files a/res/drawable-xhdpi/ic_attachment_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_audio_light.png b/res/drawable-xhdpi/ic_audio_light.png deleted file mode 100644 index fab5ba4..0000000 Binary files a/res/drawable-xhdpi/ic_audio_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_audio_pause.png b/res/drawable-xhdpi/ic_audio_pause.png deleted file mode 100644 index e33b655..0000000 Binary files a/res/drawable-xhdpi/ic_audio_pause.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_audio_play.png b/res/drawable-xhdpi/ic_audio_play.png deleted file mode 100644 index ff93063..0000000 Binary files a/res/drawable-xhdpi/ic_audio_play.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_camera_front_light.png b/res/drawable-xhdpi/ic_camera_front_light.png deleted file mode 100644 index 9cd577d..0000000 Binary files a/res/drawable-xhdpi/ic_camera_front_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_camera_light.png b/res/drawable-xhdpi/ic_camera_light.png deleted file mode 100644 index 3b74ad6..0000000 Binary files a/res/drawable-xhdpi/ic_camera_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_camera_rear_light.png b/res/drawable-xhdpi/ic_camera_rear_light.png deleted file mode 100644 index 92727af..0000000 Binary files a/res/drawable-xhdpi/ic_camera_rear_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_cancel_small_dark.png b/res/drawable-xhdpi/ic_cancel_small_dark.png deleted file mode 100644 index 8b34b9b..0000000 Binary files a/res/drawable-xhdpi/ic_cancel_small_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_cancel_small_light.png b/res/drawable-xhdpi/ic_cancel_small_light.png deleted file mode 100644 index dea984f..0000000 Binary files a/res/drawable-xhdpi/ic_cancel_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkbox_blank_light.png b/res/drawable-xhdpi/ic_checkbox_blank_light.png deleted file mode 100644 index 7f6e917..0000000 Binary files a/res/drawable-xhdpi/ic_checkbox_blank_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkbox_light.png b/res/drawable-xhdpi/ic_checkbox_light.png deleted file mode 100644 index 9cb2b7e..0000000 Binary files a/res/drawable-xhdpi/ic_checkbox_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkbox_outline_light.png b/res/drawable-xhdpi/ic_checkbox_outline_light.png deleted file mode 100644 index 64f0a2b..0000000 Binary files a/res/drawable-xhdpi/ic_checkbox_outline_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkmark_circle_blue.png b/res/drawable-xhdpi/ic_checkmark_circle_blue.png deleted file mode 100644 index 211a405..0000000 Binary files a/res/drawable-xhdpi/ic_checkmark_circle_blue.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkmark_large.png b/res/drawable-xhdpi/ic_checkmark_large.png deleted file mode 100644 index fd4420b..0000000 Binary files a/res/drawable-xhdpi/ic_checkmark_large.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkmark_large_light.png b/res/drawable-xhdpi/ic_checkmark_large_light.png deleted file mode 100644 index d841688..0000000 Binary files a/res/drawable-xhdpi/ic_checkmark_large_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkmark_small_blue.png b/res/drawable-xhdpi/ic_checkmark_small_blue.png deleted file mode 100644 index 9fc4a4b..0000000 Binary files a/res/drawable-xhdpi/ic_checkmark_small_blue.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_checkmark_small_light.png b/res/drawable-xhdpi/ic_checkmark_small_light.png deleted file mode 100644 index cd084fb..0000000 Binary files a/res/drawable-xhdpi/ic_checkmark_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_color_lens.png b/res/drawable-xhdpi/ic_color_lens.png deleted file mode 100644 index 8887429..0000000 Binary files a/res/drawable-xhdpi/ic_color_lens.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_content_copy_dark.png b/res/drawable-xhdpi/ic_content_copy_dark.png deleted file mode 100644 index 0114555..0000000 Binary files a/res/drawable-xhdpi/ic_content_copy_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_crying_hero.png b/res/drawable-xhdpi/ic_crying_hero.png deleted file mode 100644 index ccc40ab..0000000 Binary files a/res/drawable-xhdpi/ic_crying_hero.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_delete_small_dark.png b/res/drawable-xhdpi/ic_delete_small_dark.png deleted file mode 100644 index b9b0cbd..0000000 Binary files a/res/drawable-xhdpi/ic_delete_small_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_delete_small_light.png b/res/drawable-xhdpi/ic_delete_small_light.png deleted file mode 100644 index 7c45f70..0000000 Binary files a/res/drawable-xhdpi/ic_delete_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_dnd_on_dark.png b/res/drawable-xhdpi/ic_dnd_on_dark.png deleted file mode 100644 index c40fe99..0000000 Binary files a/res/drawable-xhdpi/ic_dnd_on_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_dnd_on_light.png b/res/drawable-xhdpi/ic_dnd_on_light.png deleted file mode 100644 index e0527f5..0000000 Binary files a/res/drawable-xhdpi/ic_dnd_on_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_failed_light.png b/res/drawable-xhdpi/ic_failed_light.png deleted file mode 100644 index 33d3436..0000000 Binary files a/res/drawable-xhdpi/ic_failed_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_failed_status_red.png b/res/drawable-xhdpi/ic_failed_status_red.png deleted file mode 100644 index 3c6616a..0000000 Binary files a/res/drawable-xhdpi/ic_failed_status_red.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_file_download_dark.png b/res/drawable-xhdpi/ic_file_download_dark.png deleted file mode 100644 index c31257a..0000000 Binary files a/res/drawable-xhdpi/ic_file_download_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_file_download_light.png b/res/drawable-xhdpi/ic_file_download_light.png deleted file mode 100644 index b894fa6..0000000 Binary files a/res/drawable-xhdpi/ic_file_download_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_forward_dark.png b/res/drawable-xhdpi/ic_forward_dark.png deleted file mode 100644 index b62f0de..0000000 Binary files a/res/drawable-xhdpi/ic_forward_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_history_light.png b/res/drawable-xhdpi/ic_history_light.png deleted file mode 100644 index 23585a2..0000000 Binary files a/res/drawable-xhdpi/ic_history_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_image_light.png b/res/drawable-xhdpi/ic_image_light.png deleted file mode 100644 index b2aecfb..0000000 Binary files a/res/drawable-xhdpi/ic_image_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_ime_dark.png b/res/drawable-xhdpi/ic_ime_dark.png deleted file mode 100644 index cd5c5c6..0000000 Binary files a/res/drawable-xhdpi/ic_ime_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_ime_light.png b/res/drawable-xhdpi/ic_ime_light.png deleted file mode 100644 index 14b57f8..0000000 Binary files a/res/drawable-xhdpi/ic_ime_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_info_dark.png b/res/drawable-xhdpi/ic_info_dark.png deleted file mode 100644 index 31d1e05..0000000 Binary files a/res/drawable-xhdpi/ic_info_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 464a0bc..0000000 Binary files a/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 51a1903..0000000 Binary files a/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 3c82a06..0000000 Binary files a/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_audio_mic.png b/res/drawable-xhdpi/ic_mp_audio_mic.png deleted file mode 100644 index b35d0f1..0000000 Binary files a/res/drawable-xhdpi/ic_mp_audio_mic.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_camera_large_light.png b/res/drawable-xhdpi/ic_mp_camera_large_light.png deleted file mode 100644 index 5552d92..0000000 Binary files a/res/drawable-xhdpi/ic_mp_camera_large_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_camera_small_light.png b/res/drawable-xhdpi/ic_mp_camera_small_light.png deleted file mode 100644 index 75094e1..0000000 Binary files a/res/drawable-xhdpi/ic_mp_camera_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png deleted file mode 100644 index 62125b3..0000000 Binary files a/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_full_screen_light.png b/res/drawable-xhdpi/ic_mp_full_screen_light.png deleted file mode 100644 index 02cbd48..0000000 Binary files a/res/drawable-xhdpi/ic_mp_full_screen_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_video_large_light.png b/res/drawable-xhdpi/ic_mp_video_large_light.png deleted file mode 100644 index 723b052..0000000 Binary files a/res/drawable-xhdpi/ic_mp_video_large_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mp_video_small_light.png b/res/drawable-xhdpi/ic_mp_video_small_light.png deleted file mode 100644 index 00fde5f..0000000 Binary files a/res/drawable-xhdpi/ic_mp_video_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_notifications_off_dark.png b/res/drawable-xhdpi/ic_notifications_off_dark.png deleted file mode 100644 index 4f3b924..0000000 Binary files a/res/drawable-xhdpi/ic_notifications_off_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_notifications_off_light.png b/res/drawable-xhdpi/ic_notifications_off_light.png deleted file mode 100644 index 16ae132..0000000 Binary files a/res/drawable-xhdpi/ic_notifications_off_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_notifications_off_small_light.png b/res/drawable-xhdpi/ic_notifications_off_small_light.png deleted file mode 100644 index e54fc52..0000000 Binary files a/res/drawable-xhdpi/ic_notifications_off_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_notifications_on_dark.png b/res/drawable-xhdpi/ic_notifications_on_dark.png deleted file mode 100644 index 40f2909..0000000 Binary files a/res/drawable-xhdpi/ic_notifications_on_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_notifications_on_light.png b/res/drawable-xhdpi/ic_notifications_on_light.png deleted file mode 100644 index 3c44f93..0000000 Binary files a/res/drawable-xhdpi/ic_notifications_on_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_numeric_dialpad.png b/res/drawable-xhdpi/ic_numeric_dialpad.png deleted file mode 100644 index b22360f..0000000 Binary files a/res/drawable-xhdpi/ic_numeric_dialpad.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_oobe_conv_list.png b/res/drawable-xhdpi/ic_oobe_conv_list.png deleted file mode 100644 index b41d322..0000000 Binary files a/res/drawable-xhdpi/ic_oobe_conv_list.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_oobe_freq_list.png b/res/drawable-xhdpi/ic_oobe_freq_list.png deleted file mode 100644 index 898daf5..0000000 Binary files a/res/drawable-xhdpi/ic_oobe_freq_list.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_open_in_new.png b/res/drawable-xhdpi/ic_open_in_new.png deleted file mode 100644 index 363a35d..0000000 Binary files a/res/drawable-xhdpi/ic_open_in_new.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_people_add_light.png b/res/drawable-xhdpi/ic_people_add_light.png deleted file mode 100644 index f57f056..0000000 Binary files a/res/drawable-xhdpi/ic_people_add_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_person_add_blue.png b/res/drawable-xhdpi/ic_person_add_blue.png deleted file mode 100644 index 4e76e64..0000000 Binary files a/res/drawable-xhdpi/ic_person_add_blue.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_person_add_dark.png b/res/drawable-xhdpi/ic_person_add_dark.png deleted file mode 100644 index 6f325cf..0000000 Binary files a/res/drawable-xhdpi/ic_person_add_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_person_add_light.png b/res/drawable-xhdpi/ic_person_add_light.png deleted file mode 100644 index 840f063..0000000 Binary files a/res/drawable-xhdpi/ic_person_add_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_person_light.png b/res/drawable-xhdpi/ic_person_light.png deleted file mode 100644 index 5d23639..0000000 Binary files a/res/drawable-xhdpi/ic_person_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_person_light_large.png b/res/drawable-xhdpi/ic_person_light_large.png deleted file mode 100644 index 22a4ff1..0000000 Binary files a/res/drawable-xhdpi/ic_person_light_large.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_phone_small_light.png b/res/drawable-xhdpi/ic_phone_small_light.png deleted file mode 100644 index b2cab28..0000000 Binary files a/res/drawable-xhdpi/ic_phone_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_photo_library_light.png b/res/drawable-xhdpi/ic_photo_library_light.png deleted file mode 100644 index 1f5a69c..0000000 Binary files a/res/drawable-xhdpi/ic_photo_library_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_preview_pause.png b/res/drawable-xhdpi/ic_preview_pause.png deleted file mode 100644 index bc0319d..0000000 Binary files a/res/drawable-xhdpi/ic_preview_pause.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_preview_play.png b/res/drawable-xhdpi/ic_preview_play.png deleted file mode 100644 index dc3ac1e..0000000 Binary files a/res/drawable-xhdpi/ic_preview_play.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_remove_light.png b/res/drawable-xhdpi/ic_remove_light.png deleted file mode 100644 index cbaf6d0..0000000 Binary files a/res/drawable-xhdpi/ic_remove_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_remove_small_light.png b/res/drawable-xhdpi/ic_remove_small_light.png deleted file mode 100644 index 222b86c..0000000 Binary files a/res/drawable-xhdpi/ic_remove_small_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_save_dark.png b/res/drawable-xhdpi/ic_save_dark.png deleted file mode 100644 index 7927ba6..0000000 Binary files a/res/drawable-xhdpi/ic_save_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_save_light.png b/res/drawable-xhdpi/ic_save_light.png deleted file mode 100644 index 298bb47..0000000 Binary files a/res/drawable-xhdpi/ic_save_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_search_light.png b/res/drawable-xhdpi/ic_search_light.png deleted file mode 100644 index b83d895..0000000 Binary files a/res/drawable-xhdpi/ic_search_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_send_dark.png b/res/drawable-xhdpi/ic_send_dark.png deleted file mode 100644 index a6b3699..0000000 Binary files a/res/drawable-xhdpi/ic_send_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_send_light.png b/res/drawable-xhdpi/ic_send_light.png deleted file mode 100644 index a19b335..0000000 Binary files a/res/drawable-xhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_share_dark.png b/res/drawable-xhdpi/ic_share_dark.png deleted file mode 100644 index a4b822a..0000000 Binary files a/res/drawable-xhdpi/ic_share_dark.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_share_light.png b/res/drawable-xhdpi/ic_share_light.png deleted file mode 100644 index 711797c..0000000 Binary files a/res/drawable-xhdpi/ic_share_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_sim_card_send.png b/res/drawable-xhdpi/ic_sim_card_send.png deleted file mode 100644 index 9fc7e9a..0000000 Binary files a/res/drawable-xhdpi/ic_sim_card_send.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_sms_delivery_ok.png b/res/drawable-xhdpi/ic_sms_delivery_ok.png deleted file mode 100644 index c12ae0a..0000000 Binary files a/res/drawable-xhdpi/ic_sms_delivery_ok.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_sms_failed_light.png b/res/drawable-xhdpi/ic_sms_failed_light.png deleted file mode 100644 index 90c899a..0000000 Binary files a/res/drawable-xhdpi/ic_sms_failed_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_sms_light.png b/res/drawable-xhdpi/ic_sms_light.png deleted file mode 100644 index 4b24a4b..0000000 Binary files a/res/drawable-xhdpi/ic_sms_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_sms_multi_light.png b/res/drawable-xhdpi/ic_sms_multi_light.png deleted file mode 100644 index f3ba620..0000000 Binary files a/res/drawable-xhdpi/ic_sms_multi_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_tooltip_arrow.png b/res/drawable-xhdpi/ic_tooltip_arrow.png deleted file mode 100644 index 4a59931..0000000 Binary files a/res/drawable-xhdpi/ic_tooltip_arrow.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_video_play_light.png b/res/drawable-xhdpi/ic_video_play_light.png deleted file mode 100644 index 89397a8..0000000 Binary files a/res/drawable-xhdpi/ic_video_play_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_wear_reply.png b/res/drawable-xhdpi/ic_wear_reply.png deleted file mode 100644 index c909c59..0000000 Binary files a/res/drawable-xhdpi/ic_wear_reply.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_widget_avatar_shadow.png b/res/drawable-xhdpi/ic_widget_avatar_shadow.png deleted file mode 100644 index 37600aa..0000000 Binary files a/res/drawable-xhdpi/ic_widget_avatar_shadow.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_widget_list.png b/res/drawable-xhdpi/ic_widget_list.png deleted file mode 100644 index a12764f..0000000 Binary files a/res/drawable-xhdpi/ic_widget_list.png and /dev/null differ diff --git a/res/drawable-xhdpi/msg_bubble_error.9.png b/res/drawable-xhdpi/msg_bubble_error.9.png deleted file mode 100644 index 96b139b..0000000 Binary files a/res/drawable-xhdpi/msg_bubble_error.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/msg_bubble_incoming.9.png b/res/drawable-xhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 0661fdb..0000000 Binary files a/res/drawable-xhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/msg_bubble_input.9.png b/res/drawable-xhdpi/msg_bubble_input.9.png deleted file mode 100644 index 67f92ff..0000000 Binary files a/res/drawable-xhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-xhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 52cb936..0000000 Binary files a/res/drawable-xhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/permissions.png b/res/drawable-xhdpi/permissions.png deleted file mode 100644 index 39a81bc..0000000 Binary files a/res/drawable-xhdpi/permissions.png and /dev/null differ diff --git a/res/drawable-xhdpi/swipe_shadow.9.png b/res/drawable-xhdpi/swipe_shadow.9.png deleted file mode 100644 index 45d965d..0000000 Binary files a/res/drawable-xhdpi/swipe_shadow.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/swipe_shadow_drag.9.png b/res/drawable-xhdpi/swipe_shadow_drag.9.png deleted file mode 100644 index b443b5c..0000000 Binary files a/res/drawable-xhdpi/swipe_shadow_drag.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/sym_keyboard_delete_holo.png b/res/drawable-xhdpi/sym_keyboard_delete_holo.png deleted file mode 100644 index 16b58a0..0000000 Binary files a/res/drawable-xhdpi/sym_keyboard_delete_holo.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_btn_bg_normal.9.png b/res/drawable-xhdpi/tab_btn_bg_normal.9.png deleted file mode 100644 index d9446c6..0000000 Binary files a/res/drawable-xhdpi/tab_btn_bg_normal.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xhdpi/tab_btn_bg_pressed.9.png deleted file mode 100644 index de76c06..0000000 Binary files a/res/drawable-xhdpi/tab_btn_bg_pressed.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_selected.9.png b/res/drawable-xhdpi/tab_selected.9.png deleted file mode 100644 index 95e5f43..0000000 Binary files a/res/drawable-xhdpi/tab_selected.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_selected_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_focused_holo.9.png deleted file mode 100644 index f98b4d9..0000000 Binary files a/res/drawable-xhdpi/tab_selected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png deleted file mode 100644 index e8f2cb6..0000000 Binary files a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png deleted file mode 100644 index 1882927..0000000 Binary files a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_unselected.9.png b/res/drawable-xhdpi/tab_unselected.9.png deleted file mode 100644 index 8cede8d..0000000 Binary files a/res/drawable-xhdpi/tab_unselected.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png deleted file mode 100644 index 28a7ec5..0000000 Binary files a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png deleted file mode 100644 index b9962b1..0000000 Binary files a/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png deleted file mode 100644 index dadc471..0000000 Binary files a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/widget_hr.9.png b/res/drawable-xhdpi/widget_hr.9.png deleted file mode 100644 index 31fcc7e..0000000 Binary files a/res/drawable-xhdpi/widget_hr.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png deleted file mode 100644 index 2abb496..0000000 Binary files a/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png deleted file mode 100644 index 35b58cd..0000000 Binary files a/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png deleted file mode 100644 index cc880c2..0000000 Binary files a/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png deleted file mode 100644 index 60ccf97..0000000 Binary files a/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png deleted file mode 100644 index 0358907..0000000 Binary files a/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png deleted file mode 100644 index adcddc9..0000000 Binary files a/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/cab_bg.9.png b/res/drawable-xxhdpi/cab_bg.9.png deleted file mode 100644 index b75401e..0000000 Binary files a/res/drawable-xxhdpi/cab_bg.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/contact_popup_background.9.png b/res/drawable-xxhdpi/contact_popup_background.9.png deleted file mode 100644 index bb4f281..0000000 Binary files a/res/drawable-xxhdpi/contact_popup_background.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/fab_new_message_static_shadow.png b/res/drawable-xxhdpi/fab_new_message_static_shadow.png deleted file mode 100644 index adfd569..0000000 Binary files a/res/drawable-xxhdpi/fab_new_message_static_shadow.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_add_gray.png b/res/drawable-xxhdpi/ic_add_gray.png deleted file mode 100644 index 8273830..0000000 Binary files a/res/drawable-xxhdpi/ic_add_gray.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_add_white.png b/res/drawable-xxhdpi/ic_add_white.png deleted file mode 100644 index 7b4d52c..0000000 Binary files a/res/drawable-xxhdpi/ic_add_white.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_archive_small_dark.png b/res/drawable-xxhdpi/ic_archive_small_dark.png deleted file mode 100644 index 64aa9d5..0000000 Binary files a/res/drawable-xxhdpi/ic_archive_small_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_archive_small_light.png b/res/drawable-xxhdpi/ic_archive_small_light.png deleted file mode 100644 index 042ed27..0000000 Binary files a/res/drawable-xxhdpi/ic_archive_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_archive_undo_small_dark.png b/res/drawable-xxhdpi/ic_archive_undo_small_dark.png deleted file mode 100644 index a3f7503..0000000 Binary files a/res/drawable-xxhdpi/ic_archive_undo_small_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_archive_undo_small_light.png b/res/drawable-xxhdpi/ic_archive_undo_small_light.png deleted file mode 100644 index cd9d0d5..0000000 Binary files a/res/drawable-xxhdpi/ic_archive_undo_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_arrow_back_dark.png b/res/drawable-xxhdpi/ic_arrow_back_dark.png deleted file mode 100644 index 1c71cb0..0000000 Binary files a/res/drawable-xxhdpi/ic_arrow_back_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_arrow_back_light.png b/res/drawable-xxhdpi/ic_arrow_back_light.png deleted file mode 100644 index 8b532d0..0000000 Binary files a/res/drawable-xxhdpi/ic_arrow_back_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_attachment_dark.png b/res/drawable-xxhdpi/ic_attachment_dark.png deleted file mode 100644 index 64d9dad..0000000 Binary files a/res/drawable-xxhdpi/ic_attachment_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_audio_light.png b/res/drawable-xxhdpi/ic_audio_light.png deleted file mode 100644 index 9b2f05c..0000000 Binary files a/res/drawable-xxhdpi/ic_audio_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_audio_pause.png b/res/drawable-xxhdpi/ic_audio_pause.png deleted file mode 100644 index d294f3c..0000000 Binary files a/res/drawable-xxhdpi/ic_audio_pause.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_audio_play.png b/res/drawable-xxhdpi/ic_audio_play.png deleted file mode 100644 index cb84ee6..0000000 Binary files a/res/drawable-xxhdpi/ic_audio_play.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_camera_front_light.png b/res/drawable-xxhdpi/ic_camera_front_light.png deleted file mode 100644 index 53616e4..0000000 Binary files a/res/drawable-xxhdpi/ic_camera_front_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_camera_light.png b/res/drawable-xxhdpi/ic_camera_light.png deleted file mode 100644 index 64e9873..0000000 Binary files a/res/drawable-xxhdpi/ic_camera_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_camera_rear_light.png b/res/drawable-xxhdpi/ic_camera_rear_light.png deleted file mode 100644 index 8e478e3..0000000 Binary files a/res/drawable-xxhdpi/ic_camera_rear_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_cancel_small_dark.png b/res/drawable-xxhdpi/ic_cancel_small_dark.png deleted file mode 100644 index c8f583c..0000000 Binary files a/res/drawable-xxhdpi/ic_cancel_small_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_cancel_small_light.png b/res/drawable-xxhdpi/ic_cancel_small_light.png deleted file mode 100644 index b3eacda..0000000 Binary files a/res/drawable-xxhdpi/ic_cancel_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkbox_blank_light.png b/res/drawable-xxhdpi/ic_checkbox_blank_light.png deleted file mode 100644 index 0556578..0000000 Binary files a/res/drawable-xxhdpi/ic_checkbox_blank_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkbox_light.png b/res/drawable-xxhdpi/ic_checkbox_light.png deleted file mode 100644 index 3a1896b..0000000 Binary files a/res/drawable-xxhdpi/ic_checkbox_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkbox_outline_light.png b/res/drawable-xxhdpi/ic_checkbox_outline_light.png deleted file mode 100644 index cc77c5e..0000000 Binary files a/res/drawable-xxhdpi/ic_checkbox_outline_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkmark_circle_blue.png b/res/drawable-xxhdpi/ic_checkmark_circle_blue.png deleted file mode 100644 index bede5b9..0000000 Binary files a/res/drawable-xxhdpi/ic_checkmark_circle_blue.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkmark_large.png b/res/drawable-xxhdpi/ic_checkmark_large.png deleted file mode 100644 index 1be0e02..0000000 Binary files a/res/drawable-xxhdpi/ic_checkmark_large.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkmark_large_light.png b/res/drawable-xxhdpi/ic_checkmark_large_light.png deleted file mode 100644 index b5f4e24..0000000 Binary files a/res/drawable-xxhdpi/ic_checkmark_large_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkmark_small_blue.png b/res/drawable-xxhdpi/ic_checkmark_small_blue.png deleted file mode 100644 index 65d3e5c..0000000 Binary files a/res/drawable-xxhdpi/ic_checkmark_small_blue.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_checkmark_small_light.png b/res/drawable-xxhdpi/ic_checkmark_small_light.png deleted file mode 100644 index 7aeac03..0000000 Binary files a/res/drawable-xxhdpi/ic_checkmark_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_color_lens.png b/res/drawable-xxhdpi/ic_color_lens.png deleted file mode 100644 index 6fd56fd..0000000 Binary files a/res/drawable-xxhdpi/ic_color_lens.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_content_copy_dark.png b/res/drawable-xxhdpi/ic_content_copy_dark.png deleted file mode 100644 index e3518ae..0000000 Binary files a/res/drawable-xxhdpi/ic_content_copy_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_crying_hero.png b/res/drawable-xxhdpi/ic_crying_hero.png deleted file mode 100644 index 46538b5..0000000 Binary files a/res/drawable-xxhdpi/ic_crying_hero.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_delete_small_dark.png b/res/drawable-xxhdpi/ic_delete_small_dark.png deleted file mode 100644 index 146ac39..0000000 Binary files a/res/drawable-xxhdpi/ic_delete_small_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_delete_small_light.png b/res/drawable-xxhdpi/ic_delete_small_light.png deleted file mode 100644 index b739ac9..0000000 Binary files a/res/drawable-xxhdpi/ic_delete_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_dnd_on_dark.png b/res/drawable-xxhdpi/ic_dnd_on_dark.png deleted file mode 100644 index a9ccb88..0000000 Binary files a/res/drawable-xxhdpi/ic_dnd_on_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_dnd_on_light.png b/res/drawable-xxhdpi/ic_dnd_on_light.png deleted file mode 100644 index 3ddaa13..0000000 Binary files a/res/drawable-xxhdpi/ic_dnd_on_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_failed_light.png b/res/drawable-xxhdpi/ic_failed_light.png deleted file mode 100644 index 7612138..0000000 Binary files a/res/drawable-xxhdpi/ic_failed_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_failed_status_red.png b/res/drawable-xxhdpi/ic_failed_status_red.png deleted file mode 100644 index 26f76cc..0000000 Binary files a/res/drawable-xxhdpi/ic_failed_status_red.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_file_download_dark.png b/res/drawable-xxhdpi/ic_file_download_dark.png deleted file mode 100644 index d7fc0b3..0000000 Binary files a/res/drawable-xxhdpi/ic_file_download_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_file_download_light.png b/res/drawable-xxhdpi/ic_file_download_light.png deleted file mode 100644 index 42eadbc..0000000 Binary files a/res/drawable-xxhdpi/ic_file_download_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_forward_dark.png b/res/drawable-xxhdpi/ic_forward_dark.png deleted file mode 100644 index cd071be..0000000 Binary files a/res/drawable-xxhdpi/ic_forward_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_history_light.png b/res/drawable-xxhdpi/ic_history_light.png deleted file mode 100644 index bd430f8..0000000 Binary files a/res/drawable-xxhdpi/ic_history_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_image_light.png b/res/drawable-xxhdpi/ic_image_light.png deleted file mode 100644 index 16b7e7b..0000000 Binary files a/res/drawable-xxhdpi/ic_image_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_ime_dark.png b/res/drawable-xxhdpi/ic_ime_dark.png deleted file mode 100644 index a32bd46..0000000 Binary files a/res/drawable-xxhdpi/ic_ime_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_ime_light.png b/res/drawable-xxhdpi/ic_ime_light.png deleted file mode 100644 index 159d085..0000000 Binary files a/res/drawable-xxhdpi/ic_ime_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_info_dark.png b/res/drawable-xxhdpi/ic_info_dark.png deleted file mode 100644 index 2df42de..0000000 Binary files a/res/drawable-xxhdpi/ic_info_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 09536ed..0000000 Binary files a/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 62ca382..0000000 Binary files a/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png deleted file mode 100644 index 0d61198..0000000 Binary files a/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_audio_mic.png b/res/drawable-xxhdpi/ic_mp_audio_mic.png deleted file mode 100644 index a01388b..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_audio_mic.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_camera_large_light.png b/res/drawable-xxhdpi/ic_mp_camera_large_light.png deleted file mode 100644 index 37fa935..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_camera_large_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_camera_small_light.png b/res/drawable-xxhdpi/ic_mp_camera_small_light.png deleted file mode 100644 index 8012fce..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_camera_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png deleted file mode 100644 index d34667a..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_full_screen_light.png b/res/drawable-xxhdpi/ic_mp_full_screen_light.png deleted file mode 100644 index 483a1a3..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_full_screen_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_video_large_light.png b/res/drawable-xxhdpi/ic_mp_video_large_light.png deleted file mode 100644 index 0a34b5e..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_video_large_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mp_video_small_light.png b/res/drawable-xxhdpi/ic_mp_video_small_light.png deleted file mode 100644 index 1cb7798..0000000 Binary files a/res/drawable-xxhdpi/ic_mp_video_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_notifications_off_dark.png b/res/drawable-xxhdpi/ic_notifications_off_dark.png deleted file mode 100644 index 27d3754..0000000 Binary files a/res/drawable-xxhdpi/ic_notifications_off_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_notifications_off_light.png b/res/drawable-xxhdpi/ic_notifications_off_light.png deleted file mode 100644 index a2aedec..0000000 Binary files a/res/drawable-xxhdpi/ic_notifications_off_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_notifications_off_small_light.png b/res/drawable-xxhdpi/ic_notifications_off_small_light.png deleted file mode 100644 index 2a90e07..0000000 Binary files a/res/drawable-xxhdpi/ic_notifications_off_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_notifications_on_dark.png b/res/drawable-xxhdpi/ic_notifications_on_dark.png deleted file mode 100644 index e64da6b..0000000 Binary files a/res/drawable-xxhdpi/ic_notifications_on_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_notifications_on_light.png b/res/drawable-xxhdpi/ic_notifications_on_light.png deleted file mode 100644 index f8f7d15..0000000 Binary files a/res/drawable-xxhdpi/ic_notifications_on_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_numeric_dialpad.png b/res/drawable-xxhdpi/ic_numeric_dialpad.png deleted file mode 100644 index a0d9e5e..0000000 Binary files a/res/drawable-xxhdpi/ic_numeric_dialpad.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_oobe_conv_list.png b/res/drawable-xxhdpi/ic_oobe_conv_list.png deleted file mode 100644 index f6fcde1..0000000 Binary files a/res/drawable-xxhdpi/ic_oobe_conv_list.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_oobe_freq_list.png b/res/drawable-xxhdpi/ic_oobe_freq_list.png deleted file mode 100644 index 4d7543d..0000000 Binary files a/res/drawable-xxhdpi/ic_oobe_freq_list.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_open_in_new.png b/res/drawable-xxhdpi/ic_open_in_new.png deleted file mode 100644 index 9109fa4..0000000 Binary files a/res/drawable-xxhdpi/ic_open_in_new.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_people_add_light.png b/res/drawable-xxhdpi/ic_people_add_light.png deleted file mode 100644 index 915b91d..0000000 Binary files a/res/drawable-xxhdpi/ic_people_add_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_person_add_blue.png b/res/drawable-xxhdpi/ic_person_add_blue.png deleted file mode 100644 index 10aa345..0000000 Binary files a/res/drawable-xxhdpi/ic_person_add_blue.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_person_add_dark.png b/res/drawable-xxhdpi/ic_person_add_dark.png deleted file mode 100644 index 1d9bb20..0000000 Binary files a/res/drawable-xxhdpi/ic_person_add_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_person_add_light.png b/res/drawable-xxhdpi/ic_person_add_light.png deleted file mode 100644 index 5a17f08..0000000 Binary files a/res/drawable-xxhdpi/ic_person_add_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_person_light.png b/res/drawable-xxhdpi/ic_person_light.png deleted file mode 100644 index adab957..0000000 Binary files a/res/drawable-xxhdpi/ic_person_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_person_light_large.png b/res/drawable-xxhdpi/ic_person_light_large.png deleted file mode 100644 index 3cf214f..0000000 Binary files a/res/drawable-xxhdpi/ic_person_light_large.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_phone_small_light.png b/res/drawable-xxhdpi/ic_phone_small_light.png deleted file mode 100644 index 8d7c25f..0000000 Binary files a/res/drawable-xxhdpi/ic_phone_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_photo_library_light.png b/res/drawable-xxhdpi/ic_photo_library_light.png deleted file mode 100644 index f61bb58..0000000 Binary files a/res/drawable-xxhdpi/ic_photo_library_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_preview_pause.png b/res/drawable-xxhdpi/ic_preview_pause.png deleted file mode 100644 index 4785e1b..0000000 Binary files a/res/drawable-xxhdpi/ic_preview_pause.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_preview_play.png b/res/drawable-xxhdpi/ic_preview_play.png deleted file mode 100644 index 3120cd1..0000000 Binary files a/res/drawable-xxhdpi/ic_preview_play.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_remove_light.png b/res/drawable-xxhdpi/ic_remove_light.png deleted file mode 100644 index 25bcc74..0000000 Binary files a/res/drawable-xxhdpi/ic_remove_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_remove_small_light.png b/res/drawable-xxhdpi/ic_remove_small_light.png deleted file mode 100644 index c69e993..0000000 Binary files a/res/drawable-xxhdpi/ic_remove_small_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_save_dark.png b/res/drawable-xxhdpi/ic_save_dark.png deleted file mode 100644 index 8267484..0000000 Binary files a/res/drawable-xxhdpi/ic_save_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_save_light.png b/res/drawable-xxhdpi/ic_save_light.png deleted file mode 100644 index 4b944cf..0000000 Binary files a/res/drawable-xxhdpi/ic_save_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_search_light.png b/res/drawable-xxhdpi/ic_search_light.png deleted file mode 100644 index 97d77d6..0000000 Binary files a/res/drawable-xxhdpi/ic_search_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_send_dark.png b/res/drawable-xxhdpi/ic_send_dark.png deleted file mode 100644 index 44729b1..0000000 Binary files a/res/drawable-xxhdpi/ic_send_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_send_light.png b/res/drawable-xxhdpi/ic_send_light.png deleted file mode 100644 index a1a6cab..0000000 Binary files a/res/drawable-xxhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_share_dark.png b/res/drawable-xxhdpi/ic_share_dark.png deleted file mode 100644 index 790670a..0000000 Binary files a/res/drawable-xxhdpi/ic_share_dark.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_share_light.png b/res/drawable-xxhdpi/ic_share_light.png deleted file mode 100644 index 880c6a2..0000000 Binary files a/res/drawable-xxhdpi/ic_share_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_sim_card_send.png b/res/drawable-xxhdpi/ic_sim_card_send.png deleted file mode 100644 index 8eae7c3..0000000 Binary files a/res/drawable-xxhdpi/ic_sim_card_send.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_sms_delivery_ok.png b/res/drawable-xxhdpi/ic_sms_delivery_ok.png deleted file mode 100644 index 2c8c9b0..0000000 Binary files a/res/drawable-xxhdpi/ic_sms_delivery_ok.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_sms_failed_light.png b/res/drawable-xxhdpi/ic_sms_failed_light.png deleted file mode 100644 index 5da454b..0000000 Binary files a/res/drawable-xxhdpi/ic_sms_failed_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_sms_light.png b/res/drawable-xxhdpi/ic_sms_light.png deleted file mode 100644 index 7a8b491..0000000 Binary files a/res/drawable-xxhdpi/ic_sms_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_sms_multi_light.png b/res/drawable-xxhdpi/ic_sms_multi_light.png deleted file mode 100644 index 6496cb8..0000000 Binary files a/res/drawable-xxhdpi/ic_sms_multi_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_tooltip_arrow.png b/res/drawable-xxhdpi/ic_tooltip_arrow.png deleted file mode 100644 index 72f1c5b..0000000 Binary files a/res/drawable-xxhdpi/ic_tooltip_arrow.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_video_play_light.png b/res/drawable-xxhdpi/ic_video_play_light.png deleted file mode 100644 index f49c653..0000000 Binary files a/res/drawable-xxhdpi/ic_video_play_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_wear_reply.png b/res/drawable-xxhdpi/ic_wear_reply.png deleted file mode 100644 index 0790ee8..0000000 Binary files a/res/drawable-xxhdpi/ic_wear_reply.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_widget_avatar_shadow.png b/res/drawable-xxhdpi/ic_widget_avatar_shadow.png deleted file mode 100644 index ea4e26d..0000000 Binary files a/res/drawable-xxhdpi/ic_widget_avatar_shadow.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_widget_list.png b/res/drawable-xxhdpi/ic_widget_list.png deleted file mode 100644 index c370ee8..0000000 Binary files a/res/drawable-xxhdpi/ic_widget_list.png and /dev/null differ diff --git a/res/drawable-xxhdpi/msg_bubble_error.9.png b/res/drawable-xxhdpi/msg_bubble_error.9.png deleted file mode 100644 index f8bdc96..0000000 Binary files a/res/drawable-xxhdpi/msg_bubble_error.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index ab693b3..0000000 Binary files a/res/drawable-xxhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/msg_bubble_input.9.png b/res/drawable-xxhdpi/msg_bubble_input.9.png deleted file mode 100644 index de41ef4..0000000 Binary files a/res/drawable-xxhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index 5b2a2ff..0000000 Binary files a/res/drawable-xxhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/permissions.png b/res/drawable-xxhdpi/permissions.png deleted file mode 100644 index fd61476..0000000 Binary files a/res/drawable-xxhdpi/permissions.png and /dev/null differ diff --git a/res/drawable-xxhdpi/swipe_shadow.9.png b/res/drawable-xxhdpi/swipe_shadow.9.png deleted file mode 100644 index 0491faa..0000000 Binary files a/res/drawable-xxhdpi/swipe_shadow.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/swipe_shadow_drag.9.png b/res/drawable-xxhdpi/swipe_shadow_drag.9.png deleted file mode 100644 index 0153c5b..0000000 Binary files a/res/drawable-xxhdpi/swipe_shadow_drag.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/sym_keyboard_delete_holo.png b/res/drawable-xxhdpi/sym_keyboard_delete_holo.png deleted file mode 100644 index f2adbec..0000000 Binary files a/res/drawable-xxhdpi/sym_keyboard_delete_holo.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_btn_bg_normal.9.png b/res/drawable-xxhdpi/tab_btn_bg_normal.9.png deleted file mode 100644 index 9f3a0f9..0000000 Binary files a/res/drawable-xxhdpi/tab_btn_bg_normal.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png deleted file mode 100644 index 53f226e..0000000 Binary files a/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_selected.9.png b/res/drawable-xxhdpi/tab_selected.9.png deleted file mode 100644 index e5efc58..0000000 Binary files a/res/drawable-xxhdpi/tab_selected.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png deleted file mode 100644 index 8762b9e..0000000 Binary files a/res/drawable-xxhdpi/tab_selected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png deleted file mode 100644 index 8faa7ff..0000000 Binary files a/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png deleted file mode 100644 index c7a539b..0000000 Binary files a/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_unselected.9.png b/res/drawable-xxhdpi/tab_unselected.9.png deleted file mode 100644 index 3891886..0000000 Binary files a/res/drawable-xxhdpi/tab_unselected.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png deleted file mode 100644 index 462ab15..0000000 Binary files a/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png deleted file mode 100644 index 58ddbe8..0000000 Binary files a/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png deleted file mode 100644 index de61933..0000000 Binary files a/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/widget_hr.9.png b/res/drawable-xxhdpi/widget_hr.9.png deleted file mode 100644 index 375272b..0000000 Binary files a/res/drawable-xxhdpi/widget_hr.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png deleted file mode 100644 index 5093c23..0000000 Binary files a/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png deleted file mode 100644 index 6ae5608..0000000 Binary files a/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png deleted file mode 100644 index 65cd55e..0000000 Binary files a/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png deleted file mode 100644 index b6cac56..0000000 Binary files a/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png deleted file mode 100644 index f017e03..0000000 Binary files a/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png deleted file mode 100644 index 025e694..0000000 Binary files a/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/cab_bg.9.png b/res/drawable-xxxhdpi/cab_bg.9.png deleted file mode 100644 index 978cfad..0000000 Binary files a/res/drawable-xxxhdpi/cab_bg.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/contact_popup_background.9.png b/res/drawable-xxxhdpi/contact_popup_background.9.png deleted file mode 100644 index 30919fa..0000000 Binary files a/res/drawable-xxxhdpi/contact_popup_background.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/fab_new_message_static_shadow.png b/res/drawable-xxxhdpi/fab_new_message_static_shadow.png deleted file mode 100644 index e1d16a6..0000000 Binary files a/res/drawable-xxxhdpi/fab_new_message_static_shadow.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_add_white.png b/res/drawable-xxxhdpi/ic_add_white.png deleted file mode 100644 index 0ed23b1..0000000 Binary files a/res/drawable-xxxhdpi/ic_add_white.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_archive_small_dark.png b/res/drawable-xxxhdpi/ic_archive_small_dark.png deleted file mode 100644 index c380363..0000000 Binary files a/res/drawable-xxxhdpi/ic_archive_small_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_archive_small_light.png b/res/drawable-xxxhdpi/ic_archive_small_light.png deleted file mode 100644 index 59ffef9..0000000 Binary files a/res/drawable-xxxhdpi/ic_archive_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png b/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png deleted file mode 100644 index b2e4f8b..0000000 Binary files a/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_archive_undo_small_light.png b/res/drawable-xxxhdpi/ic_archive_undo_small_light.png deleted file mode 100644 index 46fd7d8..0000000 Binary files a/res/drawable-xxxhdpi/ic_archive_undo_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_arrow_back_dark.png b/res/drawable-xxxhdpi/ic_arrow_back_dark.png deleted file mode 100644 index e7a9c81..0000000 Binary files a/res/drawable-xxxhdpi/ic_arrow_back_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_arrow_back_light.png b/res/drawable-xxxhdpi/ic_arrow_back_light.png deleted file mode 100644 index 54a5687..0000000 Binary files a/res/drawable-xxxhdpi/ic_arrow_back_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_attachment_dark.png b/res/drawable-xxxhdpi/ic_attachment_dark.png deleted file mode 100644 index 7cf871a..0000000 Binary files a/res/drawable-xxxhdpi/ic_attachment_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_audio_light.png b/res/drawable-xxxhdpi/ic_audio_light.png deleted file mode 100644 index 768ac89..0000000 Binary files a/res/drawable-xxxhdpi/ic_audio_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_audio_pause.png b/res/drawable-xxxhdpi/ic_audio_pause.png deleted file mode 100644 index f7ae29f..0000000 Binary files a/res/drawable-xxxhdpi/ic_audio_pause.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_audio_play.png b/res/drawable-xxxhdpi/ic_audio_play.png deleted file mode 100644 index 71f21e7..0000000 Binary files a/res/drawable-xxxhdpi/ic_audio_play.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_camera_front_light.png b/res/drawable-xxxhdpi/ic_camera_front_light.png deleted file mode 100644 index 3af175b..0000000 Binary files a/res/drawable-xxxhdpi/ic_camera_front_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_camera_light.png b/res/drawable-xxxhdpi/ic_camera_light.png deleted file mode 100644 index c3484b6..0000000 Binary files a/res/drawable-xxxhdpi/ic_camera_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_camera_rear_light.png b/res/drawable-xxxhdpi/ic_camera_rear_light.png deleted file mode 100644 index c59b65b..0000000 Binary files a/res/drawable-xxxhdpi/ic_camera_rear_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_cancel_small_dark.png b/res/drawable-xxxhdpi/ic_cancel_small_dark.png deleted file mode 100644 index f454d2a..0000000 Binary files a/res/drawable-xxxhdpi/ic_cancel_small_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_cancel_small_light.png b/res/drawable-xxxhdpi/ic_cancel_small_light.png deleted file mode 100644 index cce1904..0000000 Binary files a/res/drawable-xxxhdpi/ic_cancel_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkbox_blank_light.png b/res/drawable-xxxhdpi/ic_checkbox_blank_light.png deleted file mode 100644 index ad983af..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkbox_blank_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkbox_light.png b/res/drawable-xxxhdpi/ic_checkbox_light.png deleted file mode 100644 index c8e8b79..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkbox_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkbox_outline_light.png b/res/drawable-xxxhdpi/ic_checkbox_outline_light.png deleted file mode 100644 index eb0a803..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkbox_outline_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png b/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png deleted file mode 100644 index 7bb1501..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkmark_large.png b/res/drawable-xxxhdpi/ic_checkmark_large.png deleted file mode 100644 index 8119ff2..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkmark_large.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkmark_large_light.png b/res/drawable-xxxhdpi/ic_checkmark_large_light.png deleted file mode 100644 index dddac27..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkmark_large_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkmark_small_blue.png b/res/drawable-xxxhdpi/ic_checkmark_small_blue.png deleted file mode 100644 index 3199069..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkmark_small_blue.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_checkmark_small_light.png b/res/drawable-xxxhdpi/ic_checkmark_small_light.png deleted file mode 100644 index 380f4e8..0000000 Binary files a/res/drawable-xxxhdpi/ic_checkmark_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_color_lens.png b/res/drawable-xxxhdpi/ic_color_lens.png deleted file mode 100644 index 5c71071..0000000 Binary files a/res/drawable-xxxhdpi/ic_color_lens.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_content_copy_dark.png b/res/drawable-xxxhdpi/ic_content_copy_dark.png deleted file mode 100644 index 24dc390..0000000 Binary files a/res/drawable-xxxhdpi/ic_content_copy_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_crying_hero.png b/res/drawable-xxxhdpi/ic_crying_hero.png deleted file mode 100644 index 0a428c9..0000000 Binary files a/res/drawable-xxxhdpi/ic_crying_hero.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_delete_small_dark.png b/res/drawable-xxxhdpi/ic_delete_small_dark.png deleted file mode 100644 index 497e7a7..0000000 Binary files a/res/drawable-xxxhdpi/ic_delete_small_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_delete_small_light.png b/res/drawable-xxxhdpi/ic_delete_small_light.png deleted file mode 100644 index d8a661f..0000000 Binary files a/res/drawable-xxxhdpi/ic_delete_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_dnd_on_dark.png b/res/drawable-xxxhdpi/ic_dnd_on_dark.png deleted file mode 100644 index de2f17b..0000000 Binary files a/res/drawable-xxxhdpi/ic_dnd_on_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_dnd_on_light.png b/res/drawable-xxxhdpi/ic_dnd_on_light.png deleted file mode 100644 index 7851985..0000000 Binary files a/res/drawable-xxxhdpi/ic_dnd_on_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_failed_light.png b/res/drawable-xxxhdpi/ic_failed_light.png deleted file mode 100644 index 53d374c..0000000 Binary files a/res/drawable-xxxhdpi/ic_failed_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_failed_status_red.png b/res/drawable-xxxhdpi/ic_failed_status_red.png deleted file mode 100644 index ee1ed24..0000000 Binary files a/res/drawable-xxxhdpi/ic_failed_status_red.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_file_download_dark.png b/res/drawable-xxxhdpi/ic_file_download_dark.png deleted file mode 100644 index 0012e5e..0000000 Binary files a/res/drawable-xxxhdpi/ic_file_download_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_file_download_light.png b/res/drawable-xxxhdpi/ic_file_download_light.png deleted file mode 100644 index 6d7ca08..0000000 Binary files a/res/drawable-xxxhdpi/ic_file_download_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_forward_dark.png b/res/drawable-xxxhdpi/ic_forward_dark.png deleted file mode 100644 index 6408fb9..0000000 Binary files a/res/drawable-xxxhdpi/ic_forward_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_history_light.png b/res/drawable-xxxhdpi/ic_history_light.png deleted file mode 100644 index 7ff42b7..0000000 Binary files a/res/drawable-xxxhdpi/ic_history_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_image_light.png b/res/drawable-xxxhdpi/ic_image_light.png deleted file mode 100644 index 0d229e2..0000000 Binary files a/res/drawable-xxxhdpi/ic_image_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_ime_dark.png b/res/drawable-xxxhdpi/ic_ime_dark.png deleted file mode 100644 index 7c051df..0000000 Binary files a/res/drawable-xxxhdpi/ic_ime_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_ime_light.png b/res/drawable-xxxhdpi/ic_ime_light.png deleted file mode 100644 index 031d086..0000000 Binary files a/res/drawable-xxxhdpi/ic_ime_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_info_dark.png b/res/drawable-xxxhdpi/ic_info_dark.png deleted file mode 100644 index 107385b..0000000 Binary files a/res/drawable-xxxhdpi/ic_info_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png deleted file mode 100644 index 3737fec..0000000 Binary files a/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png deleted file mode 100644 index 85d005b..0000000 Binary files a/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_launcher.png b/res/drawable-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0d61198..0000000 Binary files a/res/drawable-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_audio_mic.png b/res/drawable-xxxhdpi/ic_mp_audio_mic.png deleted file mode 100644 index 9f51c4b..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_audio_mic.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_camera_large_light.png b/res/drawable-xxxhdpi/ic_mp_camera_large_light.png deleted file mode 100644 index f915989..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_camera_large_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_camera_small_light.png b/res/drawable-xxxhdpi/ic_mp_camera_small_light.png deleted file mode 100644 index d6d8c9d..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_camera_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png deleted file mode 100644 index 30e725f..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_full_screen_light.png b/res/drawable-xxxhdpi/ic_mp_full_screen_light.png deleted file mode 100644 index 4860109..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_full_screen_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_video_large_light.png b/res/drawable-xxxhdpi/ic_mp_video_large_light.png deleted file mode 100644 index 32c4317..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_video_large_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_mp_video_small_light.png b/res/drawable-xxxhdpi/ic_mp_video_small_light.png deleted file mode 100644 index 6d2a14b..0000000 Binary files a/res/drawable-xxxhdpi/ic_mp_video_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_notifications_off_dark.png b/res/drawable-xxxhdpi/ic_notifications_off_dark.png deleted file mode 100644 index 10ac318..0000000 Binary files a/res/drawable-xxxhdpi/ic_notifications_off_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_notifications_off_light.png b/res/drawable-xxxhdpi/ic_notifications_off_light.png deleted file mode 100644 index 8eb5782..0000000 Binary files a/res/drawable-xxxhdpi/ic_notifications_off_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_notifications_off_small_light.png b/res/drawable-xxxhdpi/ic_notifications_off_small_light.png deleted file mode 100644 index 3756943..0000000 Binary files a/res/drawable-xxxhdpi/ic_notifications_off_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_notifications_on_dark.png b/res/drawable-xxxhdpi/ic_notifications_on_dark.png deleted file mode 100644 index 3896212..0000000 Binary files a/res/drawable-xxxhdpi/ic_notifications_on_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_notifications_on_light.png b/res/drawable-xxxhdpi/ic_notifications_on_light.png deleted file mode 100644 index 47794fd..0000000 Binary files a/res/drawable-xxxhdpi/ic_notifications_on_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_numeric_dialpad.png b/res/drawable-xxxhdpi/ic_numeric_dialpad.png deleted file mode 100644 index 5e0986c..0000000 Binary files a/res/drawable-xxxhdpi/ic_numeric_dialpad.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_oobe_conv_list.png b/res/drawable-xxxhdpi/ic_oobe_conv_list.png deleted file mode 100644 index 0decb23..0000000 Binary files a/res/drawable-xxxhdpi/ic_oobe_conv_list.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_oobe_freq_list.png b/res/drawable-xxxhdpi/ic_oobe_freq_list.png deleted file mode 100644 index 4522cb6..0000000 Binary files a/res/drawable-xxxhdpi/ic_oobe_freq_list.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_open_in_new.png b/res/drawable-xxxhdpi/ic_open_in_new.png deleted file mode 100644 index 6d6b59f..0000000 Binary files a/res/drawable-xxxhdpi/ic_open_in_new.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_people_add_light.png b/res/drawable-xxxhdpi/ic_people_add_light.png deleted file mode 100644 index 5db76a9..0000000 Binary files a/res/drawable-xxxhdpi/ic_people_add_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_person_add_blue.png b/res/drawable-xxxhdpi/ic_person_add_blue.png deleted file mode 100644 index b358660..0000000 Binary files a/res/drawable-xxxhdpi/ic_person_add_blue.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_person_add_dark.png b/res/drawable-xxxhdpi/ic_person_add_dark.png deleted file mode 100644 index 37e252f..0000000 Binary files a/res/drawable-xxxhdpi/ic_person_add_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_person_add_light.png b/res/drawable-xxxhdpi/ic_person_add_light.png deleted file mode 100644 index b279246..0000000 Binary files a/res/drawable-xxxhdpi/ic_person_add_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_person_light.png b/res/drawable-xxxhdpi/ic_person_light.png deleted file mode 100644 index 59853fc..0000000 Binary files a/res/drawable-xxxhdpi/ic_person_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_person_light_large.png b/res/drawable-xxxhdpi/ic_person_light_large.png deleted file mode 100644 index 44d4809..0000000 Binary files a/res/drawable-xxxhdpi/ic_person_light_large.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_phone_small_light.png b/res/drawable-xxxhdpi/ic_phone_small_light.png deleted file mode 100644 index 433e4a7..0000000 Binary files a/res/drawable-xxxhdpi/ic_phone_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_photo_library_light.png b/res/drawable-xxxhdpi/ic_photo_library_light.png deleted file mode 100644 index 8627f42..0000000 Binary files a/res/drawable-xxxhdpi/ic_photo_library_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_preview_pause.png b/res/drawable-xxxhdpi/ic_preview_pause.png deleted file mode 100644 index 2ba4fe2..0000000 Binary files a/res/drawable-xxxhdpi/ic_preview_pause.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_preview_play.png b/res/drawable-xxxhdpi/ic_preview_play.png deleted file mode 100644 index f44f572..0000000 Binary files a/res/drawable-xxxhdpi/ic_preview_play.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_remove_light.png b/res/drawable-xxxhdpi/ic_remove_light.png deleted file mode 100644 index b9288bf..0000000 Binary files a/res/drawable-xxxhdpi/ic_remove_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_remove_small_light.png b/res/drawable-xxxhdpi/ic_remove_small_light.png deleted file mode 100644 index 0e0cb05..0000000 Binary files a/res/drawable-xxxhdpi/ic_remove_small_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_save_dark.png b/res/drawable-xxxhdpi/ic_save_dark.png deleted file mode 100644 index 2819de2..0000000 Binary files a/res/drawable-xxxhdpi/ic_save_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_save_light.png b/res/drawable-xxxhdpi/ic_save_light.png deleted file mode 100644 index ba7564b..0000000 Binary files a/res/drawable-xxxhdpi/ic_save_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_search_light.png b/res/drawable-xxxhdpi/ic_search_light.png deleted file mode 100644 index b38785a..0000000 Binary files a/res/drawable-xxxhdpi/ic_search_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_send_dark.png b/res/drawable-xxxhdpi/ic_send_dark.png deleted file mode 100644 index 5f52b29..0000000 Binary files a/res/drawable-xxxhdpi/ic_send_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_send_light.png b/res/drawable-xxxhdpi/ic_send_light.png deleted file mode 100644 index b4e8563..0000000 Binary files a/res/drawable-xxxhdpi/ic_send_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_share_dark.png b/res/drawable-xxxhdpi/ic_share_dark.png deleted file mode 100644 index a6ddd2d..0000000 Binary files a/res/drawable-xxxhdpi/ic_share_dark.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_share_light.png b/res/drawable-xxxhdpi/ic_share_light.png deleted file mode 100644 index ac63637..0000000 Binary files a/res/drawable-xxxhdpi/ic_share_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_sim_card_send.png b/res/drawable-xxxhdpi/ic_sim_card_send.png deleted file mode 100644 index 0db661b..0000000 Binary files a/res/drawable-xxxhdpi/ic_sim_card_send.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_sms_delivery_ok.png b/res/drawable-xxxhdpi/ic_sms_delivery_ok.png deleted file mode 100644 index 487525d..0000000 Binary files a/res/drawable-xxxhdpi/ic_sms_delivery_ok.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_sms_failed_light.png b/res/drawable-xxxhdpi/ic_sms_failed_light.png deleted file mode 100644 index 2daf2ae..0000000 Binary files a/res/drawable-xxxhdpi/ic_sms_failed_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_sms_light.png b/res/drawable-xxxhdpi/ic_sms_light.png deleted file mode 100644 index 98d1bc8..0000000 Binary files a/res/drawable-xxxhdpi/ic_sms_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_sms_multi_light.png b/res/drawable-xxxhdpi/ic_sms_multi_light.png deleted file mode 100644 index e2f4bfc..0000000 Binary files a/res/drawable-xxxhdpi/ic_sms_multi_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_tooltip_arrow.png b/res/drawable-xxxhdpi/ic_tooltip_arrow.png deleted file mode 100644 index 84b03ad..0000000 Binary files a/res/drawable-xxxhdpi/ic_tooltip_arrow.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_video_play_light.png b/res/drawable-xxxhdpi/ic_video_play_light.png deleted file mode 100644 index 9192f84..0000000 Binary files a/res/drawable-xxxhdpi/ic_video_play_light.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_wear_reply.png b/res/drawable-xxxhdpi/ic_wear_reply.png deleted file mode 100644 index 34065f6..0000000 Binary files a/res/drawable-xxxhdpi/ic_wear_reply.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png b/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png deleted file mode 100644 index 83d02ce..0000000 Binary files a/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_widget_list.png b/res/drawable-xxxhdpi/ic_widget_list.png deleted file mode 100644 index f316487..0000000 Binary files a/res/drawable-xxxhdpi/ic_widget_list.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/msg_bubble_error.9.png b/res/drawable-xxxhdpi/msg_bubble_error.9.png deleted file mode 100644 index fb8161c..0000000 Binary files a/res/drawable-xxxhdpi/msg_bubble_error.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxxhdpi/msg_bubble_incoming.9.png deleted file mode 100644 index 34130fc..0000000 Binary files a/res/drawable-xxxhdpi/msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/msg_bubble_input.9.png b/res/drawable-xxxhdpi/msg_bubble_input.9.png deleted file mode 100644 index d330459..0000000 Binary files a/res/drawable-xxxhdpi/msg_bubble_input.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png deleted file mode 100644 index cfd5734..0000000 Binary files a/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/permissions.png b/res/drawable-xxxhdpi/permissions.png deleted file mode 100644 index 2a7a0d7..0000000 Binary files a/res/drawable-xxxhdpi/permissions.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/swipe_shadow.9.png b/res/drawable-xxxhdpi/swipe_shadow.9.png deleted file mode 100644 index 4726ef4..0000000 Binary files a/res/drawable-xxxhdpi/swipe_shadow.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/swipe_shadow_drag.9.png b/res/drawable-xxxhdpi/swipe_shadow_drag.9.png deleted file mode 100644 index d48e311..0000000 Binary files a/res/drawable-xxxhdpi/swipe_shadow_drag.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png b/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png deleted file mode 100644 index 09af002..0000000 Binary files a/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png b/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png deleted file mode 100644 index 1290a0d..0000000 Binary files a/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png deleted file mode 100644 index 9fce798..0000000 Binary files a/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_selected.9.png b/res/drawable-xxxhdpi/tab_selected.9.png deleted file mode 100644 index 5c20d4d..0000000 Binary files a/res/drawable-xxxhdpi/tab_selected.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png deleted file mode 100644 index 33be972..0000000 Binary files a/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png deleted file mode 100644 index dc38a52..0000000 Binary files a/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png deleted file mode 100644 index e4518a8..0000000 Binary files a/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_unselected.9.png b/res/drawable-xxxhdpi/tab_unselected.9.png deleted file mode 100644 index 9df735b..0000000 Binary files a/res/drawable-xxxhdpi/tab_unselected.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png deleted file mode 100644 index f01cfaf..0000000 Binary files a/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png deleted file mode 100644 index 7d85b20..0000000 Binary files a/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png deleted file mode 100644 index 0ace170..0000000 Binary files a/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/widget_hr.9.png b/res/drawable-xxxhdpi/widget_hr.9.png deleted file mode 100644 index 80fdaf6..0000000 Binary files a/res/drawable-xxxhdpi/widget_hr.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png deleted file mode 100644 index 383133e..0000000 Binary files a/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png and /dev/null differ diff --git a/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png deleted file mode 100644 index daf33b5..0000000 Binary files a/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png and /dev/null differ diff --git a/res/drawable/attachment_audio_preview_background.xml b/res/drawable/attachment_audio_preview_background.xml deleted file mode 100644 index 8b62415..0000000 --- a/res/drawable/attachment_audio_preview_background.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/attachment_image_placeholder_background.xml b/res/drawable/attachment_image_placeholder_background.xml deleted file mode 100644 index b3315e8..0000000 --- a/res/drawable/attachment_image_placeholder_background.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/attachment_more_items_background.xml b/res/drawable/attachment_more_items_background.xml deleted file mode 100644 index 0ef8e61..0000000 --- a/res/drawable/attachment_more_items_background.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/attachment_vcard_preview_background.xml b/res/drawable/attachment_vcard_preview_background.xml deleted file mode 100644 index 8b62415..0000000 --- a/res/drawable/attachment_vcard_preview_background.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/audio_progress_bar_background_incoming.xml b/res/drawable/audio_progress_bar_background_incoming.xml deleted file mode 100644 index 990e1de..0000000 --- a/res/drawable/audio_progress_bar_background_incoming.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/audio_progress_bar_background_outgoing.xml b/res/drawable/audio_progress_bar_background_outgoing.xml deleted file mode 100644 index 351cf18..0000000 --- a/res/drawable/audio_progress_bar_background_outgoing.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/audio_progress_bar_progress.xml b/res/drawable/audio_progress_bar_progress.xml deleted file mode 100644 index a70ff32..0000000 --- a/res/drawable/audio_progress_bar_progress.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/audio_record_control_button_background.xml b/res/drawable/audio_record_control_button_background.xml deleted file mode 100644 index 02f329e..0000000 --- a/res/drawable/audio_record_control_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/chips_dropdown_background.xml b/res/drawable/chips_dropdown_background.xml deleted file mode 100644 index 48a727d..0000000 --- a/res/drawable/chips_dropdown_background.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/chips_dropdown_text_color.xml b/res/drawable/chips_dropdown_text_color.xml deleted file mode 100644 index 5f06751..0000000 --- a/res/drawable/chips_dropdown_text_color.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/drawable/compose_chips_divider_gradient.xml b/res/drawable/compose_chips_divider_gradient.xml deleted file mode 100644 index 65c6601..0000000 --- a/res/drawable/compose_chips_divider_gradient.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/drawable/contact_picker_tab_background_selector.xml b/res/drawable/contact_picker_tab_background_selector.xml deleted file mode 100644 index 78c443b..0000000 --- a/res/drawable/contact_picker_tab_background_selector.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/drawable/contacts_fastscroll_label_left.xml b/res/drawable/contacts_fastscroll_label_left.xml deleted file mode 100644 index 5ed079a..0000000 --- a/res/drawable/contacts_fastscroll_label_left.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/contacts_fastscroll_label_right.xml b/res/drawable/contacts_fastscroll_label_right.xml deleted file mode 100644 index 67c4622..0000000 --- a/res/drawable/contacts_fastscroll_label_right.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/conversation_compose_divider_gradient.xml b/res/drawable/conversation_compose_divider_gradient.xml deleted file mode 100644 index 2c8a180..0000000 --- a/res/drawable/conversation_compose_divider_gradient.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/drawable/default_image.xml b/res/drawable/default_image.xml deleted file mode 100644 index 749e73b..0000000 --- a/res/drawable/default_image.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - diff --git a/res/drawable/exit_button_bg.xml b/res/drawable/exit_button_bg.xml deleted file mode 100644 index 9fa0fb2..0000000 --- a/res/drawable/exit_button_bg.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/res/drawable/fab_new_message_bg.xml b/res/drawable/fab_new_message_bg.xml deleted file mode 100644 index 741b4b5..0000000 --- a/res/drawable/fab_new_message_bg.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/fastscroll_preview_left.xml b/res/drawable/fastscroll_preview_left.xml deleted file mode 100644 index 165a222..0000000 --- a/res/drawable/fastscroll_preview_left.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/res/drawable/fastscroll_preview_right.xml b/res/drawable/fastscroll_preview_right.xml deleted file mode 100644 index 7c0dc09..0000000 --- a/res/drawable/fastscroll_preview_right.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/res/drawable/fastscroll_thumb.xml b/res/drawable/fastscroll_thumb.xml deleted file mode 100644 index be7c96f..0000000 --- a/res/drawable/fastscroll_thumb.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/res/drawable/fastscroll_thumb_pressed.xml b/res/drawable/fastscroll_thumb_pressed.xml deleted file mode 100644 index 39de5a6..0000000 --- a/res/drawable/fastscroll_thumb_pressed.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/res/drawable/fastscroll_track.xml b/res/drawable/fastscroll_track.xml deleted file mode 100644 index f3bf96d..0000000 --- a/res/drawable/fastscroll_track.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/gallery_checkbox_selector.xml b/res/drawable/gallery_checkbox_selector.xml deleted file mode 100644 index cdd9734..0000000 --- a/res/drawable/gallery_checkbox_selector.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/gallery_document_picker_item_background.xml b/res/drawable/gallery_document_picker_item_background.xml deleted file mode 100644 index 4bbd7fe..0000000 --- a/res/drawable/gallery_document_picker_item_background.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/drawable/gallery_image_background_selector.xml b/res/drawable/gallery_image_background_selector.xml deleted file mode 100644 index 0b902fd..0000000 --- a/res/drawable/gallery_image_background_selector.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/generic_video_icon.xml b/res/drawable/generic_video_icon.xml deleted file mode 100644 index e98cb94..0000000 --- a/res/drawable/generic_video_icon.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/mediapicker_tab_button_background.xml b/res/drawable/mediapicker_tab_button_background.xml deleted file mode 100644 index 08dbf5c..0000000 --- a/res/drawable/mediapicker_tab_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/message_bubble_incoming_no_arrow.xml b/res/drawable/message_bubble_incoming_no_arrow.xml deleted file mode 100644 index d234508..0000000 --- a/res/drawable/message_bubble_incoming_no_arrow.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/res/drawable/message_bubble_outgoing_no_arrow.xml b/res/drawable/message_bubble_outgoing_no_arrow.xml deleted file mode 100644 index d234508..0000000 --- a/res/drawable/message_bubble_outgoing_no_arrow.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/res/drawable/send_arrow_background.xml b/res/drawable/send_arrow_background.xml deleted file mode 100644 index 4434997..0000000 --- a/res/drawable/send_arrow_background.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/sim_selector_background_gradient.xml b/res/drawable/sim_selector_background_gradient.xml deleted file mode 100644 index ea7e440..0000000 --- a/res/drawable/sim_selector_background_gradient.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/drawable/stat_notify_chat.png b/res/drawable/stat_notify_chat.png deleted file mode 100644 index cc08e21..0000000 Binary files a/res/drawable/stat_notify_chat.png and /dev/null differ diff --git a/res/drawable/subject_editor_bubble.xml b/res/drawable/subject_editor_bubble.xml deleted file mode 100644 index 21f0248..0000000 --- a/res/drawable/subject_editor_bubble.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/tab_btn_bg.xml b/res/drawable/tab_btn_bg.xml deleted file mode 100644 index f7f03dd..0000000 --- a/res/drawable/tab_btn_bg.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - diff --git a/res/drawable/tab_indicator.xml b/res/drawable/tab_indicator.xml deleted file mode 100644 index 28ecda3..0000000 --- a/res/drawable/tab_indicator.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/res/drawable/transparent_button_background.xml b/res/drawable/transparent_button_background.xml deleted file mode 100644 index 08dbf5c..0000000 --- a/res/drawable/transparent_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/widget_bottom_background.xml b/res/drawable/widget_bottom_background.xml deleted file mode 100644 index 198b2d6..0000000 --- a/res/drawable/widget_bottom_background.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/widget_top_background.xml b/res/drawable/widget_top_background.xml deleted file mode 100644 index 1476c95..0000000 --- a/res/drawable/widget_top_background.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/action_bar_conversation_name.xml b/res/layout/action_bar_conversation_name.xml deleted file mode 100644 index 586cf83..0000000 --- a/res/layout/action_bar_conversation_name.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/add_contacts_confirmation_dialog_body.xml b/res/layout/add_contacts_confirmation_dialog_body.xml deleted file mode 100644 index 07f3d0f..0000000 --- a/res/layout/add_contacts_confirmation_dialog_body.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - diff --git a/res/layout/all_contacts_list_view.xml b/res/layout/all_contacts_list_view.xml deleted file mode 100644 index fe05c4d..0000000 --- a/res/layout/all_contacts_list_view.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - diff --git a/res/layout/apn_preference_layout.xml b/res/layout/apn_preference_layout.xml deleted file mode 100644 index 25a0323..0000000 --- a/res/layout/apn_preference_layout.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/res/layout/attachment_chooser_activity.xml b/res/layout/attachment_chooser_activity.xml deleted file mode 100644 index 850716e..0000000 --- a/res/layout/attachment_chooser_activity.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/attachment_chooser_audio.xml b/res/layout/attachment_chooser_audio.xml deleted file mode 100644 index ea2f431..0000000 --- a/res/layout/attachment_chooser_audio.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_chooser_fragment.xml b/res/layout/attachment_chooser_fragment.xml deleted file mode 100644 index 15e249a..0000000 --- a/res/layout/attachment_chooser_fragment.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/res/layout/attachment_chooser_image.xml b/res/layout/attachment_chooser_image.xml deleted file mode 100644 index b635161..0000000 --- a/res/layout/attachment_chooser_image.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/res/layout/attachment_chooser_vcard.xml b/res/layout/attachment_chooser_vcard.xml deleted file mode 100644 index fb20f84..0000000 --- a/res/layout/attachment_chooser_vcard.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_chooser_video.xml b/res/layout/attachment_chooser_video.xml deleted file mode 100644 index 4ae4b0a..0000000 --- a/res/layout/attachment_chooser_video.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/res/layout/attachment_grid_item_view.xml b/res/layout/attachment_grid_item_view.xml deleted file mode 100644 index 639ea64..0000000 --- a/res/layout/attachment_grid_item_view.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - diff --git a/res/layout/attachment_more_text_view.xml b/res/layout/attachment_more_text_view.xml deleted file mode 100644 index 1a99a63..0000000 --- a/res/layout/attachment_more_text_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/attachment_multiple_audio.xml b/res/layout/attachment_multiple_audio.xml deleted file mode 100644 index 48440f0..0000000 --- a/res/layout/attachment_multiple_audio.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_multiple_image.xml b/res/layout/attachment_multiple_image.xml deleted file mode 100644 index 7d911ac..0000000 --- a/res/layout/attachment_multiple_image.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - diff --git a/res/layout/attachment_multiple_vcard.xml b/res/layout/attachment_multiple_vcard.xml deleted file mode 100644 index e03d47b..0000000 --- a/res/layout/attachment_multiple_vcard.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_multiple_video.xml b/res/layout/attachment_multiple_video.xml deleted file mode 100644 index fdbc423..0000000 --- a/res/layout/attachment_multiple_video.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/res/layout/attachment_pending_item.xml b/res/layout/attachment_pending_item.xml deleted file mode 100644 index 3e9bec7..0000000 --- a/res/layout/attachment_pending_item.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - diff --git a/res/layout/attachment_preview.xml b/res/layout/attachment_preview.xml deleted file mode 100644 index 36ea038..0000000 --- a/res/layout/attachment_preview.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/attachment_single_audio.xml b/res/layout/attachment_single_audio.xml deleted file mode 100644 index 3ad02a8..0000000 --- a/res/layout/attachment_single_audio.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_single_image.xml b/res/layout/attachment_single_image.xml deleted file mode 100644 index 7338368..0000000 --- a/res/layout/attachment_single_image.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - diff --git a/res/layout/attachment_single_vcard.xml b/res/layout/attachment_single_vcard.xml deleted file mode 100644 index 6016593..0000000 --- a/res/layout/attachment_single_vcard.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - diff --git a/res/layout/attachment_single_video.xml b/res/layout/attachment_single_video.xml deleted file mode 100644 index fba594f..0000000 --- a/res/layout/attachment_single_video.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - diff --git a/res/layout/audio_attachment_view.xml b/res/layout/audio_attachment_view.xml deleted file mode 100644 index 6fcf89c..0000000 --- a/res/layout/audio_attachment_view.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/res/layout/blocked_participant_list_item_view.xml b/res/layout/blocked_participant_list_item_view.xml deleted file mode 100644 index 7ac0ec9..0000000 --- a/res/layout/blocked_participant_list_item_view.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - diff --git a/res/layout/blocked_participants_activity.xml b/res/layout/blocked_participants_activity.xml deleted file mode 100644 index e00f20f..0000000 --- a/res/layout/blocked_participants_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/blocked_participants_fragment.xml b/res/layout/blocked_participants_fragment.xml deleted file mode 100644 index 7bb3c6a..0000000 --- a/res/layout/blocked_participants_fragment.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/chips_alternates_dropdown_item.xml b/res/layout/chips_alternates_dropdown_item.xml deleted file mode 100644 index dad052c..0000000 --- a/res/layout/chips_alternates_dropdown_item.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/compose_message_view.xml b/res/layout/compose_message_view.xml deleted file mode 100644 index fd60e2a..0000000 --- a/res/layout/compose_message_view.xml +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/contact_list_item_view.xml b/res/layout/contact_list_item_view.xml deleted file mode 100644 index 3015ae3..0000000 --- a/res/layout/contact_list_item_view.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/contact_picker_fragment.xml b/res/layout/contact_picker_fragment.xml deleted file mode 100644 index c65f140..0000000 --- a/res/layout/contact_picker_fragment.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml deleted file mode 100644 index 8879704..0000000 --- a/res/layout/conversation_activity.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/layout/conversation_fragment.xml b/res/layout/conversation_fragment.xml deleted file mode 100644 index 028e66b..0000000 --- a/res/layout/conversation_fragment.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/conversation_list_activity.xml b/res/layout/conversation_list_activity.xml deleted file mode 100644 index 48f3b15..0000000 --- a/res/layout/conversation_list_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/conversation_list_fragment.xml b/res/layout/conversation_list_fragment.xml deleted file mode 100644 index bf5d401..0000000 --- a/res/layout/conversation_list_fragment.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml deleted file mode 100644 index da1ca4e..0000000 --- a/res/layout/conversation_list_item_view.xml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/conversation_message_view.xml b/res/layout/conversation_message_view.xml deleted file mode 100644 index daad600..0000000 --- a/res/layout/conversation_message_view.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/copy_contact_dialog_view.xml b/res/layout/copy_contact_dialog_view.xml deleted file mode 100644 index 15e07f7..0000000 --- a/res/layout/copy_contact_dialog_view.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/custom_header_view_pager.xml b/res/layout/custom_header_view_pager.xml deleted file mode 100644 index 9552cd6..0000000 --- a/res/layout/custom_header_view_pager.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/debug_mmsconfig_activity.xml b/res/layout/debug_mmsconfig_activity.xml deleted file mode 100644 index 4056317..0000000 --- a/res/layout/debug_mmsconfig_activity.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/debug_mmsconfig_fragment.xml b/res/layout/debug_mmsconfig_fragment.xml deleted file mode 100644 index be61710..0000000 --- a/res/layout/debug_mmsconfig_fragment.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/debug_mmsconfig_item_view.xml b/res/layout/debug_mmsconfig_item_view.xml deleted file mode 100644 index a825ee5..0000000 --- a/res/layout/debug_mmsconfig_item_view.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - diff --git a/res/layout/debug_sms_mms_from_dump_file_dialog.xml b/res/layout/debug_sms_mms_from_dump_file_dialog.xml deleted file mode 100644 index 342f489..0000000 --- a/res/layout/debug_sms_mms_from_dump_file_dialog.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/res/layout/enter_phone_number_view.xml b/res/layout/enter_phone_number_view.xml deleted file mode 100644 index 825ac51..0000000 --- a/res/layout/enter_phone_number_view.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/res/layout/fastscroll_preview.xml b/res/layout/fastscroll_preview.xml deleted file mode 100644 index ab72744..0000000 --- a/res/layout/fastscroll_preview.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/res/layout/fastscroll_thumb.xml b/res/layout/fastscroll_thumb.xml deleted file mode 100644 index 6f57056..0000000 --- a/res/layout/fastscroll_thumb.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/fastscroll_track.xml b/res/layout/fastscroll_track.xml deleted file mode 100644 index 6a35e94..0000000 --- a/res/layout/fastscroll_track.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/res/layout/frequent_contacts_list_view.xml b/res/layout/frequent_contacts_list_view.xml deleted file mode 100644 index 9cb0c3a..0000000 --- a/res/layout/frequent_contacts_list_view.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/layout/gallery_grid_item_view.xml b/res/layout/gallery_grid_item_view.xml deleted file mode 100644 index 8b7ee58..0000000 --- a/res/layout/gallery_grid_item_view.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - diff --git a/res/layout/group_mms_setting_dialog.xml b/res/layout/group_mms_setting_dialog.xml deleted file mode 100644 index efc3b00..0000000 --- a/res/layout/group_mms_setting_dialog.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - diff --git a/res/layout/license_activity.xml b/res/layout/license_activity.xml deleted file mode 100644 index 1475ac2..0000000 --- a/res/layout/license_activity.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/layout/list_empty_view.xml b/res/layout/list_empty_view.xml deleted file mode 100644 index aa4b4fb..0000000 --- a/res/layout/list_empty_view.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - diff --git a/res/layout/mediapicker_audio_chooser.xml b/res/layout/mediapicker_audio_chooser.xml deleted file mode 100644 index 795d2f8..0000000 --- a/res/layout/mediapicker_audio_chooser.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/mediapicker_camera_chooser.xml b/res/layout/mediapicker_camera_chooser.xml deleted file mode 100644 index 27d26bd..0000000 --- a/res/layout/mediapicker_camera_chooser.xml +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/mediapicker_fragment.xml b/res/layout/mediapicker_fragment.xml deleted file mode 100644 index 2e414bd..0000000 --- a/res/layout/mediapicker_fragment.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - diff --git a/res/layout/mediapicker_image_chooser.xml b/res/layout/mediapicker_image_chooser.xml deleted file mode 100644 index b4f3c01..0000000 --- a/res/layout/mediapicker_image_chooser.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout/mediapicker_location_chooser.xml b/res/layout/mediapicker_location_chooser.xml deleted file mode 100644 index 9135299..0000000 --- a/res/layout/mediapicker_location_chooser.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/res/layout/mediapicker_location_container.xml b/res/layout/mediapicker_location_container.xml deleted file mode 100644 index 6f476d3..0000000 --- a/res/layout/mediapicker_location_container.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - diff --git a/res/layout/mediapicker_tab_button.xml b/res/layout/mediapicker_tab_button.xml deleted file mode 100644 index 353c871..0000000 --- a/res/layout/mediapicker_tab_button.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/message_audio_attachment.xml b/res/layout/message_audio_attachment.xml deleted file mode 100644 index 0854269..0000000 --- a/res/layout/message_audio_attachment.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/res/layout/message_vcard_attachment.xml b/res/layout/message_vcard_attachment.xml deleted file mode 100644 index 0da6e2d..0000000 --- a/res/layout/message_vcard_attachment.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/res/layout/message_video_attachment.xml b/res/layout/message_video_attachment.xml deleted file mode 100644 index 8aa2061..0000000 --- a/res/layout/message_video_attachment.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/res/layout/mms_config_debug_fragment.xml b/res/layout/mms_config_debug_fragment.xml deleted file mode 100644 index 27528f9..0000000 --- a/res/layout/mms_config_debug_fragment.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/open_source_licenses.xml b/res/layout/open_source_licenses.xml deleted file mode 100644 index 89995eb..0000000 --- a/res/layout/open_source_licenses.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/res/layout/people_and_options_activity.xml b/res/layout/people_and_options_activity.xml deleted file mode 100644 index af879a0..0000000 --- a/res/layout/people_and_options_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/people_and_options_fragment.xml b/res/layout/people_and_options_fragment.xml deleted file mode 100644 index be61710..0000000 --- a/res/layout/people_and_options_fragment.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/people_and_options_section_header.xml b/res/layout/people_and_options_section_header.xml deleted file mode 100644 index 44b10c1..0000000 --- a/res/layout/people_and_options_section_header.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/layout/people_list_item_view.xml b/res/layout/people_list_item_view.xml deleted file mode 100644 index e4c07e4..0000000 --- a/res/layout/people_list_item_view.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/res/layout/people_options_item_view.xml b/res/layout/people_options_item_view.xml deleted file mode 100644 index fbb9592..0000000 --- a/res/layout/people_options_item_view.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/res/layout/permission_check_activity.xml b/res/layout/permission_check_activity.xml deleted file mode 100644 index 57f665c..0000000 --- a/res/layout/permission_check_activity.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/layout/person_item_view.xml b/res/layout/person_item_view.xml deleted file mode 100644 index 9645024..0000000 --- a/res/layout/person_item_view.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/layout/place_card.xml b/res/layout/place_card.xml deleted file mode 100644 index 93b9e8c..0000000 --- a/res/layout/place_card.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/settings_fragment.xml b/res/layout/settings_fragment.xml deleted file mode 100644 index b62b245..0000000 --- a/res/layout/settings_fragment.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/settings_item_view.xml b/res/layout/settings_item_view.xml deleted file mode 100644 index a434c11..0000000 --- a/res/layout/settings_item_view.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - diff --git a/res/layout/share_intent_activity.xml b/res/layout/share_intent_activity.xml deleted file mode 100644 index d1e59ff..0000000 --- a/res/layout/share_intent_activity.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/res/layout/share_intent_conversation_list_view.xml b/res/layout/share_intent_conversation_list_view.xml deleted file mode 100644 index 3da3c6b..0000000 --- a/res/layout/share_intent_conversation_list_view.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - diff --git a/res/layout/sim_selector_item_view.xml b/res/layout/sim_selector_item_view.xml deleted file mode 100644 index a20c4a9..0000000 --- a/res/layout/sim_selector_item_view.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/res/layout/sim_selector_view.xml b/res/layout/sim_selector_view.xml deleted file mode 100644 index 816a2cc..0000000 --- a/res/layout/sim_selector_view.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/res/layout/sms_free_storage_action_item_view.xml b/res/layout/sms_free_storage_action_item_view.xml deleted file mode 100644 index fd71904..0000000 --- a/res/layout/sms_free_storage_action_item_view.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/res/layout/sms_mms_dump_file_list_item.xml b/res/layout/sms_mms_dump_file_list_item.xml deleted file mode 100644 index a939db2..0000000 --- a/res/layout/sms_mms_dump_file_list_item.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/res/layout/sms_storage_low_warning_dialog.xml b/res/layout/sms_storage_low_warning_dialog.xml deleted file mode 100644 index 965cab6..0000000 --- a/res/layout/sms_storage_low_warning_dialog.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/res/layout/snack_bar.xml b/res/layout/snack_bar.xml deleted file mode 100644 index 1cdabc3..0000000 --- a/res/layout/snack_bar.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/test_activity.xml b/res/layout/test_activity.xml deleted file mode 100644 index a34b17a..0000000 --- a/res/layout/test_activity.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/res/layout/vcard_detail_activity.xml b/res/layout/vcard_detail_activity.xml deleted file mode 100644 index aa4c3b9..0000000 --- a/res/layout/vcard_detail_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/vcard_detail_fragment.xml b/res/layout/vcard_detail_fragment.xml deleted file mode 100644 index 8689df0..0000000 --- a/res/layout/vcard_detail_fragment.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout/video_thumbnail_view.xml b/res/layout/video_thumbnail_view.xml deleted file mode 100644 index 7c009cb..0000000 --- a/res/layout/video_thumbnail_view.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/viewgroup_vertical_explode_animation_popup.xml b/res/layout/viewgroup_vertical_explode_animation_popup.xml deleted file mode 100644 index 8a8aa60..0000000 --- a/res/layout/viewgroup_vertical_explode_animation_popup.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - diff --git a/res/layout/widget_conversation.xml b/res/layout/widget_conversation.xml deleted file mode 100644 index a9cda98..0000000 --- a/res/layout/widget_conversation.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/layout/widget_conversation_list.xml b/res/layout/widget_conversation_list.xml deleted file mode 100644 index 822fe55..0000000 --- a/res/layout/widget_conversation_list.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - diff --git a/res/layout/widget_conversation_list_item.xml b/res/layout/widget_conversation_list_item.xml deleted file mode 100644 index e0115a2..0000000 --- a/res/layout/widget_conversation_list_item.xml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/widget_loading.xml b/res/layout/widget_loading.xml deleted file mode 100644 index 8282b5d..0000000 --- a/res/layout/widget_loading.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/widget_message_item_incoming.xml b/res/layout/widget_message_item_incoming.xml deleted file mode 100644 index 3a1bc00..0000000 --- a/res/layout/widget_message_item_incoming.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/widget_message_item_outgoing.xml b/res/layout/widget_message_item_outgoing.xml deleted file mode 100644 index 472b5a2..0000000 --- a/res/layout/widget_message_item_outgoing.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/widget_missing_permission.xml b/res/layout/widget_missing_permission.xml deleted file mode 100644 index 712ad02..0000000 --- a/res/layout/widget_missing_permission.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - diff --git a/res/menu/archived_conversation_list_menu.xml b/res/menu/archived_conversation_list_menu.xml deleted file mode 100644 index a3a2a19..0000000 --- a/res/menu/archived_conversation_list_menu.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/res/menu/attachment_chooser_menu.xml b/res/menu/attachment_chooser_menu.xml deleted file mode 100644 index 99345c9..0000000 --- a/res/menu/attachment_chooser_menu.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/res/menu/compose_menu.xml b/res/menu/compose_menu.xml deleted file mode 100644 index a384f57..0000000 --- a/res/menu/compose_menu.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/menu/conversation_fragment_select_menu.xml b/res/menu/conversation_fragment_select_menu.xml deleted file mode 100644 index 7ce0829..0000000 --- a/res/menu/conversation_fragment_select_menu.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/res/menu/conversation_list_fragment_menu.xml b/res/menu/conversation_list_fragment_menu.xml deleted file mode 100644 index a83a2a5..0000000 --- a/res/menu/conversation_list_fragment_menu.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/res/menu/conversation_list_fragment_select_menu.xml b/res/menu/conversation_list_fragment_select_menu.xml deleted file mode 100644 index 8704691..0000000 --- a/res/menu/conversation_list_fragment_select_menu.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - diff --git a/res/menu/conversation_menu.xml b/res/menu/conversation_menu.xml deleted file mode 100644 index 57e561b..0000000 --- a/res/menu/conversation_menu.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/res/menu/gallery_picker_menu.xml b/res/menu/gallery_picker_menu.xml deleted file mode 100644 index 428f1e7..0000000 --- a/res/menu/gallery_picker_menu.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - diff --git a/res/menu/photo_view_menu.xml b/res/menu/photo_view_menu.xml deleted file mode 100644 index 10a4367..0000000 --- a/res/menu/photo_view_menu.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/res/menu/settings_menu.xml b/res/menu/settings_menu.xml deleted file mode 100644 index 3002be8..0000000 --- a/res/menu/settings_menu.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - diff --git a/res/menu/vcard_detail_fragment_menu.xml b/res/menu/vcard_detail_fragment_menu.xml deleted file mode 100644 index ea923cb..0000000 --- a/res/menu/vcard_detail_fragment_menu.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/res/raw/audio_end.wav b/res/raw/audio_end.wav deleted file mode 100644 index 01d08f5..0000000 Binary files a/res/raw/audio_end.wav and /dev/null differ diff --git a/res/raw/audio_initiate.wav b/res/raw/audio_initiate.wav deleted file mode 100644 index e335d0d..0000000 Binary files a/res/raw/audio_initiate.wav and /dev/null differ diff --git a/res/raw/db_op_debug.wav b/res/raw/db_op_debug.wav deleted file mode 100644 index b807636..0000000 Binary files a/res/raw/db_op_debug.wav and /dev/null differ diff --git a/res/raw/message_failure.wav b/res/raw/message_failure.wav deleted file mode 100644 index 270a0b7..0000000 Binary files a/res/raw/message_failure.wav and /dev/null differ diff --git a/res/raw/message_inc_thread.wav b/res/raw/message_inc_thread.wav deleted file mode 100644 index 7dce364..0000000 Binary files a/res/raw/message_inc_thread.wav and /dev/null differ diff --git a/res/raw/message_sent.wav b/res/raw/message_sent.wav deleted file mode 100644 index 1d45718..0000000 Binary files a/res/raw/message_sent.wav and /dev/null differ diff --git a/res/raw/server_request_debug.wav b/res/raw/server_request_debug.wav deleted file mode 100644 index 864a7dd..0000000 Binary files a/res/raw/server_request_debug.wav and /dev/null differ diff --git a/res/transition/explode.xml b/res/transition/explode.xml deleted file mode 100644 index a3a94ba..0000000 --- a/res/transition/explode.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/res/transition/fade.xml b/res/transition/fade.xml deleted file mode 100644 index e3617dc..0000000 --- a/res/transition/fade.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/res/values-af/arrays.xml b/res/values-af/arrays.xml deleted file mode 100644 index d780ddc..0000000 --- a/res/values-af/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "geen onderwerp nie" - "geen onderwerp nie" - - - "Ja" - "Nee" - "Goed" - "Hê-hê" - "Dankie" - "Ek stem saam" - "Lekker" - "Op pad" - "OK, laat ek jou later antwoord" - ":)" - ":(" - - diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml deleted file mode 100644 index ebe9d4d..0000000 --- a/res/values-af/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Boodskappe" - "Boodskappe" - "Kies gesprek" - "Instellings" - "Stuur boodskap" - "Voeg \'n aanhegsel by" - "Hulp" - "Welkom" - "Slaan oor" - "Volgende >" - "Volgende" - "Gaan uit" - "Instellings >" - "Instellings" - "Boodskappe het toestemming nodig vir SMS, foon en kontakte." - "Jy kan toestemmings verander in Instellings > Programme > Boodskappe > Toestemmings." - "Jy kan toestemmings verander in Instellings, Programme, Boodskappe, Toestemmings." - "Gereeldes" - "Alle kontakte" - "Stuur na %s" - "Neem foto\'s of video" - "Kies prente van hierdie toestel af" - "Neem oudio op" - "Kies foto" - "Die media is gekies." - "Die media is nie gekies nie." - "%d gekies" - "prent %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "prent" - "Neem oudio op" - "Deel" - "Nou net" - "Nou" - - %d min. - %d min. - - - %d uur - %d uur - - - %d dae - %d dag - - - %d weke - \'n week - - - %d maande - \'n maand - - - %d jaar - \'n jaar - - "Klas 0-boodskap" - "Stoor" - "Toestel het min spasie. Boodskappe sal ouer boodskappe outomaties uitvee om spasie beskikbaar te stel." - "Bergingspasie word min" - "Boodskappe sal dalk nie boodskappe stuur of ontvang voordat meer spasie op jou toestel beskikbaar is nie." - "Min SMS-berging. Jy sal dalk boodskappe moet uitvee." - "Bevestig jou foonnommer" - "Hierdie eenmalige stap sal sorg dat Boodskappe jou groepboodskappe toepaslik aflewer." - "Foonnommer" - "Vee alle boodskappe met media uit" - "Vee boodskappe ouer as %s uit" - "Vee boodskappe ouer as %s outomaties uit" - "Ignoreer" - "Vee alle boodskappe met media uit?" - "Vee boodskappe ouer as %s uit?" - "Vee boodskappe ouer as %s uit en skakel outomatiese uitvee aan?" - "%s het gesê" - "Jy het gesê" - "Boodskap van %s af" - "Jy het \'n boodskap gestuur" - "Stuur tans …" - "Nie gestuur nie. Raak om weer te probeer." - "Nie gestuur nie. Probeer tans weer …" - "Herstuur of vee uit" - "Maak asseblief \'n stemoproep na nooddienste. Jou SMS kon nie in hierdie stadium afgelewer word nie." - "Het misluk" - "Nuwe MMS-boodskap om af te laai" - "Nuwe MMS-boodskap" - "Kon nie aflaai nie" - "Raak om weer te probeer" - "Raak om af te laai" - "Laai af of vee uit" - "Laai tans af …" - "Boodskap het verval of is nie beskikbaar nie" - "grootte: %1$s, verval: %2$s" - "Kan nie stuur nie. Ontvanger nie geldig nie." - "Diens nie in netwerk geaktiveer nie" - "Kon weens netwerkprobleem nie stuur nie" - "Boodskap het verval of is nie beskikbaar nie" - "(Geen onderwerp nie)" - "Onbekende afsender" - "Afgelewer" - "Kon nie boodskap %1$s van %2$s aflaai nie." - "Kon nie databasishandeling voltooi nie weens lae geheue" - "Boodskap nie gestuur nie" - "Sommige boodskappe is nie in Boodskappe gestuur nie" - - %d boodskappe in %d gesprekke - %d boodskappe in een gesprek - - "Boodskap het nie afgelaai nie" - "Sommige boodskappe is nie in Boodskappe afgelaai nie" - - %d boodskappe in %d gesprekke - %d boodskappe in een gesprek - - "Boodskap aan %1$s nie gestuur nie" - "Maak asseblief \'n stemoproep na nooddienste. Jou SMS na %1$s kon nie in hierdie stadium afgelewer word nie." - - %d nuwe boodskappe - Nuwe boodskap - - "Begin" - "Kamera nie beskikbaar nie" - "Kamera nie beskikbaar nie" - "Video-opname nie beskikbaar nie" - "Kan nie media stoor nie" - "Kan nie foto neem nie" - "Terug" - "Geargiveer" - "Vee uit" - "Argiveer" - "Deargiveer" - "Skakel kennisgewings af" - "Skakel kennisgewings aan" - "Voeg kontak by" - "Laai af" - "Stuur" - "Vee uit" - "Vee hierdie boodskap uit?" - "Hierdie handeling kan nie ontdoen word nie." - "Vee uit" - - Vee hierdie gesprekke uit? - Vee hierdie gesprek uit? - - "Vee uit" - "Kanselleer" - "Aan" - "Kies verskeie prente" - "Bevestig keuse" - "+%d" - "Kan nie oudio opneem nie. Probeer weer." - "Kan nie oudio speel nie. Probeer weer." - "Kon nie oudio stoor nie. Probeer weer." - "Raak en hou" - ", " - " " - ": " - " " - "Prent" - "Oudiosnit" - "video" - "Kontakkaartjie" - "Laai af" - "Antwoord via SMS" - "Antwoord via MMS" - "Antwoord" - - %d deelnemers - %d deelnemer - - "Ek" - "Kontak geblokkeer en geargiveer" - "Kontak gedeblokkeer en gedeargiveer" - "%d geargiveer" - "%d gedeargiveer" - "Kennisgewings afgeskakel" - "Kennisgewings aangeskakel" - "Alles is gereed. Raak om weer te stuur." - "Boodskappe is suksesvol as die verstek-SMS-program gestel." - - Gooi aanhegsels weg - Gooi aanhegsel weg - - "Oudio-aanhegsel" - "Speel oudio-aanhegsel" - "Wag" - "Boodskap vanaf %s: %s." - "Mislukte boodskap van %s af: %s. Tyd: %s" - "Boodskap van %s af: %s. Tyd: %s" - "Ongestuurde boodskap aan %s: %s. Tyd: %s" - "Stuur tans boodskap aan %s: %s. Tyd: %s" - "Kon nie boodskap aan %s stuur nie: %s. Tyd: %s" - "Boodskap aan %s: %s. Tyd: %s" - "Mislukte boodskap van %s af: %s. Tyd: %s. %s" - "Boodskap van %s af: %s. Tyd: %s. %s" - "Ongestuurde boodskap aan %s: %s. Tyd: %s" - "Stuur tans boodskap aan %s: %s. Tyd: %s" - "Kon nie boodskap aan %s stuur nie: %s. Tyd: %s" - "Boodskap aan %s: %s. Tyd: %s" - "Kon nie boodskap stuur nie. Raak om weer te probeer." - "Gesprek met %s" - "Vee onderwerp uit" - "Neem video op" - "Vang \'n stilprent vas" - "Neem foto" - "Begin video opneem" - "Skakel na volskerm-kamera oor" - "Wissel tussen voorste en agterste kamera" - "Staak opname en heg video aan" - "Stop video-opname" - "Foto\'s uit Boodskappe" - - %d foto\'s is na \"%s\"-album gestoor - %d foto is na \"%s\"-album gestoor - - - %d video\'s is na \"%s\"-album gestoor - %d video is na \"%s\"-album gestoor - - - %d aanhegsels is na \"%s\"-album gestoor - %d aanhegsel is na \"%s\"-album gestoor - - - %d aanhegsels is na \"Aflaaie\" gestoor - %d aanhegsel is na \"Aflaaie\" gestoor - - - %d aanhegsels is gestoor - %d aanhegsel is gestoor - - - Kon %d aanhegsels nie stoor nie - Kon %d aanhegsel nie stoor nie - - "MMS-aanhegsel gestoor" - "Instellings" - "Geargiveer" - "Maak toe" - "MMS" - "Gevorderd" - "Ontfout" - "Kennisgewings" - "Klank" - "Stil" - "Vibreer" - "Geblokkeer" - "SMS-afleweringverslae" - "Versoek \'n afleweringsverslag vir elke SMS wat jy stuur" - "Haal outomaties op" - "Gaan haal MMS-boodskappe outomaties" - "Outoherwinning wanneer swerf" - "Laai MMS outomaties af terwyl geswerf word" - "Groepboodskappe" - "Gebruik MMS om \'n enkele boodskap te stuur wanneer daar veelvuldige ontvangers is" - "Verstek-SMS-program" - "Verstek-SMS-program" - - - "Jou foonnommer" - "Onbekend" - "Klanke vir uitgaande boodskappe" - "Gooi SMS weg" - "Stort rou data van ontvangde SMS in eksterne berginglêer" - "Gooi MMS weg" - "Stort rou data van ontvangde MMS in eksterne berginglêer" - "Draadlose waarskuwings" - "Boodskapopsies" - "Kopieer teks" - "Bekyk besonderhede" - "Vee uit" - "Stuur aan" - "Boodskapbesonderhede" - "Tipe: " - "Teksboodskap" - "Multimediaboodskap" - "Van: " - "Aan: " - "Gestuur: " - "Ontvang: " - "Onderwerp: " - "Grootte: " - "Prioriteit: " - "SIM: " - "Hoog" - "Normaal" - "Laag" - "SIM %s" - "Versteekte afsenderadres" - "Kan nie boodskap stuur terwyl aanhegsels gelaai word nie." - "Kan nie aanhegsel laai nie. Probeer weer." - "Netwerk is nie gereed nie. Probeer weer." - "Vee teks uit" - "Wissel tussen invoer van teks en syfers" - "Voeg meer deelnemers by" - "Bevestig deelnemers" - "Begin nuwe gesprek" - "Kies hierdie item" - "Speel video" - "Mense en opsies" - "Ontfout" - "Mense en opsies" - "Algemeen" - "Mense in hierdie gesprek" - "Maak \'n oproep" - "Stuur boodskap" - "Stuur boodskap<br/><small>van %s</small> af" - - Stuur foto\'s - Stuur foto - - - Stuur oudio\'s - Stuur oudio - - - Stuur video\'s - Stuur video - - - Stuur kontakkaarte - Stuur kontakkaart - - - Stuur aanhegsels - Stuur aanhegsel - - - %d aanhegsels is gereed om te stuur - Een aanhegsel is gereed om te stuur - - "Stuur terugvoer" - "Bekyk in Google Play Winkel" - "Weergawe-inligting" - "Weergawe %1$s" - "Oopbronlisensies" - "Kennisgewings" - "Aanhegsellimiet is bereik" - "Kon nie aanhegsel laai nie." - "Voeg by kontakte?" - "Voeg kontak by" - "Onderwerp" - "Onderwerp: " - "%s%s" - "Laai tans kontakkaart" - "Kon nie kontakkaart laai nie" - "Bekyk kontakkaart" - - %d kontakte - %d kontak - - "Kontakkaarte" - "Verjaardag" - "Notas" - "Aanstuurboodskap" - "Antwoord" - "+1" - "+%d" - "SMS gedeaktiveer" - "Stel Boodskappe as die verstek-SMS-program om te stuur" - "Stel Boodskappe as die verstek-SMS-program" - "Verander" - "Stel Boodskappe as die verstek-SMS-program om boodskappe te ontvang" - "Geen voorkeur-SIM vir die stuur van SMS-boodskappe gekies nie" - "Hierdie program word nie deur die toesteleienaar toegelaat nie." - "Goed" - "Te veel deelnemers aan \'n gesprek" - - Ongeldige kontakte - Ongeldige kontak - - "Kon nie kameraprent laai nie" - "Jy: " - "%s: " - "Konsep" - "Sodra jy \'n nuwe gesprek begin, sal jy dit hier gelys sien" - "Geargiveerde gesprekke verskyn hier" - "Laai tans gesprekke …" - "Prent" - "Oudiosnit" - "Video" - "Kontakkaartjie" - "MMS" - "Ontdoen" - "Herprobeer" - "Voer \'n kontaknaam of foonnommer in om \'n nuwe boodskap te begin" - "Blokkeer" - "Blokkeer %s" - "Deblokkeer %s" - "Blokkeer %s?" - "Jy sal steeds boodskappe van hierdie nommer af ontvang, maar sal nie meer in kennis gestel word nie. Hierdie gesprek sal geargiveer word." - "Geblokkeerde kontakte" - "DEBLOKKEER" - "Geblokkeerde kontakte" - "Kies prent uit dokumentbiblioteek" - "Stuur tans boodskap" - "Boodskap gestuur" - "Sellulêre data is afgeskakel. Gaan jou instellings na." - "Kan nie boodskappe in vliegtuigmodus stuur nie" - "Kon nie boodskap stuur nie" - "Boodskap afgelaai" - "Sellulêre data is afgeskakel. Gaan jou instellings na." - "Kan nie boodskappe in vliegtuigmodus aflaai nie" - "Boodskap kon nie afgelaai word nie" - "Nul" - "Een" - "Twee" - "Drie" - "Vier" - "Vyf" - "Ses" - "Sewe" - "Agt" - "Nege" - "Kan nie boodskap met %1$s stuur nie, fout %2$d" - "Kan nie boodskap met onbekende diensverskaffer stuur nie, fout %1$d" - "Fwd: %s" - "Boodskap nie gestuur nie: diens nie in netwerk geaktiveer nie" - "Boodskap nie gestuur nie: ongeldige bestemmingsadres" - "Boodskap nie gestuur nie: ongeldige boodskap" - "Boodskap nie gestuur nie: inhoud word nie gesteun nie" - "Boodskap nie gestuur nie: boodskap word nie gesteun nie" - "Boodskap nie gestuur nie; te groot" - "Nuwe boodskap" - "Bekyk" - "Prent" - "Kon nie \'n toepaslike program vind nie" - "Verwyder ontvanger" - "Nuwe boodskap" - "Kanselleer" - "Wysig toegangspunt" - "Nie gestel nie" - "Naam" - "APN" - "MMSC" - "MMS-volmag" - "MMS-poort" - "MCC" - "MNC" - "APN-tipe" - "Vee APN uit" - "Nuwe APN" - "Stoor" - "Gooi weg" - "Die naamveld kan nie leeg wees nie." - "Die APN kan nie leeg wees nie." - "MCC-veld moet 3 syfers wees." - "Die MNC-veld moet uit 2 of 3 syfers bestaan." - "Stel tans verstekinstellings vir APN terug." - "Stel terug na verstek" - "Terugstel van verstek-APN-instellings voltooi." - "Titelloos" - "APN\'e" - "APN\'e" - "Nuwe APN" - "APN-instellings is nie vir hierdie gebruiker beskikbaar nie" - "Kopieer na knipbord?" - "Kopieer" - "aan %s" - "Algemeen" - "Gevorderd" - "Algemene instellings" - "Gevorderde instellings" - "\"%s\"-SIM" - "Stuur individuele SMS-boodskappe aan alle ontvangers. Net jy sal enige antwoorde kry" - "Stuur \'n enkele MMS aan alle ontvangers" - "Onbekende nommer" - "Nuwe boodskap" - "Nuwe boodskap." - "SIM-selekteerder" - "%1$s gekies, SIM-selekteerder" - "Wysig onderwerp" - "Kies SIM of wysig onderwerp" - "Raak en hou om oudio op te neem" - "Begin nuwe gesprek" - "Boodskappe" - "Boodskappe-lys" - "Boodskappe" - "Nuwe boodskap" - "Gespreklys" - "Laai tans gesprekke" - "Laai tans boodskappe" - "Bekyk nog gesprekke" - "Bekyk nog boodskappe" - "Gesprek uitgevee" - "Gesprek is uitgevee. Raak om \'n ander Boodskappe-gesprek te wys" - "Geblokkeer" - "Gedeblokkeer" - "Daar is min bergingspasie. Sommige data kan verloor word." - "Kies aanhegsels" - "Bevestig keuse" - "%d gekies" - "Verwyder asseblief een of meer aanhegsels en probeer weer." - "Jy kan probeer om jou boodskap te stuur, maar dit sal dalk nie afgelewer word tensy jy een of meer aanhegsels verwyder nie." - "Jy kan net een video per boodskap stuur. Verwyder bykomende video\'s en probeer weer." - "Boodskappe kon nie aanhegsel laai nie." - "Stuur in elk geval" - "Kon nie gesprek begin nie" - "%1$s (%2$s)" - "%s gekies" - diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml deleted file mode 100644 index 225008e..0000000 --- a/res/values-am/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ርዕሰ-ጉዳይ የሌለው" - "ምንም ርእስ የለውም" - - - "አዎ" - "አይ" - "እሺ" - "ሃሃ" - "እናመሰግናለን" - "እስማማለሁ" - "በጣም ጥሩ" - "እየመጣሁ ነኝ" - "እሺ፣ በኋላ ላይ ላግኝዎት" - ":)" - ":(" - - diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml deleted file mode 100644 index 9707157..0000000 --- a/res/values-am/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "መልዕክት መላኪያ" - "መልዕክት መላኪያ" - "ውይይት ምረጥ" - "ቅንብሮች" - "መልዕክት ላከ" - "ዓባሪ አክል" - "እገዛ" - "እንኳን በደህና መጡ" - "ዝለል" - "ቀጣይ >" - "ቀጣይ" - "ውጣ" - "ቅንብሮች >" - "ቅንብሮች" - "አላላክ የኤስኤምኤስ፣ ስልክ እና እውቂያዎች ፍቃድ ያስፈልገዋል።" - "ፍቃዶችን በቅንብሮች > መተግበሪያዎች > አላላክ > ፍቃዶች ውስጥ መለወጥ ይችላሉ።" - "ፍቃዶችን በቅንብሮች፣ መተግበሪያዎች፣ አላላክ፣ ፍቃዶች ውስጥ መለወጥ ይችላሉ።" - "ተደጋጋሚዎች" - "ሁሉም ዕውቂያዎች" - "ወደ %s ላክ" - "ስዕሎችን አንሳ ወይም ቪዲዮ ቅረጽ" - "ከዚህ መሣሪያ ላይ ምስሎችን ይምረጡ" - "ተሰሚ ቅረጽ" - "ፎቶ ይምረጡ" - "ሚዲያው ተመርጧል።" - "ሚዲያው አልተመረጠም።" - "%d ተመርጠዋል" - "ምስል %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ምስል" - "ተሰሚ ቅረጽ" - "አጋራ" - "ገና አሁን" - "አሁን" - - %d ደቂቃዎች - %d ደቂቃዎች - - - %d ሰዓቶች - %d ሰዓቶች - - - %d ቀኖች - %d ቀኖች - - - %d ሳምንቶች - %d ሳምንቶች - - - %d ወሮች - %d ወሮች - - - %d ዓመቶች - %d ዓመቶች - - "የመደብ 0 መልዕክት" - "አስቀምጥ" - "መሣሪያው አነስተኛ ቦታ ነው ያለው። አላላክ ቦታን ለማስለቀቅ የቆዩ መልዕክቶችን በራስ ሰር ይሰርዛል።" - "የማከማቻ ቦታ እያለቀ ነው" - "መሣሪያዎ ላይ ተጨማሪ ቦታ እስኪገኝ ድረስ አላላክ መልዕክቶችን ላይልክ ወይም ላይቀበል ይችላል።" - "ዝቅተኛ የኤስኤምኤስ ማከማቻ። መልዕክቶችን መሰረዝ ሊኖርብዎ ይችላል።" - "ስልክ ቁጥርዎን ያረጋግጡ" - "ይሄ የአንድ ጊዜ እርምጃ አላላክ የቡድን መልዕክቶችዎን በአግባቡ እንደሚልክ ያረጋግጣል።" - "ስልክ ቁጥር" - "ማህደረመረጃ ያላቸው ሁሉንም መልዕክቶች ሰርዝ" - "ከ%s በላይ የቆዩ መልዕክቶችን ሰርዝ" - "ከ%s በላይ የቆዩ መልዕክቶችን በራስ-ሰር ሰርዝ" - "ችላ በል" - "ማህደረመረጃ ያላቸው ሁሉም መልዕክቶች ይሰረዙ?" - "ከ%s በላይ የቆዩ መልዕክቶች ይሰረዙ?" - "ከ%s በላይ የቆዩ መልዕክቶች ይሰረዙ እና ራስ-ሰር ሰራዥ ይብራ?" - "%s እንዲህ ብለዋል፦" - "እርስዎ እንዲህ ብለዋል፦" - "መልዕክት ከ%s" - "እርስዎ መልዕክት ልከዋል" - "በመላክ ላይ…" - "አልተላከም። እንደገና ለመሞከር ይንኩ።" - "አልተላከም። እንደገና በመሞከር ላይ…" - "ዳግም ላክ ወይም ሰርዝ" - "ወደ ድንገተኛ አገልግሎቶች እባክዎ የድምፅ ጥሪ ያድርጉ። የእርስዎ የጽሑፍ መልዕክት አሁን ላይ ሊደርስ አይችልም።" - "አልተሳካም" - "አዲስ የሚወርድ የኤምኤምኤስ መልዕክት" - "አዲስ የኤምኤምኤስ መልዕክት" - "ማውረድ አልተቻለም" - "እንደገና ለመሞከር ይንኩ" - "ለማውረድ ይንኩ" - "አውርድ ወይም ሰርዝ" - "በማውረድ ላይ…" - "መልዕክቱ ጊዜው አልፎበታል ወይም አይገኝም።" - "መጠን፦ %1$s፣ ጊዜው የሚያልፍበት፦ %2$s" - "መላክ አይቻልም። ተቀባይ ልክ አይደለም።" - "አገልግሎት በአውታረ መረብ ላይ አልገበረም" - "በአውታረ መረብ ችግር ምክንያት መላክ አልተቻለም" - "መልዕክቱ ጊዜው አልፎበታል ወይም አይገኝም።" - "(ርዕስ ጉዳይ የለውም)" - "ያልታወቀ ላኪ" - "ደርሷል" - "መልዕክት %1$s%2$s ማውረድ አልተቻለም።" - "በአነስተኛ ማህደረ ትውስታ ምክንያት የውሂብ ጎታ ክወና ማጠናቀቅ አልተቻለም" - "መልዕክት አልተላከም" - "አንዳንድ መልዕክቶች በአላላክ ውስጥ አልተላኩም" - - %d መልዕክቶች በ%d ውይይቶች ውስጥ - %d መልዕክቶች በ%d ውይይቶች ውስጥ - - "መልዕክት አልወረደም" - "አንዳንድ መልዕክቶች በአላላክ ውስጥ አልወረዱም" - - %d መልዕክቶች በ%d ውይይቶች ውስጥ - %d መልዕክቶች በ%d ውይይቶች ውስጥ - - "ወደ %1$s የሚሄድ መልዕክት አልተላከም" - "ወደ ድንገተኛ አገልግሎቶች እባክዎ የድምፅ ጥሪ ያድርጉ። የእርስዎ ወደ %1$s የጽሑፍ መልዕክት አሁን ላይ ሊደርስ አይችልም።" - - %d አዲስ መልዕክቶች - %d አዲስ መልዕክቶች - - "ጀምር" - "ካሜራ ሊገኝ አይችልም" - "ካሜራ ሊገኝ አይችልም" - "የቪዲዮ ቀረጻ ሊገኝ አይችልም" - "ማህደረ መረጃን ማስቀመጥ አይቻልም" - "ስዕል ማንሳት አልተቻለም" - "ተመለስ" - "በማህደር ተቀምጧል" - "ሰርዝ" - "በማህደር አስቀምጥ" - "ከማህደር አስወጣ" - "ማሳወቂያዎችን አጥፋ" - "ማሳወቂያዎችን አብራ" - "እውቂያ አክል" - "አውርድ" - "ላክ" - "ሰርዝ" - "ይህን መልዕክት ይሰረዝ?" - "ይህ እርምጃ ሊቀለበስ አይችልም።" - "ሰርዝ" - - እነዚህ ውይይቶች ይሰርዙ? - እነዚህ ውይይቶች ይሰርዙ? - - "ሰርዝ" - "ይቅር" - "ለ" - "በርካታ ምስሎችን ምረጥ" - "ምርጫ አረጋግጥ" - "+%d" - "ድምፅን መቅዳት አልተቻለም። እንደገና ይሞክሩ።" - "ድምፅን ማጫወት አልተቻለም። እንደገና ይሞክሩ።" - "ድምፅን ማስቀመጥ አልተቻለም። እንደገና ይሞክሩ።" - "ነካ ያድርጉ እና ይያዙ" - "፣ " - " " - "፦ " - " " - "ስዕል" - "የተሰሚ ቅንጥብ" - "ቪዲዮ" - "የእውቂያ ካርድ" - "አውርድ" - "በኤስኤምኤስ በኩል መልስ ስጥ" - "በኤምኤምኤስ በኩል ምላሽ ስጥ" - "ምላሽ ስጥ" - - %d ተሳታፊዎች - %d ተሳታፊዎች - - "እኔ" - "እውቂያ ታግዶ በማህደር ውስጥ ተቀምጧል" - "እውቂያ እገዳው ተነስቷል እና ከቤተማከማቻ ወጥቷል" - "%d በማህደር ተቀምጧል" - "%d ከማህደር ወጥተዋል" - "ማሳወቂያዎች ጠፍተዋል" - "ማሳወቂያዎች በርተዋል" - "ሁሉም ተቀናብሯል። እንደገና ላክ የሚለውን ይንኩ።" - "የአላላክ በተሳካ ሁኔታ እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ተዘጋጅቷል።" - - ዓባሪዎችን አስወግድ - ዓባሪዎችን አስወግድ - - "የኦዲዮ ዓባሪ" - "የኦዲዮ ዓባሪ ያጫውቱ" - "ለአፍታ አቁም" - "መልእክት ከ %s%s።" - "ያልተሳካ መልዕክት ከ%s%s። ሰዓት፦ %s።" - "ከ%s የመጣ መልዕክት፦ %s። ሰዓት፦ %s።" - "ወደ %s የሚሄድ ያልተላከ መልዕክት፦ %s። ሰዓት፦ %s።" - "መልዕክት ወደ %s በመላክ ላይ፦ %s። ሰዓት፦ %s።" - "ያልተሳካ ወደ %s የሚሄድ መልዕክት፦ %s። ሰዓት፦ %s።" - "ወደ %s የሚሄድ መልዕክት፦ %s። ሰዓት፦ %s።" - "ያልተሳካ መልዕክት ከ%s%s። ሰዓት፦ %s%s።" - "መልዕክት ከ%s%s። ሰዓት፦ %s%s።" - "ወደ %s የሚሄድ ያልተላከ መልዕክት፦ %s። ሰዓት፦ %s።" - "መልዕክት ወደ %s በመላክ ላይ፦ %s። ሰዓት፦ %s።" - "ያልተሳካ ወደ %s የሚሄድ መልዕክት፦ %s። ሰዓት፦ %s።" - "ወደ %s የሚሄድ መልዕክት፦ %s። ሰዓት፦ %s።" - "ያልተሳካ መልዕክት። ዳግም ለመሞከር ይንኩ።" - "ከ%s ጋር ውይይት" - "ርዕሰ ጉዳይ ሰርዝ" - "ቪዲዮ ቅረጽ" - "የቆመ ምስል አንሳ" - "ፎቶ አንሳ" - "ቪዲዮ መቅረጽ ጀምር" - "ወደ የሙሉ ማያ ገጽ ካሜራ ቀይር" - "በፊት እና ኋላ ካሜራ መካከል ቀያይር" - "መቅረጽ አቁም እና ቪዲዮ አያይዝ" - "ቪዲዮ መቅዳትን አቁም" - "ፎቶዎችን በመልዕክት መላክ" - - %d ፎቶዎች ወደ የ«%s» አልበም ተቀምጠዋል - %d ፎቶዎች ወደ የ«%s» አልበም ተቀምጠዋል - - - %d ቪዲዮዎች ወደ የ«%s» አልበም ተቀምጠዋል - %d ቪዲዮዎች ወደ የ«%s» አልበም ተቀምጠዋል - - - %d ዓባሪዎች ወደ «%s» አልበም ተቀምጠዋል - %d ዓባሪዎች ወደ «%s» አልበም ተቀምጠዋል - - - %d ዓባሪዎች ወደ «ውርዶች» ተቀምጠዋል - %d ዓባሪዎች ወደ «ውርዶች» ተቀምጠዋል - - - %d ዓባሪዎች ተቀምጠዋል - %d ዓባሪዎች ተቀምጠዋል - - - %d ዓባሪዎችን ማስቀመጥ አልተቻለም - %d ዓባሪዎችን ማስቀመጥ አልተቻለም - - "የኤምኤምኤስ ዓባሪ ተቀምጧል" - "ቅንብሮች" - "በማህደር ተቀምጧል" - "ዝጋ" - "ኤምኤምኤስ" - "የላቀ" - "አርም" - "ማሳወቂያዎች" - "ድምፅ" - "ፀጥታ" - "ንዘር" - "ታግዷል" - "የኤስኤምኤስ መላክ ሪፖርቶች" - "ለሚልኩት እያንዳንዱ ኤስኤምኤስ የመላኪያ ሪፖርት ጠይቅ" - "በራስ- ሰርስረህ አውጣ" - "የኤምኤምኤስ መልዕክቶችን በራስ-ሰር ሰርስረህ አውጣ" - "በእንቅስቃሴ ላይ በራስ-ሰር ሰርስረህ አውጣ" - "የስልክ አገልግሎት ሰጪ በሚቀየርበት ጊዜ ኤምኤምኤስን በራስ-ሰር ሰርስረህ አውጣ" - "የቡድን መልዕክት መላላኪያ" - "ብዙ ተቀባዮች ሲኖሩ አንዲት መልዕክት ለመላክ ኤምኤምኤስ ይጠቀሙ" - "ነባሪ የኤስኤምኤስ መተግበሪያ" - "ነባሪ የኤስኤምኤስ መተግበሪያ" - - - "ስልክ ቁጥርዎ" - "የማይታወቅ" - "የወጪ መልዕክት ድምጾች" - "ኤስኤምኤስ አራግፍ" - "የመጡ የኤስኤምኤስ ጥሬ ውሂብ ወደ ውጫዊ የማከማቻ ፋይል ላይ አራግፍ" - "ኤምኤምኤስ አራግፍ" - "የመጡ የኤምኤምኤስ ጥሬ ውሂብ ወደ ውጫዊ የማከማቻ ፋይል ላይ አራግፍ" - "ገመድ አልባ ማንቂያዎች" - "የመልዕክት አማራጮች" - "ጽሁፍ ገልብጥ" - "ዝርዝሮችን ይመልከቱ" - "ሰርዝ" - "ያስተላልፉ" - "የመልዕክት ዝርዝሮች" - "ዓይነት፦ " - "የፅሁፍ መልዕክት" - "ማህደረ ብዙ መረጃመልዕክት" - "ከ፦ " - "ለ፦ " - "የተላከው፦ " - "የደረሰው፦ " - "ርዕሰ ጉዳይ፦ " - "መጠን፦ " - "ቅድሚያ የሚሰጠው፦ " - "ሲም፦ " - "ከፍተኛ" - "መደበኛ" - "ዝቅ ያለ" - "ሲም %s" - "የተደበቀ የላኪ አድራሻ" - "አባሪዎችን በመጫን ላይ እያለ መልዕክት መላክ አይችልም።" - "ዓባሪን መጫን አልተቻለም። እንደገና ይሞክሩ።" - "አውታረ መረቡ ዝግጁ አይደለም። እንደገና ይሞክሩ።" - "ጽሑፍ ሰርዝ" - "ጽሑፍ እና ቁጥሮችን በማስገባት መካከል ይቀያይሩ" - "ተጨማሪ ተሳታፊዎችን ያክሉ" - "ተሳታፊዎችን ያረጋግጡ" - "አዲስ ውይይት ጀምር" - "ይህን ንጥል ምረጥ" - "ቪዲዮ አጫውት" - "ሰዎች እና አማራጮች" - "አርም" - "ሰዎች እና አማራጮች" - "አጠቃላይ" - "በዚህ ውይይት ውስጥ ያሉ ሰዎች" - "ደውል" - "መልዕክት ላክ" - "መልዕክት ይላኩ<br/><small>from %s</small>" - - ፎቶዎችን ይላኩ - ፎቶዎችን ይላኩ - - - ኦዲዮዎች ይላኩ - ኦዲዮዎች ይላኩ - - - ቪዲዮዎችን ይላኩ - ቪዲዮዎችን ይላኩ - - - የእውቂያ ካርዶችን ይላኩ - የእውቂያ ካርዶችን ይላኩ - - - ዓባሪዎችን ይላኩ - ዓባሪዎችን ይላኩ - - - %d ዓባሪዎችን ለመላክ የተዘጋጁ ዓባሪዎች - %d ዓባሪዎችን ለመላክ የተዘጋጁ ዓባሪዎች - - "ግብረመልስ ላክ" - "በGoogle Play መደብር ውስጥ ይመልከቱ" - "የስሪት መረጃ" - "ስሪት %1$s" - "የክፍት ምንጭ ፍቃዶች" - "ማሳወቂያዎች" - "የዓባሪ ገደብ ተደርሷል" - "ዓባሪን መጫን አልተሳካም።" - "ወደ እውቂያ ይታከል?" - "እውቅያ ያክሉ" - "ርዕሰ ጉዳይ" - "ርዕሰ ጉዳይ፦ " - "%s%s" - "የእውቂያ ካርድ በመጫን ላይ" - "የእውቂያ ካርድን መጫን አልተቻለም" - "የእውቂያ ካርድ ይመልከቱ" - - %d እውቂያዎች - %d እውቂያዎች - - "የአውቂያ ካርዶች" - "የልደት ቀን" - "ማስታወሻዎች" - "መልዕክት አስተላልፍ" - "ምላሽ ስጥ" - "+1" - "+%d" - "ኤስኤምኤስ ተሰናክሏል" - "ለመላክ፣ የአላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ያዘጋጁት" - "አላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ያዘጋጁት" - "ለውጥ" - "መልዕክቶችን ለመቀበል የአላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ አድርገው ያዘጋጁት" - "ኤስ ኤም ኤስ መልዕክቶችን ለመላክ ምንም ተመራጭ ሲም አልተመረጠም" - "ይህ መተግበሪያ በመሣሪያው ባለቤት አይፈቀድም።" - "እሺ" - "በአንድ ውይይት ውስጥ በጣም ብዙ ተሳታፊዎች" - - ልክ ያልሆኑ እውቂያዎች - ልክ ያልሆኑ እውቂያዎች - - "የካሜራ ምስልን መጫን አልተቻለም" - "እርስዎ፦ " - "%s፦ " - "ረቂቅ" - "አንዴ አዲስ ውይይት ከጀመሩ በኋላ እዚህ ተዘርዝሮ ያዩታል" - "በቤተመዛግብት እንዲቀመጡ የተደረጉ ውይይቶች እዚህ ላይ ብቅ ያላሉ" - "ውይይቶች በመስቀል ላይ..." - "ስዕል" - "የተሰሚ ቅንጥብ" - "ቪዲዮ" - "የእውቂያ ካርድ" - "ኤምኤምኤስ" - "ቀልብስ" - "እንደገና ይሞክሩ" - "አዲስ መልዕክት ለመጀመር የእውቂያ ስም ወይም ስልክ ቁጥር ያስገቡ" - "አግድ" - "%sን አግድ" - "የ%s እገዳን አንሳ" - "%s ይታገድ?" - "ከዚህ ቁጥር መልዕክቶችን መቀበልዎን ይቀጥላሉ ነገርግን ከእንግዲህ እንዲያውቁት አይደረጉም። ይህ ውይይት በቤተመዛግብት ይቀመጣል።" - "የታገዱ እውቂያዎች" - "እገዳ አንሳ" - "የታገዱ እውቂያዎች" - "ከሰነዶች ቤተመዛግብት ምስል ይምረጡ" - "መልዕክት በመላክ ላይ" - "መልዕክት ተልኳል" - "የተንቀሳቃሽ ስልክ ውሂብ ጠፍቷል። ቅንብሮችዎን ያረጋግጡ።" - "በአይሮፕላን ሁኔታ ላይ እያለ መልዕክቶችን መላክ አይቻልም" - "መልዕክቱ ሊላክ አልቻለም" - "መልዕክት ወርዷል" - "የተንቀሳቃሽ ስልክ ውሂብ ጠፍቷል። ቅንብሮችዎን ያረጋግጡ።" - "መልዕክቶችን በአውሮፕላን ሁነታ ላይ ማውረድ አይቻልም" - "መልዕክት ሊወርድ አልቻለም" - "ዜሮ" - "አንድ" - "ሁለት" - "ሶስት" - "አራት" - "አምስት" - "ስድስት" - "ሰባት" - "ስምንት" - "ዘጠኝ" - "መልዕክት በ%1$s መላክ አልተቻለም፣ ስህተት %2$d" - "መልዕክት ባልታወቀ አገልግሎት አቅራቢ መላክ አልተቻለም፣ ስህተት %1$d" - "የተላለፈ፦ %s" - "መልዕክት አልተላከም፦ አገልግሎቱ በአውታረ መረብ ላይ አልገበረም።" - "መልዕክት አልተላከም፦ ልክ ያልሆነ የመድረሻ አድራሻ" - "መልዕክት አልተላከም፦ ልክ ያልሆነ መልዕክት" - "መልዕክት አልተላከም፦ የማይደገፍ ይዘት" - "መልዕክት አልተላከም፦ የማይደገፍ መልዕክት" - "መልዕክት አልተላከም፦ በጣም ትልቅ ነው" - "አዲስ መልዕክት" - "ይመልከቱ" - "ምስል" - "አግባብ የሆነውን መተግበሪያ ማግኘት አልተቻለም" - "ተቀባይ አስወግድ" - "አዲስ መልዕክት" - "ተወው" - "የመድረሻ ነጥብ ያርትዑ" - "አልተዘጋጀም" - "ስም" - "ኤ.ፒ.ኤን." - "MMSC" - "የMMS እጅ አዙር" - "የMMS ወደብ" - "MCC" - "MNC" - "የኤ.ፒ.ኤን አይነት" - "ኤ.ፒ.ኤን. ሰርዝ" - "አዲስ ኤ.ፒ.ኤን." - "አስቀምጥ" - "ጣለው" - "የስም መስክ ባዶ ሊሆን አይችልም" - "ኤ.ፒ.ኤን. ባዶ መሆን አይችልም።" - "የMCC መስክ 3 አሀዝ መሆን አለበት።" - "የMNC መስክ 2 ወይም 3 አሀዝ መሆን አለበት።" - "ነባሪ የኤ.ፒ.ኤን. ቅንብሮችን እነበረበት መመለስ።" - "ወደ ነባሪ ዳግም አስጀምር" - "የዳግም አስጀምር ነባሪ ኤ.ፒ.ኤን. ቅንብሮች ተጠናቀዋል።" - "ርዕስ አልባ" - "የመዳረሻ ነጥብ ስም" - "ኤ.ፒ.ኤኖች." - "አዲስ ኤ.ፒ.ኤን." - "የመዳረሻ ነጥብ ስም ቅንብሮች ለዚህ ተጠቃሚ አይገኙም" - "ወደ የቅንጥብ ሰሌዳ ይቀዳ?" - "ቅዳ" - "ለ%s" - "አጠቃላይ" - "የላቀ" - "አጠቃላይ ቅንብሮች" - "የላቁ ቅንብሮች" - "የ«%s» ሲም" - "የነጠላ ኤስኤምኤስ መልዕክቶችን ለሁሉም ተቀባዮች ይላኩ። እርስዎ ብቻ ነዎት ማንኛቸውም ምላሾችን የሚቀበሉት" - "አንዲት ኤምኤምኤስ ለሁሉም ተቀባዮች ይላኩ" - "ያልታወቀ ቁጥር" - "አዲስ መልዕክት" - "አዲስ መልዕክት" - "ሲም መራጭ" - "%1$s ተመርጧል፣ የሲም መራጭ" - "ርዕሰ ጉዳይ ያርትዑ" - "ሲም ይምረጡ ወይም ርዕሰ ጉዳይን ያርትዑ" - "ተሰሚን ለመቅረጽ ይንኩና ይያዙ" - "አዲስ ውይይት ጀምር" - "አላላክ" - "የአላላክ ዝርዝር" - "አላላክ" - "አዲስ መልዕክት" - "የውይይት ዝርዝር" - "ውይይቶችን በመጫን ላይ" - "መልዕክቶች በመስቀል ላይ" - "ተጨማሪ ውይይቶችን ይመልከቱ" - "ተጨማሪ መልዕክቶችን አሳይ" - "ውይይት ተሰርዟል" - "ውይይት ተሰርዟል። የተለየ የአላላክ ውይይት ለማሳየት ይንኩ" - "ታግዷል" - "አልታገደም" - "የማከማቻ ቦታ ዝቅተኛ ነው። አንዳንድ ውሂብ ሊጠፋ ይችላል።" - "ዓባሪዎችን ይምረጡ" - "ምርጫ አረጋግጥ" - "%d ተመርጠዋል" - "እባክዎ አንድ ወይም ተጨማሪ ዓባሪዎችን ያስወግዱና እንደገና ይሞክሩ።" - "መልዕክትዎን ለመላክ መሞከር ይችላሉ፣ ነገር ግን አንድ ወይም ተጨማሪ ዓባሪዎችን እስኪያስወግዱ ድረስ ላይደርስ ይችላል።" - "በአንድ መልዕክት አንድ ቪዲዮ ብቻ ለመላክ ይችላሉ። እባክዎ ተጨማሪ ቪዲዮዎችን ያስወግዱ እና እንደገና ይሞክሩ።" - "አላላክ አባሪን መጫን አልቻለም።" - "ለማንኛውም ላክ" - "ውይይት መጀመር አልተቻለም" - "%1$s (%2$s)" - "%s ተመርጧል" - diff --git a/res/values-ar/arrays.xml b/res/values-ar/arrays.xml deleted file mode 100644 index 0cf1702..0000000 --- a/res/values-ar/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "بدون موضوع" - "بدون موضوع" - - - "نعم" - "لا" - "موافق" - "هاها" - "شكرًا" - "أوافق" - "لطيف" - "في الطريق" - "حسنًا، سأتناول ذلك لاحقًا" - ":)" - ":(" - - diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml deleted file mode 100644 index 8a58c4d..0000000 --- a/res/values-ar/strings.xml +++ /dev/null @@ -1,623 +0,0 @@ - - - - - "المراسلة" - "المراسلة" - "تحديد محادثة" - "الإعدادات" - "إرسال رسالة" - "إضافة مرفق" - "مساعدة" - "مرحبًا" - "تخطٍ" - "‏التالي >" - "التالي" - "خروج" - "‏الإعدادات >" - "الإعدادات" - "‏يحتاج تطبيق المراسلة إلى إذن للوصول إلى الرسائل القصيرة SMS والهاتف وجهات الاتصال." - "‏يمكنك تغيير الأذونات من خلال الإعدادات > التطبيقات > المراسلة > الأذونات." - "يمكنك تغيير الأذونات من خلال الانتقال إلى الإعدادات، التطبيقات، المراسلة، الأذونات." - "من يتم الاتصال بهم بشكل متكرر" - "كل جهات الاتصال" - "إرسال إلى %s" - "التقاط الصور أو الفيديو" - "اختيار صور من هذا الجهاز" - "تسجيل الصوت" - "اختيار صورة" - "تم تحديد الوسائط." - "تم إلغاء تحديد الوسائط." - "تم تحديد %d" - "صورة %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "صورة" - "تسجيل الصوت" - "المشاركة" - "للتو" - "الآن" - - %d دقيقة - دقيقتان (%d) - %d دقائق - %d دقيقة - %d من الدقائق - %d دقيقة - - - %d ساعة - ساعتان (%d) - %d ساعات - %d ساعة - %d من الساعات - %d ساعة - - - %d يوم - يومان (%d) - %d أيام - %d يومًا - %d من الأيام - يوم واحد (%d) - - - %d أسبوع - أسبوعان (%d) - %d أسابيع - %d أسبوعًا - %d من الأسابيع - أسبوع - - - %d شهر - شهران (%d) - %d أشهر - %d شهرًا - %d من الأشهر - شهر - - - %d عام - عامان (%d) - %d أعوام - %d عامًا - %d من الأعوام - عام - - "رسالة من الفئة 0" - "حفظ" - "مساحة الجهاز منخفضة. سيحذف تطبيق المراسلة الرسائل القديمة تلقائيًا لتحرير مساحة." - "مساحة التخزين منخفضة" - "ربما لا يرسل تطبيق المراسلة رسائل أو يتلقاها ما لم تتوفر المزيد من المساحة على جهازك." - "‏سعة تخزين الرسائل القصيرة SMS منخفضة. ربما تحتاج إلى حذف بعض الرسائل." - "تأكيد رقم الهاتف" - "ستضمن هذه الخطوة - التي يتم تنفيذها مرة واحدة فقط - تسليم تطبيق المراسلة لرسائلك الجماعية بشكل صحيح." - "رقم الهاتف" - "حذف جميع الرسائل التي تتضمن وسائط" - "حذف الرسائل الأقدم من %s" - "حذف الرسائل الأقدم من %s تلقائيًا" - "تجاهل" - "هل تريد حذف كل الرسائل التي تتضمن ملفات وسائط؟" - "هل تريد حذف الرسائل الأقدم من %s؟" - "هل تريد حذف الرسائل الأقدم من %s وتشغيل الحذف التلقائي؟" - "قال %s" - "أنت قلت" - "رسالة من %s:" - "لقد أرسلت رسالة" - "جارٍ الإرسال..." - "لم يتم إرسال الرسالة. المس لإعادة المحاولة." - "لم يتم إرسال الرسالة. جارٍ إعادة المحاولة…" - "إعادة إرسال أو حذف" - "الرجاء إجراء مكالمة صوتية لخدمات الطوارئ. يتعذر توصيل رسالتك النصية في الوقت الحالي." - "أخفق الإرسال" - "هناك رسالة وسائط متعددة جديدة للتنزيل" - "رسالة وسائط متعددة جديدة" - "تعذر التنزيل" - "المس لإعادة المحاولة" - "المس للتنزيل" - "تنزيل أو حذف" - "جارٍ التنزيل…" - "انتهت صلاحية الرسالة أو ليست متاحة" - "الحجم: %1$s، تاريخ انتهاء الصلاحية: %2$s" - "يتعذر الإرسال. المستلم غير صالح." - "لم يتم تنشيط الخدمة على الشبكة" - "تعذر الإرسال بسبب حدوث مشكلة في الشبكة" - "انتهت صلاحية الرسالة أو ليست متاحة" - "(بلا موضوع)" - "مرسل غير معروف" - "تم التسليم" - "تعذر تنزيل الرسالة %1$s من %2$s." - "تعذر إتمام تشغيل قاعدة البيانات نظرًا لانخفاض الذاكرة" - "لم يتم إرسال الرسالة" - "لم يتم إرسال بعض الرسائل في تطبيق المراسلة" - - %d من الرسائل في %d محادثة - %d من الرسائل في محادثتين (%d) - %d من الرسائل في %d محادثات - %d من الرسائل في %d محادثة - %d من الرسائل في %d من المحادثات - %d من الرسائل في محادثة واحدة - - "لم يتم تنزيل الرسالة" - "لم يتم تنزيل بعض الرسائل في تطبيق المراسلة" - - %d من الرسائل في %d محادثة - %d من الرسائل في محادثتين (%d) - %d من الرسائل في %d محادثات - %d من الرسائل في %d محادثة - %d من الرسائل في %d من المحادثات - %d من الرسائل في محادثة واحدة - - "لم يتم إرسال رسالة إلى %1$s" - "الرجاء إجراء مكالمة صوتية لخدمات الطوارئ. يتعذر إرسال رسالتك النصية إلى %1$s في الوقت الحالي." - - ليست هناك أية رسالة جديدة (%d) - رسالتان (%d) جديدتان - %d رسائل جديدة - %d رسالة جديدة - %d من الرسائل الجديدة - رسالة جديدة - - "بدء" - "لا تتوفر كاميرا" - "لا تتوفر كاميرا" - "ميزة التقاط الفيديو غير متاحة" - "يتعذر حفظ ملف الوسائط" - "لا يمكن التقاط الصورة" - "رجوع" - "المؤرشفة" - "حذف" - "أرشيف" - "إزالة من الأرشيف" - "إيقاف الإشعارات" - "تشغيل الإشعارات" - "إضافة جهة اتصال" - "تنزيل" - "إرسال" - "حذف" - "هل تريد حذف هذه الرسالة؟" - "لا يمكن التراجع عن هذا الإجراء." - "حذف" - - ليست هناك محادثات. - هل تريد حذف هاتين المحادثتين؟ - هل تريد حذف هذه المحادثات؟ - هل تريد حذف هذه المحادثات؟ - هل تريد حذف هذه المحادثات؟ - هل تريد حذف هذه الدردشة؟ - - "حذف" - "إلغاء" - "إلى" - "تحديد صور متعددة" - "تأكيد الاختيار" - "+%d" - "يتعذر تسجيل الصوت. أعد المحاولة." - "يتعذر تشغيل الصوت. أعد المحاولة." - "تعذر حفظ الصوت. أعد المحاولة." - "اللمس مع الاستمرار" - "، " - " " - ": " - " " - "الصورة" - "مقطع صوتي" - "الفيديو" - "بطاقة جهة الاتصال" - "تنزيل" - "‏الرد عبر رسالة قصيرة SMS" - "الرد عبر رسالة وسائط متعددة" - "الرد" - - ليس هناك أي مشارك (%d) - مشاركان (%d) - %d مشاركين - %d مشاركًا - %d من المشاركين - مشارك واحد (%d) - - "أنا" - "تم حظر جهة الاتصال وأرشفتها" - "تم إلغاء حظر جهة الاتصال وإلغاء أرشفتها" - "تم وضع %d في الأرشيف" - "تمت إزالة %d من الأرشيف" - "تم تعطيل الإشعارات" - "تم تشغيل الإشعارات" - "اكتمل الإعداد. المس \"إرسال\" مرة أخرى." - "‏تم تعيين تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي بنجاح." - - ليست هناك مرفقات - تجاهل المرفقين - تجاهل المرفقات - تجاهل المرفقات - تجاهل المرفقات - تجاهل المرفق - - "مرفق صوتي" - "تشغيل المرفق الصوتي" - "إيقاف مؤقت" - "رسالة من %s: %s." - "رسالة تعذر إرسالها من %s: %s. الوقت: %s." - "رسالة من %s: %s. الوقت: %s." - "رسالة لم يتم إرسالها إلى %s: %s. الوقت: %s." - "جارٍ إرسال رسالة إلى %s: %s. الوقت: %s." - "رسالة تعذر إرسالها إلى %s: %s. الوقت: %s." - "رسالة إلى %s: %s. الوقت: %s." - "رسالة تعذر إرسالها من %s: %s. الوقت: %s. %s." - "رسالة من %s: %s. الوقت: %s. %s." - "رسالة لم يتم إرسالها إلى %s: %s. الوقت: %s." - "جارٍ إرسال رسالة إلى %s: %s. الوقت: %s." - "رسالة تعذر إرسالها إلى %s: %s. الوقت: %s." - "رسالة إلى %s: %s. الوقت: %s." - "رسالة تعذر إرسالها. المس لإعادة المحاولة." - "محادثة مع %s" - "حذف الموضوع" - "التقاط فيديو" - "التقاط صورة ثابتة" - "التقاط صورة" - "بدء تسجيل الفيديو" - "التبديل إلى الكاميرا بملء الشاشة" - "التبديل بين الكاميرا الأمامية والخلفية" - "وقف التسجيل وإرفاق مقطع الفيديو" - "إيقاف تسجيل الفيديو" - "صور المراسلة" - - لم يتم حفظ أية صورة (%d) في الألبوم \"%s\" - تم حفظ صورتين (%d) في الألبوم \"%s\" - تم حفظ %d صور في الألبوم \"%s\" - تم حفظ %d صورة في الألبوم \"%s\" - تم حفظ %d من الصور في الألبوم \"%s\" - تم حفظ صورة واحدة (%d) في الألبوم \"%s\" - - - لم يتم حفظ أي مقطع فيديو (%d) في الألبوم \"%s\" - تم حفظ مقطعي فيديو (%d) في الألبوم \"%s\" - تم حفظ %d مقاطع فيديو في الألبوم \"%s\" - تم حفظ %d مقطع فيديو في الألبوم \"%s\" - تم حفظ %d من مقاطع الفيديو في الألبوم \"%s\" - تم حفظ مقطع فيديو واحد (%d) في الألبوم \"%s\" - - - لم يتم حفظ أي مرفق (%d) في ألبوم \"%s\" - تم حفظ مرفقين (%d) في ألبوم \"%s\" - تم حفظ %d مرفقات في ألبوم \"%s\" - تم حفظ %d مرفقًا في ألبوم \"%s\" - تم حفظ %d من المرفقات في ألبوم \"%s\" - تم حفظ مرفق %d في ألبوم \"%s\" - - - لم يتم حفظ أي مرفق (%d) في \"التنزيلات\" - تم حفظ مرفقين (%d) في \"التنزيلات\" - تم حفظ %d مرفقات في \"التنزيلات\" - تم حفظ %d مرفقًا في \"التنزيلات\" - تم حفظ %d من المرفقات في \"التنزيلات\" - تم حفظ مرفق واحد (%d) في \"التنزيلات\" - - - لم يتم حفظ أية مرفقات (%d) - تم حفظ مرفقين (%d) - تم حفظ %d مرفقات - تم حفظ %d مرفقًا - تم حفظ %d من المرفقات - تم حفظ مرفق واحد %d - - - لم يتعذر حفظ أية مرفقات (%d) - تعذر حفظ مرفقين (%d) - تعذر حفظ %d مرفقات - تعذر حفظ %d مرفقًا - تعذر حفظ %d من المرفقات - تعذر حفظ مرفق واحد (%d) - - "تم حفظ مرفق رسالة الوسائط المتعددة" - "الإعدادات" - "المؤرشفة" - "إغلاق" - "رسالة وسائط متعددة" - "إعدادات متقدمة" - "تصحيح الأخطاء" - "الإشعارات" - "الصوت" - "صامت" - "اهتزاز" - "محظور" - "‏تقارير تسليم الرسائل القصيرة SMS" - "‏طلب تقرير تسليم لكل رسالة قصيرة SMS ترسلها" - "استرداد تلقائي" - "استرداد رسائل الوسائط المتعددة تلقائيًا" - "استرداد تلقائي للتجوال" - "استرداد رسائل الوسائط المتعددة عند التجوال تلقائيًا" - "المراسلة الجماعية" - "يمكنك استخدام رسائل الوسائط المتعددة لإرسال رسالة واحدة عندما يكون هناك عدة مستلمين" - "‏تطبيق الرسائل القصيرة SMS الافتراضي" - "‏تطبيق الرسائل القصيرة SMS الافتراضي" - - - "رقم هاتفك" - "غير معروف" - "أصوات الرسائل الصادرة" - "‏تفريغ الرسائل القصيرة SMS" - "‏تفريغ البيانات الأولية للرسائل القصيرة SMS في ملف تخزين خارجي" - "تفريغ رسائل الوسائط المتعددة" - "تفريغ البيانات الأولية لرسائل الوسائط المتعددة في ملف تخزين خارجي" - "تنبيهات لاسلكية" - "خيارات الرسالة" - "نسخ النص" - "عرض التفاصيل" - "حذف" - "إعادة توجيه" - "تفاصيل الرسالة" - "النوع: " - "رسالة نصية" - "رسالة وسائط متعددة" - "من: " - "إلى: " - "تاريخ الإرسال: " - "تاريخ التلقي: " - "الموضوع: " - "الحجم: " - "الأولوية: " - "SIM: " - "عالية" - "عادية" - "منخفضة" - "SIM %s" - "عنوان المرسل المخفي" - "يتعذر إرسال رسالة أثناء تحميل المرفقات." - "يتعذر تحميل المرفق. أعد المحاولة." - "الشبكة ليست جاهزة بعد. يمكنك إعادة المحاولة." - "حذف النص" - "التبديل بين إدخال النص والأرقام" - "إضافة مزيد من المشاركين" - "تأكيد المشاركين" - "بدء محادثة جديدة" - "تحديد هذا العنصر" - "تشغيل الفيديو" - "الأشخاص والخيارات" - "تصحيح الأخطاء" - "الأشخاص والخيارات" - "عام" - "الأشخاص في هذه المحادثة" - "إجراء مكالمة" - "إرسال رسالة" - "‏إرسال رسالة<br/><small>من %s</small>" - - ليست هناك صور لإرسالها - إرسال الصورتين - إرسال الصور - إرسال الصور - إرسال الصور - إرسال الصورة - - - ليست هناك ملفات صوت لإرسالها - إرسال ملفيّ الصوت - إرسال ملفات الصوت - إرسال ملفات الصوت - إرسال ملفات الصوت - إرسال ملف الصوت - - - ليست هناك ملفات فيديو لإرسالها - إرسال ملفي الفيديو - إرسال ملفات الفيديو - إرسال ملفات الفيديو - إرسال ملفات الفيديو - إرسال ملف الفيديو - - - ليست هناك بطاقات جهة اتصال لإرسالها - إرسال بطاقتي جهة الاتصال - إرسال بطاقات جهة الاتصال - إرسال بطاقات جهة الاتصال - إرسال بطاقات جهة الاتصال - إرسال بطاقة جهة الاتصال - - - ليست هناك مرفقات لإرسالها - إرسال المرفقين - إرسال المرفقات - إرسال المرفقات - إرسال المرفقات - إرسال المرفق - - - لم يتم تحديد أي مرفقات - مرفقان (%d) جاهزان للإرسال - %d مرفقات جاهزة للإرسال - %d مرفقًا جاهزًا للإرسال - %d من المرفقات الجاهزة للإرسال - مرفق واحد جاهز للإرسال - - "إرسال تعليقات" - "‏عرض في متجر Google Play" - "معلومات الإصدار" - "‏الإصدار %1$s" - "تراخيص برامج مفتوحة مصدر" - "الإشعارات" - "تم بلوغ حد المرفقات" - "أخفق تحميل المرفق." - "إضافة إلى جهات الاتصال؟" - "إضافة جهة اتصال" - "الموضوع" - "الموضوع: " - "%s%s" - "جارٍ تحميل بطاقة جهة الاتصال" - "تعذر تحميل بطاقة جهة الاتصال" - "عرض بطاقة جهة الاتصال" - - ليست هناك أية جهة اتصال (%d) - جهتا اتصال (%d) - %d جهات اتصال - %d جهة اتصال - %d من جهات الاتصال - جهة اتصال واحدة (%d) - - "بطاقات جهات الاتصال" - "تاريخ الميلاد" - "ملاحظات" - "إعادة توجيه الرسالة" - "رد" - "1+" - "+%d" - "‏تم تعطيل الرسائل القصيرة SMS" - "‏للإرسال، عيّن تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي" - "‏يجب تعيين تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي" - "تغيير" - "‏لاستلام رسائل، عيّن تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي" - "‏لم يتم تحديد شريحة SIM المفضلة لإرسال رسائل قصيرة SMS" - "لم يتم السماح لهذا التطبيق بواسطة مالك الجهاز." - "موافق" - "هناك عدد كبير جدًا من المشاركين في محادثة" - - ليست هناك جهات اتصال غير صالحة - جهتا اتصال غير صالحتين - جهات اتصال غير صالحة - جهات اتصال غير صالحة - جهات اتصال غير صالحة - جهة اتصال غير صالحة - - "يتعذر تحميل صورة الكاميرا" - "أنت: " - "%s: " - "مسودة" - "بعد بدء دردشة جديدة، ستظهر مدرجة هنا" - "الإشعارات المؤرشفة تظهر هنا" - "جارٍ تحميل المحادثات..." - "صورة" - "مقطع صوتي" - "فيديو" - "بطاقة جهة الاتصال" - "رسالة وسائط متعددة" - "تراجع" - "إعادة المحاولة" - "أدخل اسم جهة اتصال أو رقم هاتف لبدء رسالة جديدة" - "حظر" - "حظر %s" - "إلغاء حظر %s" - "هل تريد حظر %s؟" - "ستستمر في استلام الرسائل من هذا الرقم ولكن لن يتم إرسال إشعارات إليك مرة أخرى. ستتم أرشفة هذه المحادثة." - "جهات الاتصال المحظورة" - "إلغاء الحظر" - "جهات الاتصال المحظورة" - "اختيار صورة من مكتبة المستندات" - "جارٍ إرسال الرسالة" - "تم إرسال الرسالة" - "تم إيقاف اتصال بيانات الجوّال. تحقق من إعداداتك." - "يتعذر إرسال رسائل في وضع الطائرة" - "تعذر إرسال الرسالة" - "تم تنزيل الرسالة" - "تم إيقاف اتصال بيانات الجوّال. تحقق من إعداداتك." - "يتعذر تنزيل الرسائل في وضع الطائرة" - "تعذر تنزيل الرسالة" - "صفر" - "واحد" - "اثنان" - "ثلاثة" - "أربعة" - "خمسة" - "ستة" - "سبعة" - "ثمانية" - "تسعة" - "يتعذر إرسال الرسالة مع %1$s، الخطأ %2$d" - "يتعذر إرسال الرسالة مع مشغِّل شبكة جوّال غير معروف، الخطأ %1$d" - "إعادة التوجيه: %s" - "لم يتم إرسال الرسالة: لم يتم تنشيط الخدمة على الشبكة" - "لم يتم إرسال الرسالة: عنوان الوجهة غير صالح" - "لم يتم إرسال الرسالة: رسالة غير صالحة" - "لم يتم إرسال الرسالة: محتوى غير متوافق" - "لم يتم إرسال الرسالة: رسالة غير متوافقة" - "لم يتم إرسال الرسالة: الحجم كبير جدًا" - "رسالة جديدة" - "عرض" - "صورة" - "تعذر العثور على تطبيق مناسب" - "إزالة المستلم" - "رسالة جديدة" - "إلغاء" - "تعديل نقطة الدخول" - "لم يتم التعيين" - "الاسم" - "APN" - "MMSC" - "الخادم الوكيل لرسائل الوسائط المتعددة" - "منفذ رسائل الوسائط المتعددة" - "MCC" - "MNC" - "‏نوع APN" - "‏حذف APN" - "‏APN جديد" - "حفظ" - "تجاهل" - "لا يمكن أن يكون حقل الاسم فارغًا." - "‏لا يمكن أن يكون APN فارغًا." - "‏يجب أن يكون حقل MCC من ثلاثة أرقام." - "‏يجب أن يتكون حقل MNC من رقمين أو ثلاثة أرقام." - "‏جارٍ استعادة إعدادات APN الافتراضية." - "إعادة تعيين إلى الإعداد الافتراضي" - "‏اكتملت إعادة تعيين إعدادات APN الافتراضية." - "بلا عنوان" - "أسماء نقاط الدخول" - "‏أسماء نقاط الدخول (APN)" - "‏APN جديد" - "إعدادات اسم نقطة الدخول ليست متوفرة لهذا المستخدم" - "هل تريد النسخ في الحافظة؟" - "نسخ" - "إلى %s" - "عام" - "إعدادات متقدمة" - "الإعدادات العامة" - "إعدادات متقدمة" - "‏شريحة SIM من \"%s\"" - "إرسال رسالة قصيرة واحدة إلى جميع المستلمين. ولن تصل الردود إلا إليك" - "إرسال رسالة وسائط متعددة إلى جميع المستلمين" - "رقم غير معروف" - "رسالة جديدة" - "رسالة جديدة." - "‏محدد SIM" - "‏تم اختيار %1$s، أداة اختيار شريحة SIM" - "تعديل الموضوع" - "‏تحديد SIM أو تعديل الموضوع" - "المس مع الاستمرار لتسجيل الصوت" - "بدء محادثة جديدة" - "المراسلة" - "قائمة المراسلة" - "المراسلة" - "رسالة جديدة" - "قائمة المحادثات" - "جارٍ تحميل المحادثات." - "جارٍ تحميل الرسائل" - "عرض المزيد من المحادثات" - "عرض المزيد من الرسائل" - "تم حذف المحادثة" - "تم حذف المحادثة. المس لعرض محادثة مختلفة في تطبيق المراسلة" - "تم الحظر" - "تم إلغاء الحظر" - "مساحة التخزين منخفضة. ربما يتم فقد بعض البيانات." - "تحديد المرفقات" - "تأكيد التحديد" - "تم تحديد %d" - "يُرجى إزالة مرفق أو أكثر وإعادة المحاولة." - "يمكنك محاولة إرسال الرسالة، ولكن ربما لا يتم تسليمها ما لم تتم إزالة مرفق أو أكثر من هذه الرسالة." - "لا يمكنك إرسال أكثر من مقطع فيديو واحد لكل رسالة. يُرجى إزالة مقاطع الفيديو الإضافية وإعادة المحاولة." - "أخفق تطبيق المراسلة في تحميل المرفق." - "إرسال على أية حال" - "لا يمكن بدء المحادثة" - "%1$s (%2$s)" - "تم اختيار %s" - diff --git a/res/values-az-rAZ/arrays.xml b/res/values-az-rAZ/arrays.xml deleted file mode 100644 index f4b43e4..0000000 --- a/res/values-az-rAZ/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "mövzu yoxdur" - "mövzusuz" - - - "Hə" - "Yox" - "Ok" - "Heh" - "Çox sağ olun" - "Razıyam" - "Nizza" - "Yolumda" - "Ok, Sizinlə sonra əlaqə saxlayacam" - ":)" - ":(" - - diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml deleted file mode 100644 index 0bb8ef8..0000000 --- a/res/values-az-rAZ/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mesajlaşma" - "Mesajlaşma" - "Söhbəti seç" - "Ayarlar" - "Mesaj göndər" - "Qoşma əlavə edin" - "Yardım" - "Xoş gəlmisiniz" - "Ötürün" - "Sonrakı >" - "Növbəti" - "Çıxış" - "&gt ayarlar;" - "Ayarlar" - "Mesajlaşma SMS, Telefon və Kontaktlara icazə istəyir." - "İcazələri Ayarlar > Tətbiqlər > Mesajlaşma > İcazələr bölməsindən dəyişə bilərsiniz." - "İcazələri Ayarlar, Tətbiqlər, Mesajlaşma, İcazələr bölməsindən dəyişə bilərsiniz." - "Tez-tez olanlar" - "Bütün kontaktlar" - "%s ünvanına göndərin" - "Şəkilləri və ya videonu çəkin" - "Bu cihazdan şəkillər seçin" - "Səs yazın" - "Foto seçin" - "Media seçildi." - "Media seçilmədi." - "%d seçilib" - "təsvir %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "təsvir" - "Səs yazın" - "Paylaş" - "İndicə" - "İndi" - - %d dəq - %d dəq - - - %d saat - %d saat - - - %d gün - %d gün - - - %d həftə - bir həftə - - - %d ay - bir ay - - - %d il - bir il - - "Klass 0 mesaj" - "Yadda saxlayın" - "Cihazda boş yer azdır. Yer boşaltmaq üçün Mesajlaşma köhnə mesajları avtomatik siləcək." - "Yaddaş ehtiyatı qurtarır" - "Mesajlaşma cihazınızda kifayət qədər yaddaş olmayana kimi mesaj göndərə və qəbul etməyə bilər." - "SMS yaddaşı azdır. Mesajları silmə ehtiyacı hiss edə bilərsiniz." - "Telefon nömrənizi təsdiq edin" - "Bu bir addımlı mərhələ Mesajlaşmanın qrup mesajlarınızı düzgün şəkildə çatdıracağını təmin edəcək." - "Telefon nömrəsi" - "Media ilə olan bütün mesajları silin" - "%s müddətindən əvvəl köhnə mesajları silin" - "%s müddətindən köhnə mesajlar avtomatik silinsin" - "İqnor edin" - "Bütün mesajlar media ilə birlikdə silinsin?" - "%s müddətindən köhnə mesajlar silinsin?" - "%s müddətindən köhnə mesajlar avtomatik silinsin və avtomatik silinmə aktiv olsun?" - "%s bildirib" - "Bildirdiniz" - "%s tərəfindən mesaj:" - "Mesajı göndərdiniz" - "Göndərilir..." - "Göndərilmədi. Yenidən cəhd etmək üçün toxunun." - "Göndərilmədi. Yenidən cəhd edilir..." - "Resend or delete" - "Lütfən, təcili yardım xidmətlərinə səsli zəng edin. Mətn mesajınız bu vaxt çatdırıla bilməz." - "Alınmadı" - "Yükləmək üçün yeni MMS mesajı" - "Yeni MMS mesajı" - "Yüklənmədi" - "Yenidən cəhd etmək üçün toxun" - "Yükləmək üçün toxun" - "Yüklə və ya sil" - "Yüklənir..." - "Mesajın vaxtı bitib və ya mövcud deyil" - "size: %1$s, expiration: %2$s" - "Göndərə bilmir. Alıcı etibarsızdır." - "Xidmət şəbəkədə aktiv deyil" - "Şəbəkə probleminə görə göndərmək olmadı" - "Mesajın vaxtı keçib və ya əlçatmazdır" - "(Mövzusuz)" - "Naməlum göndərən" - "Çatdırılıb" - "%1$s mesajı %2$s bölməsindən endirilə bilmir." - "Az yaddaş ilə əlaqədar olaraq verilənlər bazası əməliyyatı tamamlana bilmədi" - "Mesaj göndərilmədi" - "Bəzi mesajlar Mesajlaşmada göndərilməyib" - - %d söhbətdə %d mesaj - bir söhbətdə %d mesaj - - "Mesaj endirilmədi" - "Bəzi mesajlar Mesajlaşmada endirilməyib" - - %d söhbətdə %d mesaj - bir söhbətdə %d mesaj - - "%1$s nömrəsinə mesaj göndərilmədi" - "Lütfən, təcili yardım xidmətlərinə səsli zəng edin. Mətn mesajınız %1$s nömrəsinə bu vaxt çatdırıla bilməz." - - %d yeni mesaj - Yeni mesaj - - "Başla" - "Kamera əlçatmazdır" - "Kamera əlçatmazdır" - "Video çəkiliş əlçatmazdır" - "Medianı yadda saxlamaq olmur" - "Şəkil çəkmək mümkün deyil" - "Geri" - "Arxivləşmiş" - "Sil" - "Arxiv" - "Arxivdən çıxarın" - "Bildirişləri deaktiv edin" - "Bildirişləri aktivləşdirin" - "Kontakt əlavə edin" - "Endirin" - "Göndər" - "Sil" - "Bu mesaj silinsin?" - "Bu əməliyyat geri qaytarıla bilməz." - "Sil" - - Bu söhbətlər silinsin? - Bu söhbət silinsin? - - "Sil" - "Ləğv et" - "Kimə" - "Çox şəkil seçin" - "Seçimi təsdiq edin" - "+%d" - "Audionu qeydə almaq olmur. Yenidən cəhd edin." - "Audionu oxutmaq olmur. Yenidən cəhd edin." - "Audionu saxlamaq olmadı. Yenidən cəhd edin." - "Toxunun və saxlayın" - ", " - " " - ": " - " " - "Şəkil" - "Audio klip" - "Videolar" - "Kontakt kartı" - "Endirin" - "SMS ilə cavab verin" - "MMS vasitəsilə cavab verin" - "Cavabla" - - %d iştirakçı - %d iştirakçı - - "Mən" - "Kontakt bloklandı və arxivləndi" - "Kontakt blokdan və arxivdən çıxarıldı" - "%d arxivlənib" - "%d arxivdən çıxarıldı" - "Bildirişlər deaktiv edildi" - "Bildirişlər aktiv edildi" - "Hamısı hazırdır. Göndərmək üçün toxunun." - "Mesajlaşma uğurla defolt SMS tətbiqi seçildi." - - Qoşmaları ləğv edin - Qoşmanı ləğv edin - - "Audio qoşma" - "Audio qoşmanı ifa et" - "Pauza" - "%s adlı göndərəndən mesaj: %s." - "%s adlı göndərənin mesajı getmədi: %s. Vaxt: %s." - "%s adlı göndərən tərəfindən mesaj: %s. Vaxt: %s." - "%s adlı əlaqəyə göndərilməmiş mesaj: %s. Vaxt: %s." - "%s adlı əlaqəyə mesaj göndərilir: %s. Vaxt: %s." - "%s adlı əlaqəyə mesaj göndərilmədi: %s. Vaxt: %s." - "%s adlı əlaqəyə mesaj: %s. Vaxt: %s." - "%s adlı göndərənin mesajı getmədi: %s. Vaxt: %s. %s." - "%s adlı göndərən tərəfindən mesaj: %s. Vaxt: %s. %s." - "%s istifadəçisinə göndərilməmiş mesaj: %s. Vaxt: %s." - "%s istifadəçisinə mesaj göndərilir: %s. Vaxt: %s." - "%s istifadəçisinə mesaj göndərilmədi: %s. Vaxt: %s." - "%s istifadəçisinə mesaj: %s. Vaxt: %s." - "Mesaj göndərilmədi. Təkrar cəhd etmək üçün toxunun." - "%s ilə söhbət" - "Mövzunu silin" - "Video çəkin" - "Durğun şəkli çəkin" - "Şəkil çəkin" - "Video çəkilişə başlayın" - "Tam ekranlı kameraya keçin" - "Ön və arxa kameralar arasında seçim edin" - "Çəkilişi dayandırın və videonu qoşun" - "Video qeydi dayandırın" - "Foto mesajlaşma" - - %d foto \"%s\" albomunda yadda saxlanıldı - %d foto \"%s\" albomunda yadda saxlanıldı - - - %d video \"%s\" albomunda yadda saxlanıldı - %d video \"%s\" albomunda yadda saxlanıldı - - - %d qoşma \"%s\" albomunda yadda saxlanıldı - %d qoşma \"%s\" albomunda yadda saxlanıldı - - - %d qoşma \"Endirilənlər\" qovluğunda yadda saxlanıldı - %d qoşma \"Endirilənlər\" qovluğunda yadda saxlanıldı - - - %d qoşma yadda saxlanıldı - %d qoşma yadda saxlanıldı - - - %d qoşma yadda saxlanıla bilmədi - %d qoşma yadda saxlanıla bilmədi - - "MMS qoşması yadda saxlandı" - "Ayarlar" - "Arxivləşmiş" - "Bağla" - "MMS" - "Qabaqcıl" - "Sazlama" - "Bildirişlər" - "Səs" - "Səssiz" - "Titrət" - "Bloklanmış" - "SMS çatdırma xətaları" - "Hər göndərdiyiniz SMS üçün çatdırılma raportu tələb edin" - "Avtomatik əldə edin" - "MMS mesajları avtomatik əldə edin" - "Rouminq zamanı avtomatik əldə edin" - "Rominq zamanı MMS-i avtomatim bərpa edin" - "Qrup mesajlaşması" - "Bir neçə alıcının olduğu halda bir mesaj göndərmək üçün MMS istifadə edin" - "Defolt SMS proqramı" - "Defolt SMS proqramı" - - - "Telefon nömrəniz" - "Naməlum" - "Gedən mesaj səsləri" - "Damp SMS" - "Damp xarici saxlanma faylına emal edilməmiş SMS datası qəbul etdi" - "Damp MMS" - "Damp xarici saxlanma faylına emal edilməmiş MMS datası qəbul etdi" - "Simsiz siqnallar" - "Mesaj seçimləri" - "Mətni kopyalayın" - "Detallara baxın" - "Sil" - "Yönləndir" - "Mesaj detalları" - "Növ: " - "Mətn mesajı" - "Multimedia mesajı" - "Kimdən: " - "Kimə: " - "Göndərilən: " - "Qəbul edilən: " - "Mövzu: " - "Ölçü: " - "Prioritet: " - "SIM: " - "Yüksək" - "Normal" - "Aşağı" - "SIM %s" - "Gizli göndərici ünvanı" - "Qoşmalar yüklənən zaman mesaj göndərmək olmur." - "Qoşma yüklənə bilmir. Yenidən cəhd edin." - "Şəbəkə hazır deyil. Yenidən cəhd edin." - "Mətni silin" - "Mətn və nömrə daxiletməsi arasında seçim edin" - "İştirakçı əlavə edin" - "İştirakçıları təsdiq edin" - "Yeni söhbətə başlayın" - "Bu elementi seçin" - "Videonu oxut" - "Adamlar və seçimlər" - "Sazlama" - "Adamlar və seçimlər" - "Ümumi" - "Bu söhbətdəki adamlar" - "Zəng edin" - "Mesaj göndərin" - "Mesaj göndərin<br/><kiçik>buradan: %s</kiçik>" - - Fotolar göndərin - Foto göndərin - - - Audioları göndərin - Audio göndərin - - - Videoları göndərin - Video göndərin - - - Kontakt kartlarını göndərin - Kontakt kartını göndərin - - - Qoşmaları göndərin - Qoşma göndərin - - - %d qoşma göndərilmə üçün hazırdır - 1 qoşma göndərilmə üçün hazırdır - - "Geri əlaqə göndərin" - "Google Play Store\'də bax" - "Versiya haqqında" - "Versiya %1$s" - "Açıq mənbə lisenziyaları" - "Bildirişlər" - "Qoşma limitini keçdi" - "Qoşmanı yükləyə bilmədi." - "Kontaktlara əlavə edilsin?" - "Kontakt əlavə edin" - "Mövzu" - "Mövzu: " - "%s%s" - "Kontakt kartı yüklənir" - "Kontakt kartını yükləmək alınmadı" - "Kontakt kartına baxın" - - %d kontakt - %d kontakt - - "Kontakt kartları" - "Doğum günü" - "Qeydlər" - "Mesajı yönləndirin" - "Cavablayın" - "+1" - "+%d" - "SMS deaktivdir" - "Göndərmək üçün Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın" - "Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın" - "Dəyişin" - "Mesaj qəbul etmək üçün Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın" - "SMS göndərmək üçün tərcih olunan SIM seçilməyib" - "Bu tətbiqə cihaz sahibi tərəfindən icazə verilmir." - "Ok" - "Söhbətdə həddən çox iştirakçı var" - - Yanlış kontakt - Yanlış kontakt - - "Kamera şəklini yükləmək olmur" - "Siz: " - "%s: " - "Qaralama" - "Söhbətə başlayan kimi onu burada siyahılanmış görəcəksiniz" - "Arxivlənmiş söhbətlər burada görünür" - "Söhbətlər yüklənir…" - "Şəkil" - "Audio klip" - "Video" - "Əlaqə kartı" - "MMS" - "Geri" - "Yenidən cəhd edin" - "Yeni mesaja başlamaq üçün kontakt adını və ya telefon nömrəsini daxil edin" - "Blok edin" - "%s bloklayın" - "%s blokdan çıxarın" - "%s bloklansın?" - "Bu nömrədən mesaj almağa davam edəcəksiniz, lakin mesajın gəlməsinə dair bildiriş olmayacaq. Söhbət arxivlənəcək." - "Bloklanmış kontaktlar" - "BLOKDAN ÇIXARIN" - "Bloklanmış kontaktlar" - "Sənəd kitabxanasından şəkil seçin" - "Mesaj göndərilir" - "Mesaj göndərildi" - "Mobil data deaktivdir. Ayarları yoxlayın." - "Təyyarə rejimində mesaj göndərmək olmur" - "Mesaj göndərilə bilmədi" - "Mesaj yükləndi" - "Mobil data deaktivdir. Ayarları yoxlayın." - "Təyyarrə rejimində mesajları endirə bilməz" - "Mesaj endirilə bilmədi" - "Sıfır" - "Bir" - "İki" - "Üç" - "Dörd" - "Beş" - "Altı" - "Yeddi" - "Səkkiz" - "Doqquz" - "Mesajı %1$s ilə göndərmək olmur, xəta %2$d" - "Mesajı naməlum operator ilə göndərmək olmur, xəta %1$d" - "Yön: %s" - "Mesaj göndərilmədi. Xidmət şəbəkədə aktivləşdirilməyib." - "Mesaj göndərilmədi: Etibarsız təyinat ünvanı" - "Mesaj göndərilmədi: etibarsız mesaj" - "Mesaj göndərilmədi: dəstəklənməyən məzmun" - "Mesaj göndərilmədi: dəstəklənməyən mesaj" - "Mesaj göndərilmədi: çox böyükdür" - "Yeni mesaj" - "Göstər" - "Təsvir" - "Uyğun tətbiqi tapmaq olmadı" - "Qəbuledicini silin" - "Yeni mesaj" - "Ləğv et" - "Edit access point" - "Ayarlanmayıb" - "Ad" - "APN" - "MMSC" - "MMS proksi" - "MMS por" - "MCC" - "MNC" - "APN növü" - "APN silin" - "Yeni APN" - "Yadda saxlayın" - "İmtina edin" - "Ad sahəsi boş ola bilməz." - "APN boş ola bilməz." - "MCC sahəsində 3 rəqəm olmalıdır." - "MNC sahəsi 2 və ya 3 rəqəm olmalıdır." - "Defolt APN ayarları bərpa olunur." - "Sıfırlayın" - "Varsayılan APN ayarlarının sıfırlanması tamamlandı." - "Başlıqsız" - "Çatma Nöqtəsi Adları" - "APNs" - "Yeni APN" - "Giriş Nöqtəsi Ad Ayarları bu istifadəçi üçün əlçatmazdır" - "Panoya kopyalansın?" - "Kopyalayın" - "Buraya: %s" - "Ümumi" - "Qabaqcıl" - "Ümumi ayarlar" - "Qabaqcıl ayarlar" - "\"%s\" SIM" - "Bütün alıcılara fərdi SMS göndər. Hər hansı bir cavabı özünüz olacaq" - "Bütün alıcılara bir MMS göndər" - "Naməlum nömrə" - "Yeni mesaj" - "Yeni mesaj." - "SIM seçicisi" - "%1$s seçildi, SIM seçicisi" - "Mövzunu dəyiş" - "SİM seçin və ya mövzu redaktə edin" - "Səs yazmaq üçün basın və saxlayın" - "Yeni söhbətə başla" - "Mesajlaşma" - "Mesajlaşma Siyahısı" - "Mesajlaşma" - "Yeni mesaj" - "Söhbət siyahısı" - "Söhbətlər yüklənir" - "Mesajlar yüklənir" - "Daha çox söhbət görüntülə" - "Daha çox mesaja bax" - "Söhbət silinib" - "Söhbət silindi. Başqa Mesajlaşma söhbəti göstərmək üçün toxunun" - "Bloklanıb" - "Blokdan çıxarılıb" - "Yaddaş yeri azdır. Bəzi məlumatlar itə bilər." - "Qoşmaları seçin" - "Seçimi təsdiq edin" - "%d seçilib" - "Lütfən, bir və ya daha çox qoşmanı silin və yenidən cəhd edin." - "Mesajınızı göndərməyə cəhd edə bilərsiniz, lakin bir və ya daha çox qoşmanı silmədiyiniz halda, mesaj çatmaya bilər." - "Hər mesaja yalnız bir video yükləyə bilərsiniz. Əlavə videoları silin və yenidən cəhd edin." - "Mesajlaşma qoşmanı yükləyə bilmədi." - "İstənilən halda göndərin" - "Söhbətə başlamaq olmadı" - "%1$s (%2$s)" - "%s seçilmiş" - diff --git a/res/values-bg/arrays.xml b/res/values-bg/arrays.xml deleted file mode 100644 index ac036c4..0000000 --- a/res/values-bg/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "без тема" - "няма тема" - - - "Да" - "Не" - "ОK" - "Ха-ха" - "Благодаря" - "Приемам" - "Чудесно" - "Идвам" - "OK, ще се свържа с теб по-късно" - ":)" - ":(" - - diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml deleted file mode 100644 index 5251fa8..0000000 --- a/res/values-bg/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Съобщения" - "Съобщения" - "Избиране на разговор" - "Настройки" - "Изпращане на съобщение" - "Добавяне на прикачен файл" - "Помощ" - "Добре дошли" - "Пропускане" - "Напред >" - "Напред" - "Изход" - "Настройки >" - "Настройки" - "Приложението Съобщения се нуждае от разрешение за SMS, телефон и контакти." - "Можете да промените разрешенията от „Настройки“ > „Приложения“ > „Съобщения“ > „Разрешения“." - "Можете да промените разрешенията от „Настройки“, „Приложения“, „Съобщения“, „Разрешения“." - "Често търсени" - "Всички контакти" - "Изпращане до %s" - "Правете снимки или видеоклипове" - "Избиране на изображения от това устройство" - "Запишете звук" - "Избиране на снимка" - "Мултимедийният файл е избран." - "Изборът на мултимедиен файл е премахнат." - "Избрахте %d" - "изображение от %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "изображение" - "Запис на звук" - "Споделяне" - "Току-що" - "Сега" - - %d мин - %d мин - - - %d часа - %d час - - - %d дни - %d ден - - - %d седмици - една седмица - - - %d месеца - един месец - - - %d години - една година - - "Съобщения от клас 0" - "Запазване" - "Мястото на устройството не е достатъчно. Приложението Съобщения автоматично ще изтрие по-старите кореспонденции, за да освободи място." - "Мястото в хранилището е на изчерпване" - "Възможно е Съобщения да не изпраща, нито да получава съобщения, докато на устройството не се освободи повече място." - "Мястото в хранилището за SMS намалява. Може да се наложи да изтриете съобщения." - "Потвърждаване на телефонния ви номер" - "Тази еднократна стъпка ще гарантира, че Съобщения ще доставя правилно груповите ви съобщения." - "Телефонен номер" - "Изтриване на всички съобщения с мултимедия" - "Изтриване на съобщенията, по-стари от %s" - "Автоматично изтриване на съобщенията, по-стари от %s" - "Пренебрегване" - "Да се изтрият ли всички съобщения с медийни файлове?" - "Да се изтрият ли съобщенията, по-стари от %s?" - "Да се изтрият ли съобщенията, по-стари от %s, и да се включи ли функцията за автоматично изтриване?" - "%s каза" - "Казахте" - "Съобщение от %s" - "Изпратихте съобщение" - "Изпраща се…" - "Не е изпратено. Докоснете за нов опит." - "Не е изпратено. Извършва се нов опит…" - "Повторно изпращане или изтриване" - "Моля, осъществете гласово обаждане до спешните служби. Понастоящем текстовото ви съобщение не можа да бъде доставено." - "Не бе успешно" - "Ново MMS съобщение за изтегляне" - "Ново MMS съобщение" - "Не можа да се изтегли" - "Докоснете за нов опит" - "Докоснете за изтегляне" - "Изтегляне или изтриване" - "Изтегля се…" - "Съобщението е с изтекла валидност или не е налице" - "размер: %1$s, валидност: %2$s" - "Не може да се изпрати. Получателят не е валиден." - "Услугата не е активирана за мрежата" - "Не можа да се изпрати поради проблем с мрежата" - "Съобщението е с изтекла валидност или не е налице" - "(Без тема)" - "Неизвестен подател" - "Доставено" - "Съобщението „%1$s“ от %2$s не можа да се изтегли." - "Операцията в базата от данни не можа да завърши поради недостиг на памет" - "Съобщението не е изпратено" - "Някои съобщения в приложението Съобщения не са изпратени" - - %d съобщения в %d разговора - %d съобщения в един разговор - - "Съобщението не е изтеглено" - "Някои съобщения в приложението Съобщения не са изтеглени" - - %d съобщения в %d разговора - %d съобщения в един разговор - - "Съобщението до %1$s не е изпратено" - "Моля, осъществете гласово обаждане до спешните служби. Понастоящем текстовото ви съобщение до %1$s не можа да бъде доставено." - - %d нови съобщения - Ново съобщение - - "Стартиране" - "Няма достъп до камерата" - "Няма достъп до камерата" - "Функцията за заснемане на видео не е налице" - "Медийният файл не може да се запази" - "Не може да се направи снимка" - "Назад" - "Архивирани" - "Изтриване" - "Архивиране" - "Деархивиране" - "Изключване на известията" - "Включване на известията" - "Добавяне на контакт" - "Изтегляне" - "Изпращане" - "Изтриване" - "Да се изтрие ли това съобщение?" - "Това действие не може да се отмени." - "Изтриване" - - Да се изтрият ли тези разговори? - Да се изтрие ли този разговор? - - "Изтриване" - "Отказ" - "До" - "Избиране на няколко изображения" - "Потвърждаване на избора" - "+ %d" - "Аудиото не може да се запише. Опитайте отново." - "Аудиото не може да се възпроизведе. Опитайте отново." - "Аудиото не можа да се запази. Опитайте отново." - "Докоснете и задръжте" - ", " - " ," - ": " - " :" - "Снимка" - "Аудиоклип" - "Видеоклип" - "Визитна картичка" - "Изтегляне" - "Отговор чрез SMS" - "Отговор чрез MMS" - "Отговор" - - %d участници - %d участник - - "Аз" - "Контактът е блокиран и архивиран" - "Контактът е отблокиран и деархивиран" - "Архивирахте %d" - "Деархивирахте %d" - "Известията са изключени" - "Известията са включени" - "Готово. Докоснете „Изпращане“ отново." - "Успешно зададохте Съобщения като стандартно приложение за SMS." - - Отхвърляне на прикачените файлове - Отхвърляне на прикачения файл - - "Прикачен аудиофайл" - "Пускане на прикачения аудиофайл" - "Пауза" - "Съобщение от %s: „%s“." - "Неуспешно съобщение от %s: „%s“. Час: %s." - "Съобщение от %s: „%s“. Час: %s." - "Неизпратено съобщение до %s: „%s“. Час: %s." - "Изпраща се съобщение до %s: „%s“. Час: %s." - "Неуспешно съобщение до %s: „%s“. Час: %s." - "Съобщение до %s: „%s“. Час: %s." - "Неуспешно съобщение от %s: „%s“. Час: %s. %s." - "Съобщение от %s: „%s“. Час: %s. %s." - "Неизпратено съобщение до %s: „%s“. Час: %s." - "Изпраща се съобщение до %s: „%s“. Час: %s." - "Неуспешно съобщение до %s: „%s“. Час: %s." - "Съобщение до %s: „%s“. Час: %s." - "Неуспешно съобщение. Докоснете, за да опитате отново." - "Разговор с/ъс %s" - "Изтриване на темата" - "Заснемане на видеоклип" - "Заснемане на кадър" - "Правене на снимка" - "Стартиране на записване на видеоклип" - "Превключване към камера на цял екран" - "Превключване между предната и задната камера" - "Спиране на записването и прикачване на видеоклипа" - "Спиране на видеозаписа" - "Снимки в Съобщения" - - %d снимки са запазени в албума „%s - %d снимка е запазена в албума „%s - - - %d видеоклипа са запазени в албума „%s - %d видеоклип е запазен в албума „%s - - - %d прикачени файла са запазени в албума „%s - %d прикачен файл е запазен в албума „%s - - - %d прикачени файла са запазени в „Изтегляния“ - %d прикачен файл е запазен в „Изтегляния“ - - - %d прикачени файла са запазени - %d прикачен файл е запазен - - - %d прикачени файла не можаха да бъдат запазени - %d прикачен файл не можа да се запази - - "Запазен прикачен файл от MMS" - "Настройки" - "Архивирани" - "Затваряне" - "MMS" - "Разширени" - "Отстраняване на грешки" - "Известия" - "Звук" - "Тих режим" - "Вибриране" - "Блокирано" - "Отчети за получаване на SMS" - "Заявяване на отчет за доставка за всеки изпратен от вас SMS" - "Автоматично извличане" - "Автоматично извличане на мултимедийните съобщения (MMS)" - "Автоматично извличане при роуминг" - "Автоматично извличане на MMS при роуминг" - "Групови съобщения" - "Използвайте MMS, за да изпратите едно съобщение, когато има няколко получатели" - "Стандартно приложение за SMS" - "Стандартно приложение за SMS" - - - "Вашият телефонен номер" - "Няма информация" - "Звуци за изходящи съобщения" - "Разтоварване на SMS" - "Разтоварване на необработените данни от получените SMS съобщения във файл във външното хранилище" - "Разтоварване на MMS" - "Разтоварване на необработените данни от получените MMS съобщения във файл във външното хранилище" - "Безжични сигнали" - "Опции на съобщението" - "Копиране на текста" - "Преглед на подробностите" - "Изтриване" - "Напред" - "Подробности за съобщението" - "Тип: " - "Текстово съобщение" - "Мултимедийно съобщение" - "От: " - "До: " - "Изпратено: " - "Получено: " - "Тема: " - "Размер: " - "Приоритет: " - "SIM: " - "Висок" - "Нормален" - "Нисък" - "SIM %s" - "Скрит адрес на изпращача" - "Съобщението не може да се изпрати, докато прикачените файлове се зареждат." - "Прикаченият файл не може да се зареди. Опитайте отново." - "Мрежата не е готова. Опитайте отново." - "Изтриване на текста" - "Превключване между букви и цифри" - "Добавяне на още участници" - "Потвърждаване на участниците" - "Начало на нова кореспонденция" - "Избиране на този елемент" - "Пускане на видеоклипа" - "Хора и опции" - "Отстраняване на грешки" - "Хора и опции" - "Общи настройки" - "Хора в този разговор" - "Обаждане по телефона" - "Изпращане на съобщение" - "Изпращане на съобщение<br/><small>от %s</small>" - - Изпращане на снимките - Изпращане на снимката - - - Изпращане на аудиозаписите - Изпращане на аудиозаписа - - - Изпращане на видеоклиповете - Изпращане на видеоклипа - - - Изпращане на визитните картички - Изпращане на визитната картичка - - - Изпращане на прикачените файлове - Изпращане на прикачения файл - - - %d прикачени файла са готови за изпращане - Един прикачен файл е готов за изпращане - - "Изпращане на отзиви" - "Преглед в Google Play Магазин" - "Информация за версията" - "Версия %1$s" - "Лицензи за отворен код" - "Известия" - "Ограничението за прикачени файлове е достигнато" - "Зареждането на прикачения файл не бе успешно." - "Да се добави ли към контактите?" - "Добавяне на контакт" - "Тема" - "Тема: " - "„%s“ – „%s“" - "Визитната картичка се зарежда" - "Визитната картичка не можа да се зареди" - "Преглед на визитната картичка" - - %d контакта - %d контакт - - "Визитни картички" - "Рожден ден" - "Бележки" - "Препращане на съобщението" - "Отговор" - "+1" - "+%d" - "SMS съобщенията са деактивирани" - "За да изпращате съобщения, задайте Съобщения като стандартно приложение за SMS" - "Задайте Съобщения като стандартно приложение за SMS" - "Промяна" - "За да получавате съобщения, задайте Съобщения като стандартно приложение за SMS" - "Няма избрана предпочитана SIM карта за изпращане на SMS съобщения" - "Това приложение не е разрешено от собственика на устройството." - "ОK" - "Твърде много участници в разговора" - - Контактите са невалидни - Контактът е невалиден - - "Изображението от камерата не можа да се зареди" - "Вие: " - "%s: " - "Чернова" - "Когато започнете нов разговор, ще го видите посочен тук" - "Тук се показват архивираните кореспонденции" - "Кореспонденциите се зареждат…" - "Снимка" - "Аудиоклип" - "Видеоклип" - "Визитна картичка" - "MMS" - "Отмяна" - "Повторен опит" - "Въведете име на контакт или телефонен номер, за да започнете ново съобщение" - "Блокиране" - "Блокиране на %s" - "Отблокиране на %s" - "Да се блокира ли %s?" - "Ще продължите да получавате съобщения от този номер, но вече няма да бъдете известявани. Кореспонденцията ще се архивира." - "Блокирани контакти" - "ОТБЛОКИРАНЕ" - "Блокирани контакти" - "Избиране на изображение от библиотеката с документи" - "Съобщението се изпраща" - "Съобщението е изпратено" - "Мобилните данни са изключени. Проверете настройките си." - "Не можете да изпращате съобщения в самолетен режим" - "Съобщението не можа да се изпрати" - "Съобщението е изтеглено" - "Мобилните данни са изключени. Проверете настройките си." - "Съобщенията не могат да се изтеглят в самолетен режим" - "Съобщението не можа да бъде изтеглено" - "нула" - "едно" - "две" - "три" - "четири" - "пет" - "шест" - "седем" - "осем" - "девет" - "Съобщението не може да се изпрати с/ъс %1$s – грешка %2$d" - "Съобщението не може да се изпрати с неизвестен оператор – грешка %1$d" - "Препр.: %s" - "Съобщението не е изпратено: Услугата не е активирана за мрежата" - "Съобщението не е изпратено: невалиден целеви адрес" - "Съобщението не е изпратено: невалидно съобщение" - "Съобщението не е изпратено: неподдържано съдържание" - "Съобщението не е изпратено: неподдържано съобщение" - "Съобщението не е изпратено: твърде голямо" - "Ново съобщение" - "Преглед" - "Изображение" - "Не можа да бъде намерено подходящо приложение" - "Премахване на получателя" - "Ново съобщение" - "Отказ" - "Редактиране на точката за достъп" - "Не е зададено" - "Име" - "Име на точката за достъп (APN)" - "MMSC" - "MMS прокси сървър" - "MMS порт" - "Mобилен код на държава" - "Код на мобилна мрежа (MNC)" - "Тип на името на точката за достъп (APN)" - "Изтриване на името на точката за достъп (APN)" - "Ново име на точката за достъп (APN)" - "Запазване" - "Отхвърляне" - "Полето за име трябва да се попълни." - "Името на точката за достъп (APN) трябва да се попълни." - "Полето за мобилен код на държавата трябва да е 3 цифри." - "Полето за код на мобилна мрежа (MNC) трябва да е с 2 или 3 цифри." - "Стандартните настройки за името на точката за достъп (APN) се възстановяват." - "Възстановяване на стандартните" - "Възстановяването на стандартните настройки за името на точката за достъп (APN) завърши." - "Без име" - "Имена на точката за достъп" - "Имена на точката за достъп (APN)" - "Ново име на точката за достъп (APN)" - "Настройките за име на точката за достъп не са налице за този потребител" - "Да се копира ли в буферната памет?" - "Копиране" - "до „%s“" - "Общи настройки" - "Разширени настройки" - "Общи настройки" - "Разширени настройки" - "SIM карта „%s“" - "Изпращане на отделни SMS съобщения до всички участници. Само вие ще получавате отговорите" - "Изпращане на един MMS до всички участници" - "Неизвестен номер" - "Ново съобщение" - "Ново съобщение." - "Инструмент за избор на SIM карта" - "Избрахте „%1$s“ – инструмент за избиране на SIM карти" - "Редактиране на темата" - "Избиране на SIM карта или редактиране на темата" - "Докоснете и задръжте, за да запишете аудио" - "Начало на нова кореспонденция" - "Съобщения" - "Списък със съобщения" - "Съобщения" - "Ново съобщение" - "Списък с кореспонденции" - "Кореспонденциите се зареждат" - "Съобщенията се зареждат" - "Преглед на още кореспонденции" - "Преглед на още съобщения" - "Кореспонденцията бе изтрита" - "Разговорът е изтрит. Докоснете за показване на друг от Съобщения" - "Блокирано" - "Отблокирано" - "Мястото в хранилището е малко. Може да изгубите някои данни." - "Избиране на прикачени файлове" - "Потвърждаване на избора" - "Избрахте %d" - "Моля, премахнете един или повече прикачени файлове и опитайте отново." - "Можете да опитате да изпратите съобщението си, но е възможно то да не бъде доставено, ако не премахнете един или повече прикачени файлове." - "Можете да изпращате само по един видеоклип с всяко съобщение. Моля, премахнете допълнителните клипове и опитайте отново." - "Зареждането на прикачения файл от Съобщения не бе успешно." - "Изпращане въпреки това" - "Разговорът не можа да започне" - "%1$s (%2$s)" - "Избрахте %s" - diff --git a/res/values-bn-rBD/arrays.xml b/res/values-bn-rBD/arrays.xml deleted file mode 100644 index bdf164c..0000000 --- a/res/values-bn-rBD/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "কোনো বিষয় নেই" - "বিষয়হীন" - - - "হ্যাঁ" - "না" - "ঠিক আছে" - "হিহি" - "ধন্যবাদ" - "আমি রাজি" - "সুন্দর" - "রাস্তায় আছি" - "ঠিক আছে, পরে আপনার সঙ্গে এই নিয়ে কথা বলছি" - ":)" - ":(" - - diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml deleted file mode 100644 index 8c58185..0000000 --- a/res/values-bn-rBD/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "বার্তাপ্রেরণ" - "বার্তাপ্রেরণ" - "কথোপকথন নির্বাচন করুন" - "সেটিংস" - "বার্তা পাঠান" - "একটি সংযুক্তি যোগ করুন" - "সহায়তা" - "স্বাগতম" - "এড়িয়ে যান" - "পরবর্তী >" - "পরবর্তী" - "প্রস্থান করুন" - "সেটিংস >" - "সেটিংস" - "SMS, ফোন এবং পরিচিতিগুলিতে বার্তাপ্রেরণ -এর অনুমতির প্রয়োজন।" - "আপনি সেটিংস > অ্যাপ্লিকেশানগুলি > বার্তাপ্রেরণ > অনুমতিগুলি এ অনুমতিগুলি পরিবর্তন করতে পারেন৷" - "আপনি সেটিংস, অ্যাপ্লিকেশানগুলি, বার্তাপ্রেরণ, অনুমতিগুলি এ অনুমতিগুলি পরিবর্তন করতে পারেন৷" - "প্রায়শই ব্যবহৃত" - "সকল পরিচিতি" - "%s এ পাঠান" - "ছবি বা ভিডিও ক্যাপচার করুন" - "এই ডিভাইস থেকে চিত্র নির্বাচন করুন" - "অডিও রেকর্ড করুন" - "ফটো চয়ন করুন" - "মিডিয়া নির্বাচিত হয়েছে।" - "মিডিয়া নির্বাচিত হয় নি।" - "%dটি নির্বাচন করা হয়েছে" - "চিত্র %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "চিত্র" - "অডিও রেকর্ড করুন" - "ভাগ করুন" - "এইমাত্র" - "এখনই" - - %d মিনিট - %d মিনিট - - - %d ঘণ্টা - %d ঘণ্টা - - - %d দিন - %d দিন - - - %d সপ্তাহ - %d সপ্তাহ - - - %d মাস - %d মাস - - - %d বছর - %d বছর - - "ক্লাস ০ বার্তা" - "সংরক্ষণ করুন" - "ডিভাইসে কম জায়গা আছে। বার্তাপ্রেরণ জায়গা খালি করতে স্বয়ংক্রিয়ভাবে পুরানো পাঠ্য বার্তাগুলি মুছে ফেলবে।" - "সঞ্চয়স্থান পূর্ণ হতে চলেছে" - "আপনার ফোনে অরো বেশি সঞ্চয়স্থান উপলব্ধ না থাকলে, বার্তাপ্রেরণ বার্তাগুলি পাঠাতে বা গ্রহণ করতে নাও পারে৷" - "SMS সংগ্রহস্থল বেশি নেই৷ আপনাকে বার্তা মুছতে হতে পারে৷" - "আপনার ফোন নম্বর নিশ্চিত করুন" - "এই এককালীন ধাপটি নিশ্চিত করে যে বার্তাপ্রেরণ গোষ্ঠী বার্তাগুলিকে সঠিকভাবে বিতরণ করবে৷" - "ফোন নম্বর" - "মিডিয়ার সাথে সমস্ত বার্তা মুছুন" - "%s এর থেকে পুরানো বার্তাগুলি মুছুন" - "%s-এর থেকে পুরানো বার্তা স্বংয়ক্রিয়ভাবে মুছুন" - "উপেক্ষা করুন" - "মিডিয়ার সমন্বিত সমস্ত বার্তা মুছুন" - "%s-এর থেকে পুরানো বার্তাগুলি মুছবেন?" - "%s-এর থেকে পুরানো বার্তা মুছে দিন এবং স্বয়ংক্রিয়ভাবে মোছা চালু করবেন?" - "%s বলেছেন" - "আপনি বলেছেন" - "%s এর থেকে বার্তা" - "আপনি একটি বার্তা পাঠিয়েছেন" - "পাঠানো হচ্ছে..." - "পাঠানো হয়নি। আবার চেষ্টা করতে স্পর্শ করুন।" - "পাঠানো হয়নি। আবার চেষ্টা হচ্ছে…" - "আবার পাঠান অথবা মুছে ফেলুন" - "অনুগ্রহ করে জরুরি পরিষেবাগুলিতে একটি ভয়েস কল করুন৷ আপনার পাঠ্য বার্তাগুলিকে এই মূহুর্তে বিতরণ করা যাবে না৷" - "ব্যর্থ হয়েছে" - "ডাউনলোড করার জন্য নতুন MMS বার্তা" - "নতুন MMS বার্তা" - "ডাউনলোড করা যায়নি" - "আবার চেষ্টা করতে স্পর্শ করুন" - "ডাউনলোড করার জন্য স্পর্শ করুন" - "ডাউনলোড করুন অথবা মুছুন" - "ডাউনলোড হচ্ছে..." - "বার্তার মেয়াদ শেষ হয়ে গেছে বা অনুপলব্ধ" - "আকার: %1$s, মেয়াদ শেষ: %2$s" - "পাঠানো যাবে না। প্রাপক বৈধ নয়।" - "নেটওয়ার্কে পরিষেবা সক্রিয় নেই" - "নেটওয়ার্ক সমস্যার কারণে পাঠানো যায়নি" - "বার্তার মেয়াদ শেষ হয়ে গেছে বা অনুপলব্ধ" - "(কোনো বিষয় নেই)" - "অজ্ঞাত প্রেরক" - "পৌঁছে দেওয়া হয়েছে" - "%2$s থেকে %1$s বার্তা ডাউনলোড করা যায়নি।" - "কম মেমরি থাকার কারণে ডেটাবেস ক্রিয়াকলাপ সম্পন্ন করা যায়নি" - "বার্তা পাঠানো হয়নি" - "বার্তাপ্রেরণ এ কিছু বার্তা পাঠানো হয়নি" - - %dটি কথোপকথনে %dটি বার্তা রয়েছে - %dটি কথোপকথনে %dটি বার্তা রয়েছে - - "বার্তা ডাউনলোড করা যাবে না" - "বার্তাপ্রেরণ এ কিছু বার্তা ডাউনলোড করা যায়নি" - - %dটি কথোপকথনে %dটি বার্তা রয়েছে - %dটি কথোপকথনে %dটি বার্তা রয়েছে - - "%1$s এ বার্তা পাঠানো হয়নি" - "অনুগ্রহ করে জরুরি পরিষেবাগুলিতে একটি ভয়েস কল করুন৷ %1$s এ পাঠানো আপনার পাঠ্য বার্তাগুলিকে এই মূহুর্তে বিতরণ করা যাবে না৷" - - %dটি নতুন বার্তা - %dটি নতুন বার্তা - - "আরম্ভ" - "ক্যামেরা উপলব্ধ নয়" - "ক্যামেরা উপলব্ধ নয়" - "ভিডিও ক্যাপচার উপলব্ধ নয়" - "মিডিয়া সংরক্ষণ করা যাবে না" - "ছবি তুলতে পারবেন না" - "ফিরুন" - "সংরক্ষণাগারভুক্ত" - "মুছুন" - "সংরক্ষণ করুন" - "সংরক্ষণাগারমুক্ত করুন" - "বিজ্ঞপ্তিগুলি বন্ধ করুন" - "বিজ্ঞপ্তি চালু করুন" - "পরিচিতি যোগ করুন" - "ডাউনলোড করুন" - "পাঠান" - "মুছুন" - "এই বার্তাটি মুছবেন?" - "এই কাজটিকে পূর্বাবস্থায় ফেরানো যাবে না৷" - "মুছুন" - - এই কথোপকথনগুলি মুছবেন? - এই কথোপকথনগুলি মুছবেন? - - "মুছুন" - "বাতিল করুন" - "প্রাপক" - "একাধিক চিত্র নির্বাচন করুন" - "নির্বাচন নিশ্চিত করুন" - "+%dটি" - "অডিও রেকর্ড করা যাবে না। আবার চেষ্টা করুন।" - "অডিও প্লে করা যাবে না। আবার চেষ্টা করুন।" - "অডিও সংরক্ষণ করা যায়নি। আবার চেষ্টা করুন" - "স্পর্শ করুন এবং ধরে রাখুন" - ", " - " " - ": " - " " - "ছবি" - "অডিও ক্লিপ" - "ভিডিও" - "পরিচিতি কার্ড" - "ডাউনলোড" - "SMS এর মাধ্যমে উত্তর দিন" - "MMS এর মাধ্যমে উত্তর দিন" - "উত্তর দিন" - - %d জন অংশগ্রহণকারী - %d জন অংশগ্রহণকারী - - "আমি" - "পরিচিতি অবরুদ্ধ ও সংরক্ষণাগারভুক্ত হয়েছে" - "পরিচিতি অবরোধ মুক্ত এবং সংরক্ষণাগারমুক্ত করা হয়েছে" - "%d টি সংরক্ষণাগারভুক্ত হয়েছে" - "%d সংরক্ষণাগারমুক্ত হয়েছে" - "বিজ্ঞপ্তিগুলি বন্ধ করা হয়েছে" - "বিজ্ঞপ্তিগুলি চালু করা হয়েছে" - "সবকিছু সেট। আবার পাঠাতে স্পর্শ করুন।" - "ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সফলভাবে সেট করা হয়েছে৷" - - সংযুক্তিগুলি বাতিল করুন - সংযুক্তিগুলি বাতিল করুন - - "অডিও সংযুক্তি" - "অডিও সংযুক্তি চালান" - "বিরাম দিন" - "%s এর পাঠানো বার্তা: %s৷" - "%s:-র %s বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: %s।" - "%s: এর থেকে %s বার্তা। সময়: %s।" - "%s: -কে %s বার্তা পাঠানো যায় নি। সময়: %s।" - "%s: -কে %s বার্তা পাঠাচ্ছে। সময়: %s।" - "%s: -কে %s বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: %s।" - "%s: -কে %s বার্তা পাঠান। সময়: %s।" - "%s: %s বার্তা পাঠাতে ব্যর্থ হয়েছেন। সময়: %s%s।" - "%s: এর থেকে %s বার্তা। সময়: %s%s।" - "%s: -কে %s বার্তা পাঠানো যায় নি। সময়: %s।" - "%s:-এ %s বার্তা পাঠানো হচ্ছে। সময়: %s।" - "%s: -এ %s বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: %s।" - "%s: -এ %s বার্তা পাঠান। সময়: %s।" - "বার্তা পাঠাতে ব্যর্থ হয়েছে। পুনরায় চেষ্টা করতে স্পর্শ করুন।" - "%s এর সাথে কথোপকথন" - "বিষয় মুছুন" - "ভিডিও ক্যাপচার করুন" - "একটি স্থির চিত্র ক্যাপচার করুন" - "ছবি তুলুন" - "ভিডিও রেকর্ডিং শুরু করুন" - "সম্পূর্ণ স্ক্রীন ক্যামেরাতে স্যুইচ করুন" - "সামনে ও পিছনে ক্যামেরার মধ্যে স্যুইচ করুন" - "রেকর্ডিং করা বন্ধ করুন এবং ভিডিও সংযুক্ত করুন" - "ভিডিও রেকর্ডিং বন্ধ করুন" - "বার্তাপ্রেরণ ফটোগুলি" - - %dটি ফটো \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - %dটি ফটো \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - - - %dটি ভিডিও \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - %dটি ভিডিও \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - - - %dটি সংযুক্তি \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - %dটি সংযুক্তি \"%s\" অ্যালবামে সংরক্ষিত হয়েছে - - - %dটি সংযুক্তি \"ডাউনলোডগুলি\" এ সংরক্ষিত হয়েছে - %dটি সংযুক্তি \"ডাউনলোডগুলি\" এ সংরক্ষিত হয়েছে - - - %dটি সংযুক্তি সংরক্ষণ করা হয়েছে - %dটি সংযুক্তি সংরক্ষণ করা হয়েছে - - - %dটি সংযুক্তি সংরক্ষণ করা যায়নি - %dটি সংযুক্তি সংরক্ষণ করা যায়নি - - "MMS সংযুক্তি সংরক্ষণ করা হয়েছে" - "সেটিংস" - "সংরক্ষণাগারভুক্ত" - "বন্ধ করুন" - "MMS" - "উন্নত" - "ডিবাগ" - "বিজ্ঞপ্তিগুলি" - "আওয়াজ" - "নীরব" - "কম্পন" - "অবরুদ্ধ" - "SMS বিতরণের প্রতিবেদনগুলি" - "আপনি যে SMS পাঠাবেন তার প্রতিটির জন্য পৌঁছে দেওয়ার প্রতিবেদনের অনুরোধ করুন" - "স্বতঃ-উদ্ধার" - "স্বয়ংক্রিয়ভাবে MMS বার্তাগুলি পুনরুদ্ধার করুন" - "রোমিং থাকাকালীন স্বতঃ-উদ্ধার" - "রোমিং-এ থাকার সময় স্বয়ংক্রিয়ভাবে MMS পুনরুদ্ধার করুন" - "গোষ্ঠীর মধ্যে বার্তাপ্রেরণ" - "যখন একাধিক প্রাপক রয়েছে তখন একটি বার্তা পাঠাতে MMS ব্যবহার করুন" - "ডিফল্ট SMS অ্যাপ্লিকেশান" - "ডিফল্ট SMS অ্যাপ্লিকেশান" - - - "আপনার ফোন নম্বর" - "অজানা" - "আউটগোয়িং বার্তার শব্দগুলি" - "ডাম্প SMS" - "পাওয়া SMS এর অপীষ্কার ডেটাকে বহিরাগত সংগ্রহস্থলে জমা করুন" - "ডাম্প MMS" - "গৃহিত MMS এর কাঁচা ডেটাকে বহিরাগত সংগ্রহস্থলে জমা করুন" - "বেতার সতর্কতাগুলি" - "বার্তা বিকল্পগুলি" - "পাঠ্য অনুলিপি করুন" - "বিশদ বিবরণ দেখুন" - "মুছুন" - "ফরওয়ার্ড করুন" - "বার্তার বিশদ বিবরণ" - "প্রকার: " - "পাঠ্য বার্তা" - "মাল্টিমিডিয়া বার্তা" - "প্রেরক: " - "প্রাপক: " - "পাঠানো হয়েছে: " - "গ্রহণ করা হয়েছে: " - "বিষয়: " - "মাপ: " - "অগ্রাধিকার: " - "SIM: " - "উচ্চ" - "স্বাভাবিক" - "নিম্ন" - "SIM %s" - "লুকানো প্রেরক ঠিকানা" - "সংযুক্তি লোড করার সময় বার্তা পাঠানো যাচ্ছে না।" - "সংযুক্তি লোড করা যাবে না। আবার চেষ্টা করুন।" - "নেটওয়ার্ক প্রস্তুত নয়৷ আবার চেষ্টা করুন৷" - "পাঠ্য মুছে দিন" - "পাঠ্য এবং সংখ্যা লেখার মধ্যে স্যুইচ করুন" - "আরো অংশগ্রহণকারী যোগ করুন" - "অংশগ্রহণকারীদের নিশ্চিত করুন" - "নতুন কথোপকথন শুরু করুন" - "এই আইটেমটি নির্বাচন করুন" - "ভিডিও দেখুন" - "ব্যক্তি ও বিকল্পগুলি" - "ডিবাগ" - "ব্যক্তি ও বিকল্পগুলি" - "সাধারণ" - "এই কথোপকথনে থাকা ব্যক্তিগণ" - "একটি কল করুন" - "বার্তা পাঠান" - "%s</small> থেকে <br/><small>from বার্তা পাঠান" - - ফটোগুলি পাঠান - ফটোগুলি পাঠান - - - অডিওগুলি পাঠান - অডিওগুলি পাঠান - - - ভিডিওগুলি পাঠান - ভিডিওগুলি পাঠান - - - পরিচিতি কার্ডগুলি পাঠান - পরিচিতি কার্ডগুলি পাঠান - - - সংযুক্তিগুলি পাঠান - সংযুক্তিগুলি পাঠান - - - পাঠানোর জন্য %dটি সংযুক্তি প্রস্তুত - পাঠানোর জন্য %dটি সংযুক্তি প্রস্তুত - - "প্রতিক্রিয়া পাঠান" - "Google Play স্টোরে দেখুন" - "সংস্করণ তথ্য" - "সংস্করণ %1$s" - "মুক্ত উৎস লাইসেন্সগুলি" - "বিজ্ঞপ্তিগুলি" - "সংযুক্তির সীমা অতিক্রান্ত হয়েছে" - "সংযুক্তি লোড করতে ব্যর্থ হয়েছে।" - "পরিচিতিতে জুড়ুন" - "পরিচিতি জুড়ুন" - "বিষয়" - "বিষয়: " - "%s%s" - "পরিচিতি কার্ড লোড হচ্ছে" - "পরিচিতি কার্ড লোড করা যায়নি" - "পরিচিতি কার্ড দেখুন" - - %dটি পরিচিতি - %dটি পরিচিতি - - "যোগাযোগ কার্ড" - "জন্মদিন" - "টীকাগুলি" - "বার্তা ফরোয়ার্ড করুন" - "উত্তর" - "+১" - "+%d" - "SMS অক্ষম করা হয়েছে" - "পাঠাতে, ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন" - "ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন" - "পরিবর্তন" - "বার্তাগুলি গ্রহণ করতে, আপনার ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন" - "SMS বার্তাগুলি পাঠানোর জন্য কোন পছন্দের SIM নির্বাচিত নেই৷" - "এই অ্যাপ্লিকেশানটি ডিভাইস মালিক দ্বারা অনুমোদিত নয়।" - "ঠিক আছে" - "একটি কথোপকথনে খুব বেশি সংখ্যার অংশগ্রহণকারী" - - অবৈধ পরিচিতিগুলি - অবৈধ পরিচিতিগুলি - - "ক্যামেরা চিত্র লোড করা যায়নি" - "আপনি: " - "%s: " - "খসড়া" - "আপনি নতুন কোনো কথোপকথন শুরু করলে, সেটিকে এই তালিকায় দেখতে পাবেন" - "সংরক্ষিত করা কথোপকথন এখানে প্রদর্শিত হয়" - "কথোপকথনগুলি লোড হচ্ছে…" - "ছবি" - "অডিও ক্লিপ" - "ভিডিও" - "পরিচিতি কার্ড" - "MMS" - "পূর্বাবস্থায় ফিরুন" - "পুনরায় চেষ্টা করুন" - "একটি নতুন বার্তা পাঠাতে একটি পরিচিতি নাম অথবা ফোন নম্বর লিখুন" - "অবরুদ্ধ করুন" - "%s-কে অবরুদ্ধ করুন" - "%s-কে অবরোধ মুক্ত করুন" - "%s-কে অবরুদ্ধ করবেন?" - "আপনি এই নম্বর থেকে অবিরত বার্তা গ্রহণ করতে করবেন কিন্তু আপনাকে আর সূচিত করা হবে না। এই কথোপকথন সংরক্ষণ করা হবে।" - "অবরুদ্ধ পরিচিতিগুলি" - "অবরোধ মুক্ত করুন" - "অবরুদ্ধ পরিচিতিগুলি" - "দস্তাবেজ লাইব্রেরি থেকে চিত্র নির্বাচন করুন" - "বার্তা পাঠানো হচ্ছে" - "বার্তা পাঠানো হয়েছে" - "সেলুলার ডেটা বন্ধ আছে। আপনার সেটিংস চেক করুন।" - "বিমান মোডে বার্তা পাঠানো যাবে না" - "এই বার্তাটি পাঠানো যায়নি" - "বার্তা ডাউনলোড করা হয়েছে" - "সেলুলার ডেটা বন্ধ আছে। আপনার সেটিংস চেক করুন।" - "বিমান মোডে বার্তাগুলি ডাউনলোড করা যাবে না" - "বার্তা ডাউনলোড করা যায়নি" - "শূন্য" - "এক" - "দুই" - "তিন" - "চার" - "পাঁচ" - "ছয়" - "সাত" - "আট" - "নয়" - "%1$s এর মাধ্যমে বার্তা পাঠানো যাবে না, ত্র্রুটি %2$d" - "অজানা পরিষেবা প্রদানকারীর মাধ্যমে বার্তা পাঠানো যাবে না, ত্রুটি %1$d" - "ফরওয়ার্ড: %s" - "বার্তা পাঠানো হয়নি: নেটওয়ার্কে পরিষেবা সক্রিয় নেই" - "বার্তা পাঠানো যায়নি: অবৈধ গন্তব্য ঠিকানা" - "বার্তা পাঠানো যায়নি: অবৈধ বার্তা" - "বার্তা পাঠানো যায়নি: অসমর্থিত বিষয়বস্তু" - "বার্তা পাঠানো যায়নি: অসমর্থিত বার্তা" - "বার্তা পাঠানো হয়নি: খুব বড়" - "নতুন বার্তা" - "দেখুন" - "চিত্র" - "কোনো উপযুক্ত অ্যাপ্লিকেশান পাওয়া যায়নি" - "প্রাপককে সরান" - "নতুন বার্তা" - "বাতিল করুন" - "অ্যাক্সেস পয়েন্ট সম্পাদনা করুন" - "সেট করা নেই" - "নাম" - "APN" - "MMSC" - "MMS প্রক্সি" - "MMS পোর্ট" - "MCC" - "MNC" - "APN প্রকার" - "APN মুছে দিন" - "নতুন APN" - "সংরক্ষণ করুন" - "খারিজ করুন" - "নামের ক্ষেত্রটি খালি থাকতে পারে না।" - "APN খালি থাকতে পারে না।" - "MCC ক্ষেত্রটিকে ৩ ডিজিটের হতে হবে।" - "MNC ক্ষেত্রটিকে ২ অথবা ৩ ডিজিটের হতে হবে।" - "ডিফল্ট APN সেটিংস পুনরুদ্ধার করা হচ্ছে।" - "পুনরায় ডিফল্ট হিসাবে সেট করুন" - "ডিফল্ট APN সেটিংস পুনরায় সেট করা সম্পন্ন হয়েছে।" - "শীর্ষকহীন" - "অ্যাক্সেস পয়েন্টের নামগুলি" - "APNগুলি" - "নতুন APN" - "এই ব্যবহারকারীর জন্য অ্যাক্সেস পয়েন্ট নাম সেটিংস উপলব্ধ নয়" - "ক্লিপবোর্ডে অনুলিপি করবেন?" - "অনুলিপি করুন" - "%s এ" - "সাধারণ" - "আরো বিকল্প" - "সাধারণ সেটিংস" - "উন্নত সেটিংস" - "\"%s\" SIM" - "সকল প্রাপকের কাছে আলাদা আলাদা করে SMS পাঠান৷ শুধুমাত্র আপনিই উত্তরগুলি পাবেন" - "সকল প্রাপকের কাছে একটি নির্দিষ্ট MMS পাঠান" - "অজানা নম্বর" - "নতুন বার্তা" - "নতুন বার্তা৷" - "SIM নির্বাচক" - "%1$s নির্বাচিত, SIM নির্বাচক" - "বিষয় সম্পাদনা করুন" - "SIM নির্বাচন করুন বা বিষয় সম্পাদনা করুন" - "অডিও রেকর্ড করতে স্পর্শ করুন এবং ধরে রাখুন" - "নতুন কথোপকথন শুরু করুন" - "বার্তাপ্রেরণ" - "বার্তাপ্রেরণ তালিকা" - "বার্তাপ্রেরণ" - "নতুন বার্তা" - "কথোপকথনের তালিকা" - "কথোপকথনগুলি লোড হচ্ছে" - "বার্তাগুলি লোড করা হচ্ছে" - "আরো কথোপকথন দেখুন" - "আরো বার্তা দেখুন" - "কথোপকথন মুছে দেওয়া হয়েছে" - "কথোপকথন মুছে ফেলা হয়েছে৷ একটি ভিন্ন বার্তাপ্রেরণ কথোপকথন দেখানোর জন্য স্পর্শ করুন" - "অবরুদ্ধ" - "অবরোধ মুক্ত করা হয়েছে" - "সঞ্চয়স্থান কম রয়েছে৷ কিছু ডেটা হারিয়ে যেতে পারে৷" - "সংযুক্তিগুলি নির্বাচন করুন" - "নির্বাচন নিশ্চিত করুন" - "%dটি নির্বাচন করা হয়েছে" - "একটি অথবা আরও বেশি সংযুক্তি সরিয়ে আবার চেষ্টা করুন।" - "আপনি আপনার বার্তা পাঠানোর চেষ্টা করতে পারেন, কিন্তু আপনি এক বা একাধিক সংযুক্তি অপসারণ না করা পর্যন্ত তা বিতরণ নাও করা হতে পারে।" - "আপনি বার্তা প্রতি শুধুমাত্র একটি ভিডিও পাঠাতে পারেন৷ অতিরিক্ত ভিডিওগুলি সরিয়ে দিয়ে আবার চেষ্টা করুন৷" - "বার্তাপ্রেরণ সংযুক্তি লোড করতে ব্যর্থ হয়েছে৷" - "তবুও পাঠান" - "কথোপকথন শুরু করা যায়নি" - "%1$s (%2$s)" - "%s নির্বাচিত হয়েছে" - diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml deleted file mode 100644 index 69ce724..0000000 --- a/res/values-ca/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sense assumpte" - "nosubject" - - - "Sí" - "No" - "D\'acord" - "Hehe" - "Gràcies" - "Accepto" - "Molt bé" - "Ja vinc." - "D\'acord. Et diré alguna cosa més tard." - ":)" - ":(" - - diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml deleted file mode 100644 index 8923f0c..0000000 --- a/res/values-ca/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Missatges" - "Missatges" - "Selecció de la conversa" - "Configuració" - "Envia el missatge" - "Afegeix un fitxer adjunt" - "Ajuda" - "Et donem la benvinguda" - "Omet" - "Següent >" - "Següent" - "Surt" - "Configuració >" - "Configuració" - "Missatges necessita permís per accedir als SMS, al telèfon i als contactes." - "Pots canviar els permisos a Configuració > Aplicacions > Missatges > Permisos." - "Pots canviar els permisos a Configuració, Aplicacions, Missatges, Permisos." - "Freqüents" - "Tots els contactes" - "Envia al %s" - "Captura fotos o vídeos" - "Tria imatges d\'aquest dispositiu." - "Enregistra l\'àudio" - "Triar una foto" - "S\'ha seleccionat el fitxer multimèdia." - "No hi ha cap fitxer multimèdia seleccionat." - "Seleccionats: %d" - "imatge: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imatge" - "Enregistra l\'àudio" - "Comparteix" - "Ara mateix" - "Ara" - - %d min - %d min - - - %d hores - %d hora - - - %d dies - %d dia - - - %d setmanes - una setmana - - - %d mesos - un mes - - - %d anys - un any - - "Missatge de classe 0" - "Desa" - "Queda poc espai al dispositiu. Per alliberar-ne, Missatgeria suprimirà automàticament els missatges antics." - "L\'espai d\'emmagatzematge s\'està esgotant" - "Pot ser que Missatges no enviï ni rebi missatges fins que no tinguis més espai disponible al dispositiu." - "Poc emmagatzematge per a SMS. És possible que et calgui suprimir missatges." - "Confirma el teu número de telèfon" - "Aquest pas únic garanteix que els missatges de grup s\'enviïn correctament amb Missatges." - "Número de telèfon" - "Suprimeix tots els missatges amb fitxers multimèdia" - "Suprimeix els missatges de més de %s" - "Suprimeix automàticament els missatges de més de %s" - "Ignora" - "Confirmes que vols suprimir tots els missatges amb fitxers multimèdia?" - "Confirmes que vols suprimir els missatges de més de %s?" - "Confirmes que vols suprimir els missatges de més de %s i activar la funció per suprimir-los automàticament?" - "%s ha dit:" - "Has dit:" - "Missatge de: %s" - "Has enviat un missatge." - "S\'està enviant…" - "No s\'ha enviat. Toca per tornar-ho a provar." - "No s\'ha enviat. S\'està tornant a provar…" - "Reenvia o suprimeix" - "Truca als serveis d\'emergència. En aquest moment, el missatge de text no s\'ha pogut enviar." - "S\'ha produït un error." - "Missatge MMS nou per baixar" - "Missatge MMS nou" - "No s\'ha pogut baixar." - "Toca per tornar-ho a provar" - "Toca per baixar" - "Baixa o suprimeix" - "S\'està baixant..." - "El missatge ha caducat o no està disponible." - "mida: %1$s, caducitat: %2$s" - "No es pot enviar. El destinatari no és vàlid." - "El servei no està activat a la xarxa." - "No s\'ha pogut enviar a causa d\'un problema de xarxa." - "El missatge ha caducat o no està disponible." - "(Sense assumpte)" - "Emissor desconegut" - "Entregat" - "No s\'ha pogut baixar el missatge %1$s de %2$s." - "No s\'ha pogut completar l\'operació de la base de dades perquè hi ha poca memòria." - "No s\'ha enviat el missatge." - "Alguns missatges no s\'han enviat a Missatges" - - %d missatges en %d converses - %d missatges en una conversa - - "No s\'ha baixat el missatge" - "Alguns missatges a Missatges no s\'han baixat" - - %d missatges en %d converses - %d missatges en una conversa - - "No s\'ha enviat el missatge al %1$s" - "Truca als serveis d\'emergència. En aquest moment, el missatge de text al %1$s no s\'ha pogut enviar." - - %d missatges nous - Missatge nou - - "Inicia" - "La càmera no està disponible." - "La càmera no està disponible." - "La captura de vídeo no està disponible." - "No es pot desar el fitxer multimèdia." - "No es pot fer una foto." - "Enrere" - "Arxivades" - "Suprimeix" - "Arxiu" - "Treu de l\'arxiu" - "Desactiva les notificacions" - "Activa les notificacions" - "Afegeix un contacte" - "Baixa" - "Envia" - "Suprimeix" - "Vols suprimir aquest missatge?" - "Aquesta acció no es pot desfer." - "Suprimeix" - - Vols suprimir aquestes converses? - Vols suprimir aquesta conversa? - - "Suprimeix" - "Cancel·la" - "Per a" - "Selecciona diverses imatges" - "Confirma la selecció" - "+%d" - "No es pot enregistrar l\'àudio. Torna-ho a provar." - "No es pot reproduir l\'àudio. Torna-ho a provar." - "No s\'ha pogut desar l\'àudio. Torna-ho a provar." - "Toca i mantén premut" - ", " - " " - ": " - " " - "Imatge" - "Clip d\'àudio" - "Vídeo" - "Targeta de contacte" - "Baixa" - "Respon amb un SMS" - "Respon amb un MMS" - "Respon" - - %d participants - %d participant - - "Jo" - "Contactes bloquejats i arxivats" - "Contacte desbloquejat i no arxivat" - "Arxivades: %d" - "No arxivades: %d" - "Notificacions desactivades" - "Notificacions activades" - "Ja està tot llest. Torna a tocar Envia." - "Missatges s\'ha definit correctament com a aplicació predeterminada de SMS." - - Descarta els fitxers adjunts - Descarta el fitxer adjunt - - "Àudio adjunt" - "Reprodueix l\'àudio adjunt" - "Atura" - "Missatge de %s: %s." - "Missatge no enviat de %s: %s. Hora: %s." - "Missatge de %s: %s. Hora: %s." - "Missatge no enviat a %s: %s. Hora: %s" - "S\'està enviant un missatge a %s: %s. Hora: %s." - "Missatge no enviat a %s: %s. Hora: %s." - "Missatge a %s: %s. Hora: %s." - "Missatge no enviat de %s: %s. Hora: %s. %s." - "Missatge de %s: %s. Hora: %s. %s." - "Missatge no enviat a %s: %s. Hora: %s" - "S\'està enviant un missatge a %s: %s. Hora: %s." - "Missatge no enviat a %s: %s. Hora: %s." - "Missatge a %s: %s. Hora: %s." - "Hi ha hagut un problema amb aquest missatge. Toca per tornar-ho a provar." - "Conversa amb %s" - "Suprimeix l\'assumpte" - "Captura un vídeo" - "Captura una imatge fixa" - "Fes una foto" - "Comença a enregistrar vídeo" - "Canvia a la càmera de pantalla completa" - "Canvia entre la càmera frontal i la posterior" - "Deixa d\'enregistrar i adjunta un vídeo" - "Atura l\'enregistrament de vídeo" - "Fotos de missatges" - - S\'han desat %d fotos a l\'àlbum %s - S\'ha desat %d foto a l\'àlbum %s - - - S\'han desat %d vídeos a l\'àlbum %s - S\'ha desat %d vídeo a l\'àlbum %s - - - S\'han desat %d fitxers adjunts a l\'àlbum %s - S\'ha desat %d fitxer adjunt a l\'àlbum %s - - - S\'han desat %d fitxers adjunts a Baixades - S\'ha desat %d fitxer adjunt a Baixades - - - S\'han desat %d fitxers adjunts - S\'ha desat %d fitxer adjunt - - - No s\'han pogut desar %d fitxers adjunts - No s\'ha pogut desar %d fitxer adjunt - - "S\'ha desat el fitxer adjunt MMS." - "Configuració" - "Arxivades" - "Tanca" - "MMS" - "Avançada" - "Depuració" - "Notificacions" - "So" - "Silenci" - "Vibració" - "S\'ha bloquejat" - "Informes d\'entrega de SMS" - "Sol·licita un informe d\'entrega per a cada SMS enviat" - "Recuperació automàtica" - "Recupera MMS automàticament" - "Recupera aut. els MMS en itinerància" - "Recupera MMS automàticament en itinerància" - "Missatge de grup" - "Utilitza MMS per enviar un únic missatge a diversos destinataris" - "Aplicació de SMS predeterminada" - "Aplicació de SMS predeterminada" - - - "Número de telèfon" - "Desconegut" - "Sons per a missatges de sortida" - "Envia el contingut dels SMS" - "Envia les dades sense processar dels SMS rebuts al fitxer d\'emmagatzematge extern." - "Envia el contingut dels MMS" - "Buida les dades sense processar dels MMS rebuts en un fitxer d’emmagatzematge extern" - "Alertes sense fil" - "Opcions del missatge" - "Copia el text" - "Mostra els detalls" - "Suprimeix" - "Reenvia" - "Detalls del missatge" - "Tipus: " - "Missatge de text" - "Missatge multimèdia" - "De: " - "Per a: " - "Enviat: " - "Rebut: " - "Assumpte: " - "Mida: " - "Prioritat: " - "SIM: " - "Alta" - "Normal" - "Baixa" - "SIM %s" - "Amaga l\'adreça del remitent" - "No es pot enviar el missatge durant la càrrega dels fitxers adjunts." - "No s\'ha pogut carregar el fitxer adjunt. Torna-ho a provar." - "La xarxa no està a punt. Torna-ho a provar." - "Suprimeix el text" - "Alterna entre escriure lletres i números" - "Afegeix més participants" - "Confirma els participants" - "Inicia una conversa nova" - "Selecciona aquest element." - "Reprodueix el vídeo." - "Persones i opcions" - "Depura" - "Persones i opcions" - "General" - "Persones en aquesta conversa" - "Fes una trucada" - "Envia el missatge" - "Envia un missatge<br/><small>des de %s</small>." - - Envia les fotos - Envia la foto - - - Envia els àudios - Envia l\'àudio - - - Envia els vídeos - Envia el vídeo - - - Envia les targetes de contacte - Envia la targeta de contacte - - - Envia els fitxers adjunts - Envia el fitxer adjunt - - - Ja es poden enviar %d fitxers adjunts - Ja es pot enviar un fitxer adjunt - - "Envia suggeriments" - "Mostra a Google Play Store" - "Informació de la versió" - "Versió %1$s" - "Llicències de codi obert" - "Notificacions" - "S\'ha assolit el límit de fitxers adjunts" - "No s\'ha pogut carregar el fitxer adjunt." - "Vols afegir-lo als contactes?" - "Afegeix el contacte" - "Assumpte" - "Assumpte: " - "%s%s" - "S\'està carregant la targeta de contacte" - "No s\'ha pogut carregar la targeta de contacte" - "Mostra la targeta de contactes" - - %d contactes - %d contacte - - "Targetes de contacte" - "Natalici" - "Notes" - "Reenvia el missatge" - "Respon" - "+1" - "+%d" - "SMS desactivats" - "Per enviar missatges, defineix Missatges com a aplicació predeterminada de SMS" - "Defineix Missatges com a aplicació predeterminada de SMS" - "Canvia" - "Per rebre missatges, defineix Missatges com a aplicació predeterminada de SMS" - "No s\'ha seleccionat cap SIM preferida per enviar missatges SMS." - "El propietari del dispositiu no admet l\'aplicació." - "D\'acord" - "Hi ha massa participants a la conversa." - - Els contactes no són vàlids - El contacte no és vàlid - - "No s\'ha pogut carregar la imatge de la càmera." - "Tu: " - "%s: " - "Esborrany" - "Quan comencis una conversa nova, es mostrarà aquí." - "Les converses arxivades es mostren aquí." - "S\'estan carregant les converses..." - "Imatge" - "Clip d\'àudio" - "Vídeo" - "Targeta de contacte" - "MMS" - "Desfés" - "Torna-ho a provar" - "Introdueix el nom d\'un contacte o el número de telèfon per escriure un missatge nou." - "Bloqueja" - "Bloqueja %s" - "Desbloqueja %s" - "Vols bloquejar %s?" - "Continuaràs rebent missatges d\'aquest número, però no se t\'enviarà cap notificació. Aquesta conversa s\'arxivarà." - "Contactes bloquejats" - "DESBLOQUEJA" - "Contactes bloquejats" - "Tria una imatge de la biblioteca de documents." - "S\'està enviant el missatge" - "Missatge enviat" - "Les dades del telèfon estan desactivades. Comprova\'n la configuració." - "No es poden enviar missatges amb el mode d\'avió activat." - "No s\'ha pogut enviar el missatge" - "Missatge baixat" - "Les dades del telèfon estan desactivades. Comprova\'n la configuració." - "No es poden baixar missatges en mode d\'avió" - "El missatge no s\'ha pogut baixar" - "Zero" - "Un" - "Dos" - "Tres" - "Quatre" - "Cinc" - "Sis" - "Set" - "Vuit" - "Nou" - "No es pot enviar el missatge amb %1$s, error %2$d" - "No es pot enviar el missatge amb un operador de telefonia mòbil desconegut, error %1$d" - "Rv: %s" - "No s\'ha enviat el missatge; el servei no s\'ha activat a la xarxa." - "Missatge no enviat: adreça de destinació no vàlida" - "Missatge no enviat: missatge no vàlid" - "Missatge no enviat: contingut no admès" - "Missatge no enviat: missatge no admès" - "El missatge no s\'ha enviat: és massa gran" - "Missatge nou" - "Mostra" - "Imatge" - "No s\'ha pogut trobar una aplicació adequada." - "Suprimeix el destinatari." - "Missatge nou" - "Cancel·la" - "Edita el punt d\'accés" - "No definit" - "Nom" - "APN" - "MMSC" - "Servidor intermediari MMS" - "Port per a MMS" - "MCC" - "MNC" - "Tipus d\'APN" - "Suprimeix l\'APN" - "APN nou" - "Desa" - "Descarta" - "El camp Nom no pot quedar buit." - "L\'APN no pot quedar buit." - "El camp MCC ha de tenir 3 dígits." - "El camp MNC ha de tenir 2 o 3 dígits." - "S\'està restaurant la configuració predeterminada d\'APN." - "Restableix al valor predeterminat" - "S\'ha restablert la configuració predeterminada d\'APN." - "Sense títol" - "Noms de punt d\'accés" - "APN" - "APN nou" - "La configuració del nom del punt d\'accés no està disponible per a aquest usuari." - "Ho vols copiar al porta-retalls?" - "Copia" - "per a %s" - "General" - "Avançada" - "Configuració general" - "Configuració avançada" - "SIM %s" - "Envia missatges SMS individuals a tots els destinataris. Només tu rebràs les respostes." - "Envia un MMS individual a tots els destinataris" - "Número desconegut" - "Missatge nou" - "Missatge nou" - "Selector de SIM" - "%1$s seleccionada, selector de SIM" - "Edita l\'assumpte" - "Selecciona la SIM o edita l\'assumpte" - "Mantén premut per enregistrar àudio" - "Inicia una conversa nova" - "Missatges" - "Llista de missatges" - "Missatges" - "Missatge nou" - "Llista de converses" - "S\'estan carregant les converses" - "S\'estan carregant els missatges" - "Mostra més converses" - "Mostra més missatges" - "S\'ha suprimit la conversa." - "S\'ha suprimit la conversa. Toca per mostrar una altra conversa de Missatges" - "Bloquejat" - "Desbloquejat" - "Hi ha poc espai d\'emmagatzematge. És possible que es perdin algunes dades." - "Selecciona els fitxers adjunts" - "Confirma la selecció" - "Seleccionats: %d" - "Suprimeix un o més fitxers adjunts i torna-ho a provar." - "Pots provar d\'enviar el missatge, però és possible que no s\'enviï si no suprimeixes un o més fitxers adjunts." - "Només pots enviar un vídeo per missatge. Suprimeix els vídeos addicionals i torna-ho a provar." - "Missatges no ha pogut carregar el fitxer adjunt." - "Envia igualment" - "No s\'ha pogut iniciar la conversa" - "%1$s (%2$s)" - "%s seleccionada" - diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml deleted file mode 100644 index 95c2775..0000000 --- a/res/values-cs/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "bez předmětu" - "bezpředmětu" - - - "Ano" - "Ne" - "OK" - "Cha cha" - "Díky" - "Souhlasím" - "Hezké" - "Jsem na cestě." - "OK, ozvu se později." - ":)" - ":(" - - diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml deleted file mode 100644 index 8b56e47..0000000 --- a/res/values-cs/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "SMS a MMS" - "SMS a MMS" - "Vybrat konverzaci" - "Nastavení" - "Odeslat zprávu" - "Přidat přílohu" - "Nápověda" - "Vítejte" - "Přeskočit" - "Další >" - "Další" - "Ukončit" - "Nastavení >" - "Nastavení" - "Aplikace SMS a MMS vyžaduje oprávnění k přístupu k SMS, telefonu a kontaktům." - "Oprávnění můžete změnit v Nastavení > Aplikace > SMS a MMS > Oprávnění." - "Oprávnění můžete změnit v Nastavení, Aplikace, SMS a MMS, Oprávnění." - "Časté kontakty" - "Všechny kontakty" - "Odeslat na číslo %s" - "Pořídit fotky nebo video" - "Vyberte obrázky z tohoto zařízení." - "Nahrát zvuk" - "Vybrat fotku" - "Médium je vybráno." - "Výběr média je zrušen." - "Vybráno: %d" - "obrázek %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "obrázek" - "Nahrávání zvuku" - "Sdílet" - "Právě teď" - "Teď" - - %d min - %d min - %d min - %d min - - - %d hodiny - %d hodiny - %d hodin - %d hodina - - - %d dny - %d dne - %d dnů - %d den - - - %d týdny - %d týdne - %d týdnů - jeden týden - - - %d měsíce - %d měsíce - %d měsíců - jeden měsíc - - - %d roky - %d roku - %d let - jeden rok - - "Zpráva třídy 0" - "Uložit" - "V zařízení dochází místo. Aplikace SMS a MMS automaticky smaže starší zprávy, aby uvolnila místo." - "V úložišti je málo místa" - "Aplikace SMS a MMS nebude moci posílat ani přijímat zprávy, dokud v zařízení nebude k dispozici více místa." - "Dochází volné místo na zprávy SMS. Možná bude potřeba nějaké zprávy vymazat." - "Ověřit telefonní číslo" - "Tento jednorázový krok zajistí, že aplikace SMS a MMS správně doručí vaše skupinové zprávy." - "Telefonní číslo" - "Smazat všechny zprávy s médii" - "Smazat zprávy starší než %s" - "Automaticky smazat zprávy starší než %s" - "Ignorovat" - "Vymazat všechny zprávy s médii?" - "Smazat zprávy starší než %s?" - "Chcete smazat zprávy starší než %s a zapnout automatické mazání?" - "%s říká" - "Řekli jste" - "Zpráva od uživatele %s" - "Odeslali jste zprávu" - "Odesílání…" - "Zpráva nebyla odeslána. Klepnutím to zkusíte znovu." - "Zpráva nebyla odeslána. Probíhá další pokus…" - "Znovu odeslat nebo smazat" - "Uskutečněte prosím hlasové volání nouzových služeb. Vaši textovou zprávu v současné době nebylo možné doručit." - "Nezdařilo se" - "Nová zpráva MMS ke stažení" - "Nová zpráva MMS" - "Stažení se nezdařilo." - "Klepnutím to zkusíte znovu" - "Stáhněte klepnutím." - "Stáhnout nebo smazat" - "Stahování…" - "Platnost zprávy vypršela nebo zpráva není k dispozici." - "velikost: %1$s, vypršení platnosti: %2$s" - "Zprávu nelze odeslat. Příjemce není platný." - "Služba není v síti aktivována." - "Zprávu se nepodařilo odeslat z důvodu chyby v síti." - "Platnost zprávy vypršela nebo zpráva není k dispozici." - "(Bez předmětu)" - "Neznámý odesílatel" - "Doručeno" - "Stažení zprávy %1$s od odesílatele %2$s se nezdařilo." - "Databázovou operaci se nepodařilo dokončit z důvodu nedostatku paměti." - "Zprávu se nepodařilo odeslat." - "Několik zpráv v aplikaci SMS a MMS nebylo odesláno." - - Počet zpráv ve %d konverzacích: %d - Počet zpráv v %d konverzace: %d - Počet zpráv v %d konverzacích: %d - Počet zpráv v jedné konverzaci: %d - - "Zprávu se nepodařilo stáhnout" - "Několik zpráv se do aplikace SMS a MMS nepodařilo stáhnout." - - Počet zpráv ve %d konverzacích: %d - Počet zpráv v %d konverzace: %d - Počet zpráv v %d konverzacích: %d - Počet zpráv v jedné konverzaci: %d - - "Zpráva na číslo %1$s nebyla odeslána." - "Uskutečněte prosím hlasové volání nouzových služeb. Vaši textovou zprávu na číslo %1$s v současné době nebylo možné doručit." - - %d nové zprávy - %d nové zprávy - %d nových zpráv - Nová zpráva - - "Spustit" - "Fotoaparát není k dispozici" - "Fotoaparát není k dispozici." - "Záznam videa není k dispozici." - "Média nelze uložit." - "Nelze pořídit fotku" - "Zpět" - "Archivováno" - "Smazat" - "Archivovat" - "Vyjmout z archivu" - "Vypnout oznámení" - "Zapnout oznámení" - "Přidat kontakt" - "Stáhnout" - "Odeslat" - "Smazat" - "Smazat tuto zprávu?" - "Tuto akci nelze vrátit zpět." - "Smazat" - - Smazat tyto konverzace? - Smazat tyto konverzace? - Smazat tyto konverzace? - Smazat tuto konverzaci? - - "Smazat" - "Zrušit" - "Komu" - "Vybrat několik obrázků" - "Potvrďte výběr" - "+%d" - "Zvuk nelze zaznamenat. Zkuste to znovu." - "Zvuk nelze přehrát. Zkuste to znovu." - "Zvuk nelze uložit. Zkuste to znovu." - "Dotyk s podržením" - ", " - " " - ": " - " " - "Fotografie" - "Zvukový klip" - "Video" - "Karta kontaktu" - "Stáhnout" - "Odpovědět – SMS" - "Odpovědět v MMS" - "Odpovědět" - - %d účastníci - %d účastníka - %d účastníků - %d účastník - - "Já" - "Kontakt byl zablokován a archivován." - "Kontakt byl odblokován a jeho archivace byla zrušena" - "Archivováno: %d" - "Vyjmuto z archivu: %d" - "Oznámení jsou vypnuta" - "Oznámení jsou zapnuta" - "Vše je připraveno. Znovu klepněte na Odeslat." - "Aplikace SMS a MMS byla úspěšně nastavena jako výchozí aplikace pro SMS." - - Zahodit přílohy - Zahodit přílohy - Zahodit přílohy - Zahodit přílohu - - "Zvuková příloha" - "Přehrát zvukovou přílohu" - "Pozastavit" - "Zpráva od uživatele %s: %s" - "Neúspěšná zpráva od uživatele %s: %s. Čas: %s." - "Zpráva od uživatele %s: %s. Čas: %s." - "Neodeslaná zpráva pro uživatele %s: %s. Čas: %s." - "Odesílání zprávy uživateli %s: %s. Čas: %s." - "Neúspěšná zpráva pro uživatele %s: %s. Čas: %s." - "Zpráva pro uživatele %s: %s. Čas: %s." - "Neúspěšná zpráva od uživatele %s: %s. Čas: %s. %s." - "Zpráva od uživatele %s: %s. Čas: %s. %s." - "Neodeslaná zpráva pro uživatele %s: %s. Čas: %s." - "Odesílání zprávy uživateli %s: %s. Čas: %s." - "Neúspěšná zpráva pro uživatele %s: %s. Čas: %s." - "Zpráva pro uživatele %s: %s. Čas: %s." - "Neúspěšná zpráva. Klepnutím pokus opakujte." - "Konverzace s uživateli %s" - "Smazat předmět" - "Natočit video" - "Pořídit statický snímek" - "Vyfotit" - "Začít natáčet video" - "Přepnout na fotoaparát na celou obrazovku" - "Přepnout mezi předním a zadním fotoaparátem" - "Zastavit nahrávání a připojit video" - "Zastavit záznam videa" - "Fotky z aplikace SMS a MMS" - - %d fotky byly uloženy do alba %s. - %d fotky bylo uloženo do alba %s. - %d fotek bylo uloženo do alba %s. - %d fotka byla uložena do alba %s. - - - %d videa byla uložena do alba %s. - %d videa bylo uloženo do alba %s. - %d videí bylo uloženo do alba %s. - %d video bylo uloženo do alba %s. - - - %d přílohy byly uloženy do alba %s. - %d přílohy bylo uloženo do alba %s. - %d příloh bylo uloženo do alba %s. - %d příloha byla uložena do alba %s. - - - %d přílohy byly uloženy do složky stažených položek. - %d přílohy bylo uloženo do složky stažených položek. - %d příloh bylo uloženo do složky stažených položek. - %d příloha byla uložena do složky stažených položek. - - - Uloženy %d přílohy - Uloženo %d přílohy - Uloženo %d příloh - Uložena %d příloha - - - %d přílohy se nepodařilo uložit - %d přílohy se nepodařilo uložit - %d příloh se nepodařilo uložit - %d přílohu se nepodařilo uložit - - "Příloha MMS byla uložena" - "Nastavení" - "Archivováno" - "Zavřít" - "MMS" - "Rozšířená nastavení" - "Ladit" - "Oznámení" - "Zvuk" - "Tichý režim" - "Vibrace" - "Zablokováno" - "Zprávy o doručení SMS" - "U každé odeslané SMS požadovat potvrzení o doručení" - "Automatické načítání" - "Automaticky načítat zprávy MMS" - "Autom. načítání při roamingu" - "Při roamingu automaticky načítat zprávy MMS" - "Skupinové zprávy" - "Použít MMS k odeslání jedné zprávy, pokud má několik příjemců" - "Výchozí aplikace SMS" - "Výchozí aplikace SMS" - - - "Vaše telefonní číslo" - "Neznámé" - "Zvuky odchozích zpráv" - "Vypsání zpráv SMS" - "Vypsat nezpracovaná data přijatých zpráv SMS do souboru externího úložiště" - "Vypsání zpráv MMS" - "Vypsat nezpracovaná data přijatých zpráv MMS do souboru externího úložiště" - "Bezdrátová upozornění" - "Možnosti zprávy" - "Kopírovat text" - "Zobrazit podrobnosti" - "Smazat" - "Přeposlat" - "Podrobnosti zprávy" - "Typ: " - "Textová zpráva" - "Multimediální zpráva" - "Od: " - "Komu: " - "Odesláno: " - "Přijato: " - "Předmět: " - "Velikost: " - "Priorita: " - "SIM karta: " - "Vysoká" - "Normální" - "Nízká" - "SIM karta %s" - "Skrytá adresa odesílatele" - "Při načítání příloh nelze odeslat zprávu." - "Přílohu nelze načíst. Zkuste to znovu." - "Síť není připravena. Zkuste to znovu." - "Smazat text" - "Přepnutí mezi zadáváním textu a čísel" - "Přidat další účastníky" - "Potvrdit účastníky" - "Zahájit novou konverzaci" - "Vybrat tuto položku" - "Přehrát video" - "Lidé a možnosti" - "Ladit" - "Lidé a možnosti" - "Obecné" - "Lidé v této konverzaci" - "Uskutečnit hovor" - "Poslat zprávu" - "Odeslat zprávu<br/><small>ze SIM karty %s</small>" - - Odeslat fotky - Odeslat fotky - Odeslat fotky - Odeslat fotku - - - Odeslat zvukové nahrávky - Odeslat zvukové nahrávky - Odeslat zvukové nahrávky - Odeslat zvukovou nahrávku - - - Odeslat videa - Odeslat videa - Odeslat videa - Odeslat video - - - Odeslat vizitky - Odeslat vizitky - Odeslat vizitky - Odeslat vizitku - - - Odeslat přílohy - Odeslat přílohy - Odeslat přílohy - Odeslat přílohu - - - %d přílohy jsou připraveny k odeslání - %d přílohy je připraveno k odeslání - %d příloh je připraveno k odeslání - Jedna příloha je připravena k odeslání - - "Odeslat zpětnou vazbu" - "Zobrazit v Obchodě Google Play" - "Informace o verzi" - "Verze %1$s" - "Licence open source" - "Oznámení" - "Byl dosažen maximální počet příloh" - "Načtení přílohy se nezdařilo." - "Přidat do kontaktů?" - "Přidat kontakt" - "Předmět" - "Předmět: " - "%s%s" - "Načítání kontaktní karty" - "Kartu kontaktu nelze načíst." - "Zobrazit kontaktní kartu" - - %d kontakty - %d kontaktu - %d kontaktů - %d kontakt - - "Karty kontaktů" - "Narozeniny" - "Poznámky" - "Přeposlat zprávu" - "Odpovědět" - "+1" - "+%d" - "Zprávy SMS jsou zakázány" - "Chcete-li odeslat zprávu, nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS." - "Nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS." - "Změnit" - "Chcete-li dostávat zprávy, nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS." - "Není vybrána preferovaná SIM karta pro odesílání zpráv SMS." - "Tuto aplikaci vlastník zařízení nepovolil." - "OK" - "Konverzace obsahuje příliš mnoho účastníků" - - Neplatné kontakty - Neplatné kontakty - Neplatné kontakty - Neplatný kontakt - - "Obraz z fotoaparátu nelze načíst." - "Vy: " - "%s: " - "Koncept" - "Zde budou uvedeny nové konverzace" - "Zde se zobrazují archivované konverzace." - "Načítání konverzací…" - "Fotografie" - "Zvukový klip" - "Video" - "Karta kontaktu" - "MMS" - "Vrátit zpět" - "Opakovat" - "Chcete-li začít psát novou zprávu, zadejte jméno kontaktu nebo telefonní číslo." - "Blokovat" - "Blokovat %s" - "Odblokovat kontakt %s" - "Blokovat %s?" - "Zprávy z tohoto čísla budete i nadále dostávat, ale nebude se pro ně již zobrazovat oznámení. Tato konverzace bude archivována." - "Blokované kontakty" - "ODBLOKOVAT" - "Blokované kontakty" - "Vyberte obrázek z knihovny dokumentů." - "Odesílání zprávy" - "Zpráva byla odeslána" - "Mobilní datové přenosy jsou vypnuty. Zkontrolujte svá nastavení." - "V režimu Letadlo zprávy nelze odeslat." - "Zprávu nelze odeslat" - "Zpráva byla stažena" - "Mobilní datové přenosy jsou vypnuty. Zkontrolujte svá nastavení." - "V režimu Letadlo zprávy nelze stahovat" - "Zprávu nelze stáhnout" - "Nula" - "Jedna" - "Dvě" - "Tři" - "Čtyři" - "Pět" - "Šest" - "Sedm" - "Osm" - "Devět" - "Zprávu prostřednictvím operátora %1$s nelze odeslat. Chyba %2$d." - "Zprávu prostřednictvím neznámého operátora nelze odeslat. Chyba %1$d." - "Fwd: %s" - "Zpráva nebyla odeslána: služba není v síti aktivována" - "Zpráva nebyla odeslána: neplatná cílová adresa" - "Zpráva nebyla odeslána: neplatná zpráva" - "Zpráva nebyla odeslána: nepodporovaný obsah" - "Zpráva nebyla odeslána: nepodporovaná zpráva" - "Zpráva nebyla odeslána, protože je příliš velká" - "Nová zpráva" - "Zobrazit" - "Obrázek" - "Vhodná aplikace nebyla nalezena." - "Odebrat příjemce" - "Nová zpráva" - "Zrušit" - "Upravit přístupový bod" - "Nenastaveno" - "Název" - "Název přístupového bodu (APN)" - "MMSC" - "Proxy server pro MMS" - "Port pro MMS" - "MCC" - "MNC" - "Typ APN" - "Smazat APN" - "Nový název přístupového bodu (APN)" - "Uložit" - "Zahodit" - "Pole Název nesmí být prázdné." - "Název přístupového bodu (APN) nesmí být prázdný." - "Pole MCC musí obsahovat alespoň 3 číslice." - "Pole MNC musí obsahovat 2 nebo 3 číslice." - "Obnovování výchozích nastavení (APN)" - "Obnovit výchozí" - "Obnovení výchozích nastavení APN bylo dokončeno." - "Bez názvu" - "Názvy přístupových bodů" - "Názvy přístupových bodů (APN)" - "Nový název přístupového bodu (APN)" - "Nastavení názvu přístupového budu pro tohoto uživatele není dostupné." - "Kopírovat do schránky?" - "Kopírovat" - "na SIM kartu %s" - "Obecné" - "Rozšířená nastavení" - "Obecná nastavení" - "Rozšířená nastavení" - "SIM karta %s" - "Odesílat všem příjemcům jednotlivé zprávy SMS. Odpovědi dostanete pouze vy." - "Odeslat všem příjemcům jednu zprávu MMS" - "Neznámé číslo" - "Nová zpráva" - "Nová zpráva" - "Výběr SIM karty" - "Výběr SIM karty (vybrána SIM karta %1$s)" - "Upravit předmět" - "Vybrat SIM kartu nebo upravit předmět" - "Zvuk zaznamenáte klepnutím a podržením" - "Zahájit novou konverzaci" - "SMS a MMS" - "Seznam SMS a MMS" - "SMS a MMS" - "Nová zpráva" - "Seznam konverzací" - "Načítání konverzací" - "Načítání zpráv" - "Zobrazit další konverzace" - "Zobrazit další zprávy" - "Konverzace byla smazána" - "Konverzace byla smazána. Klepnutím zobrazíte jinou konverzaci v aplikaci SMS a MMS." - "Zablokováno" - "Odblokováno" - "V úložišti je málo místa. Některá data mohou být ztracena." - "Vybrat přílohy" - "Potvrďte výběr" - "Vybráno: %d" - "Odstraňte prosím alespoň jednu přílohu a zkuste to znovu." - "Zprávu můžete zkusit odeslat, avšak může se stát, že nebude doručena, dokud neodstraníte alespoň jednu přílohu." - "Ve zprávě lze odeslat pouze jedno video. Odstraňte ostatní videa a zkuste to znovu." - "Načtení přílohy v aplikaci SMS a MMS se nezdařilo." - "Přesto odeslat" - "Konverzaci nelze zahájit" - "%1$s (%2$s)" - "Vybráno: %s" - diff --git a/res/values-da/arrays.xml b/res/values-da/arrays.xml deleted file mode 100644 index d40336a..0000000 --- a/res/values-da/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "intet emne" - "nosubject" - - - "Ja" - "Nej" - "OK" - "Haha" - "Tak" - "Jeg er enig" - "Sådan!" - "Er på vej" - "Jeg vender tilbage til dig lidt senere" - ":)" - ":(" - - diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml deleted file mode 100644 index 340c28f..0000000 --- a/res/values-da/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Beskeder" - "Beskeder" - "Vælg en samtale" - "Indstillinger" - "Send besked" - "Tilføj en vedhæftet fil" - "Hjælp" - "Velkommen" - "Spring over" - "Næste >" - "Næste" - "Afslut" - "Indstillinger >" - "Indstillinger" - "Beskeder skal have tilladelse for at kunne anvende Sms, Telefon og Kontaktpersoner." - "Du kan ændre tilladelser i Indstillinger > Apps > Beskeder > Tilladelser." - "Du kan ændre tilladelser under Indstillinger, Apps, Beskeder, Tilladelser." - "Mest brugte" - "Alle kontaktpersoner" - "Send til %s" - "Optag billeder eller video" - "Vælg billeder fra denne enhed" - "Optag lyd" - "Vælg billede" - "Medierne er valgt." - "Medierne er fravalgt." - "Der er valgt %d" - "billede %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "billede" - "Optag lyd" - "Del" - "Lige nu" - "Nu" - - %d min. - %d min. - - - %d timer - %d timer - - - %d dage - %d dage - - - %d uger - %d uger - - - %d måneder - %d måneder - - - %d år - %d år - - "Klasse 0-besked" - "Gem" - "Enheden er ved at løbe tør for plads. Ældre beskeder bliver automatisk slettet for at frigøre plads." - "Der er snart ikke mere lagerplads" - "Beskeder sender eller modtager muligvis ikke beskeder, før der er mere ledig plads på din enhed." - "Begrænset lagerplads til sms. Det kan være nødvendigt at slette beskeder." - "Bekræft dit telefonnummer" - "Dette engangstrin sikrer, at Beskeder leverer dine gruppebeskeder på korrekt vis." - "Telefonnummer" - "Slet alle beskeder med medier" - "Slet beskeder, der er mere end %s gamle" - "Slet automatisk beskeder, der er mere end %s gamle" - "Ignorer" - "Slet alle beskeder, der indeholder medier?" - "Vil du slette beskeder, der er mere end %s gamle?" - "Vil du slette beskeder, der er ældre end %s, og aktiverr automatisk sletning?" - "%s sagde" - "Du sagde" - "Besked fra %s" - "Du sendte en besked" - "Sender…" - "Ikke sendt. Tryk for at prøve igen." - "Ikke sendt. Prøver igen…" - "Send igen eller slet" - "Ring op til alarmcentralen. Din sms-besked kan ikke leveres på nuværende tidspunkt." - "Mislykkedes" - "Ny MMS-besked til download" - "Ny MMS-besked" - "Download mislykkedes" - "Tryk for at prøve igen" - "Tryk for at downloade" - "Download eller slet" - "Downloader…" - "Beskeden er udløbet eller ikke tilgængelig" - "størrelse: %1$s, udløb: %2$s" - "Beskeden kan ikke sendes. Ugyldig modtager." - "Tjenesten er ikke aktiveret på netværket" - "Et netværksproblem er skyld i, at afsendelsen mislykkedes" - "Beskeden er udløbet eller ikke tilgængelig" - "(Intet emne)" - "Ukendt afsender" - "Leveret" - "Beskeden %1$s blev ikke downloadet fra %2$s." - "Databasehandlingen blev ikke udført, fordi hukommelsen er for lille" - "Beskeden blev ikke sendt" - "Nogle beskeder blev ikke sendt i Beskeder" - - %d beskeder i %d samtaler - %d beskeder i %d samtaler - - "Beskeden blev ikke downloadet" - "Nogle beskeder blev ikke downloadet i Beskeder" - - %d beskeder i %d samtaler - %d beskeder i %d samtaler - - "Beskeden til %1$s blev ikke sendt" - "Ring op til alarmcentralen. Din sms-besked til %1$s kan ikke leveres på nuværende tidspunkt." - - %d nye beskeder - %d nye beskeder - - "Start" - "Kameraet er ikke tilgængeligt" - "Kameraet er ikke tilgængeligt" - "Videooptagelse er ikke tilgængelig" - "Medie kan ikke gemmes" - "Der kan ikke tages billeder" - "Tilbage" - "Arkiveret" - "Slet" - "Arkivér" - "Fjern fra arkiv" - "Deaktiver underretninger" - "Aktivér underretninger" - "Tilføj kontaktperson" - "Download" - "Send" - "Slet" - "Vil du slette denne besked?" - "Denne handling kan ikke fortrydes." - "Slet" - - Vil du slette disse samtaler? - Vil du slette disse samtaler? - - "Slet" - "Annuller" - "Til" - "Vælg flere billeder" - "Bekræft valg" - "+ %d" - "Der kan ikke optages lyd. Prøv igen." - "Lyden kan ikke afspilles. Prøv igen." - "Lyden kunne ikke gemmes. Prøv igen." - "Tryk og hold nede" - ", " - " " - ": " - " " - "Billede" - "Lydklip" - "Video" - "Visitkort" - "Download" - "Svar via sms" - "Svar via mms" - "Besvar" - - %d deltagere - %d deltagere - - "Mig" - "Kontakten er blokeret og arkiveret" - "Kontaktpersonen er ikke længere blokeret og er fjernet fra arkivet" - "%d blev arkiveret" - "%d er blevet fjernet fra arkivet." - "Underretninger er slået fra" - "Underretninger er slået til" - "Alt er klart. Tryk på Send igen." - "Beskeder blev angivet som standardappen til sms-beskeder." - - Kassér vedhæftede filer - Kassér vedhæftede filer - - "Vedhæftet lydfil" - "Afspil vedhæftet lydfil" - "Sæt på pause" - "Besked fra %s: %s." - "Mislykket besked fra %s: %s. Tidspunkt: %s." - "Besked fra %s: %s. Tidspunkt: %s." - "Ikke-afsendt besked til %s: %s. Tidspunkt: %s." - "Sender besked til %s: %s. Tidspunkt: %s." - "Mislykket besked til %s: %s. Tidspunkt: %s." - "Besked til %s: %s. Tidspunkt: %s." - "Mislykket besked fra %s: %s. Tidspunkt: %s. %s." - "Besked fra %s: %s. Tidspunkt: %s. %s." - "Ikke-afsendt besked til %s: %s. Tidspunkt: %s." - "Sender besked til %s: %s. Tidspunkt: %s." - "Mislykket besked til %s: %s. Tidspunkt: %s." - "Besked til %s: %s. Tidspunkt: %s." - "Mislykket besked. Tryk for at prøve igen." - "Samtale med %s" - "Slet emne" - "Optag video" - "Tag et stillbillede" - "Tag et billede" - "Start videooptagelse" - "Skift til kamera i fuld skærm" - "Skift mellem front- og bagudvendt kamera" - "Stop optagelsen, og vedhæft video" - "Stands optagelse af video" - "Billeder i Beskeder" - - %d billeder blev gemt i albummet \"%s\" - %d billeder blev gemt i albummet \"%s\" - - - %d videoer blev gemt i albummet \"%s\" - %d videoer blev gemt i albummet \"%s\" - - - %d vedhæftede filer blev gemt i albummet \"%s\" - %d vedhæftede filer blev gemt i albummet \"%s\" - - - %d vedhæftede filer blev gemt under \"Downloads\" - %d vedhæftede filer blev gemt under \"Downloads\" - - - %d vedhæftede filer blev gemt - %d vedhæftede filer blev gemt - - - %d vedhæftede filer kunne ikke gemmes - %d vedhæftede filer kunne ikke gemmes - - "Den vedhæftede MMS blev gemt" - "Indstillinger" - "Arkiveret" - "Luk" - "Mms" - "Avanceret" - "Fejlretning" - "Underretninger" - "Lyd" - "Lydløs" - "Vibrer" - "Blokeret" - "SMS-leveringsrapporter" - "Anmod om leveringsrapporter for alle de sms-beskeder, du sender" - "Automatisk hentning" - "Hent automatisk mms-beskeder" - "Automatisk hentning under roaming" - "Hent automatisk mms under roaming" - "Gruppebeskeder" - "Brug mms til at sende en enkelt besked, når der er flere modtagere" - "Standardapp til sms-beskeder" - "Standardapp til sms-beskeder" - - - "Dit telefonnummer" - "Ukendt" - "Lyde for udgående besked" - "Gem sms" - "Gem rådata fra modtagne sms-beskeder i en fil på et eksternt lager" - "Gem mms" - "Gem rådata fra modtagne mms-beskeder i en fil på et eksternt lager" - "Trådløse alarmer" - "Indstillinger for beskeder" - "Kopiér tekst" - "Se info" - "Slet" - "Videresend" - "Beskedinfo" - "Type: " - "Sms" - "Mms" - "Fra: " - "Til: " - "Sendt: " - "Modtaget: " - "Emne: " - "Størrelse: " - "Prioritet: " - "SIM-kort: " - "Høj" - "Normal" - "Lav" - "SIM-kort %s" - "Skjult afsenderadresse" - "Beskeden kan ikke sendes, mens der indlæses vedhæftede filer." - "Den vedhæftede fil kunne ikke indlæses. Prøv igen." - "Netværket er ikke klar. Prøv igen." - "Slet tekst" - "Skift mellem indtastning af tekst og tal" - "Tilføj flere deltagere" - "Bekræft deltagere" - "Start en ny samtale" - "Vælg dette element" - "Afspil video" - "Personer og valgmuligheder" - "Fejlretning" - "Personer og valgmuligheder" - "Generelt" - "Personer i denne samtale" - "Foretag et opkald" - "Send besked" - "Send besked<br/><small>fra %s</small>" - - Send billeder - Send billeder - - - Send lydoptagelser - Send lydoptagelser - - - Send videoer - Send videoer - - - Send visitkort - Send visitkort - - - Send vedhæftede filer - Send vedhæftede filer - - - %d vedhæftede filer er klar til at blive sendt - %d vedhæftede filer er klar til at blive sendt - - "Send feedback" - "Se i Google Play Butik" - "Oplysninger om versionen" - "Version %1$s" - "Open source-licenser" - "Underretninger" - "Grænsen for vedhæftede filer er nået" - "Den vedhæftede fil kunne ikke indlæses." - "Vil du føje dette nummer til Kontaktpersoner?" - "Tilføj kontakt" - "Emne" - "Emne: " - "%s%s" - "Kontaktkortet indlæses" - "Kontaktkortet kunne ikke indlæses" - "Se visitkortet" - - %d kontaktpersoner - %d kontaktpersoner - - "Kontaktkort" - "Fødselsdag" - "Noter" - "Videresend besked" - "Svar" - "+1" - "+%d" - "Sms er slået fra" - "Angiv Beskeder som standardappen til sms-beskeder for at sende beskeder" - "Angiv Beskeder som standardappen til sms-beskeder" - "Skift" - "Angiv Beskeder som standardappen til sms-beskeder for at modtage beskeder" - "Der er ikke valgt et foretrukket simkort til afsendelse af sms-beskeder" - "Denne app er ikke tilladt af enhedens ejer." - "OK" - "Der er for mange deltagere i en samtale" - - Ugyldige kontaktpersoner - Ugyldige kontaktpersoner - - "Kamerabilledet kunne ikke indlæses" - "Dig: " - "%s: " - "Kladde" - "Når du starter en ny samtale, vises den her" - "Arkiverede samtaler vises her" - "Indlæser samtaler ..." - "Billede" - "Lydklip" - "Video" - "Visitkort" - "Mms" - "Fortryd" - "Prøv igen" - "Indtast navnet på en kontaktperson eller et telefonnummer for at starte på en ny besked" - "Bloker" - "Bloker %s" - "Ophæv blokering af %s" - "Vil du blokere %s?" - "Du vil fortsat modtage beskeder fra dette nummer, men du bliver ikke længere underrettet. Denne samtale arkiveres." - "Blokerede kontakter" - "OPHÆV BLOKERING" - "Blokerede kontakter" - "Vælg billede fra dokumentbiblioteket" - "Sender besked" - "Beskeden er sendt" - "Mobildata er slået fra. Kontrollér dine indstillinger." - "Der kan ikke sendes beskeder i flytilstand" - "Beskeden blev ikke sendt" - "Beskeden blev downloadet" - "Mobildata er slået fra. Kontrollér dine indstillinger." - "Beskeder kan ikke downloades i flytilstand" - "Beskeden kunne ikke downloades" - "Nul" - "Et" - "To" - "Tre" - "Fire" - "Fem" - "Seks" - "Syv" - "Otte" - "Ni" - "Der kan ikke sendes en besked med %1$s, fejl %2$d" - "Der kan ikke sendes en besked med ukendt mobilselskab, fejl %1$d" - "Vs: %s" - "Beskeden blev ikke sendt: Tjenesten er ikke aktiveret på netværket" - "Beskeden blev ikke sendt: Destinationsadressen er ugyldig" - "Beskeden blev ikke sendt: Beskeden er ugyldig" - "Beskeden blev ikke sendt: Indholdet understøttes ikke" - "Beskeden blev ikke sendt: Beskeden understøttes ikke" - "Beskeden blev ikke sendt, da den er for stor" - "Ny besked" - "Vis" - "Billede" - "Vi kunne ikke finde en passende applikation" - "Fjern modtager" - "Ny besked" - "Annuller" - "Rediger adgangspunkt" - "Ikke angivet" - "Navn" - "Adgangspunkt" - "MMSC" - "MMS-proxy" - "MMS-port" - "MCC" - "MNC" - "Adgangspunktstype" - "Slet adgangspunkt" - "Nyt adgangspunkt" - "Gem" - "Kassér" - "Feltet Navn skal udfyldes." - "Du skal angive et adgangspunkt." - "Feltet MCC skal indeholde tre cifre." - "Feltet MNC skal indeholde to eller tre cifre." - "Gendanner standardindstillingerne for adgangspunktet." - "Nulstil til standard" - "Standardindstillingerne for adgangspunktet er nulstillet." - "Ikke-navngivet" - "Adgangspunkter" - "Adgangspunkter" - "Nyt adgangspunkt" - "Indstillingerne for adgangspunkt (APN) er ikke tilgængelige for denne bruger" - "Vil du kopiere til udklipsholder?" - "Kopiér" - "til %s" - "Generelt" - "Avanceret" - "Generelle indstillinger" - "Avancerede indstillinger" - "Indstillinger for SIM-kortet \"%s\"" - "Send individuelle sms-beskeder til alle modtagere. Der kan kun sendes svar til dig" - "Send en enkelt mms-besked til alle modtagere" - "Ukendt nummer" - "Ny besked" - "Ny besked." - "SIM-vælger" - "%1$s er valgt. SIM-vælger" - "Rediger emne" - "Vælg SIM-kort, eller rediger emne" - "Tryk og hold nede for at optage lyd" - "Start en ny samtale" - "Beskeder" - "Liste i Beskeder" - "Beskeder" - "Ny besked" - "Samtaleliste" - "Indlæser samtaler" - "Indlæser beskeder" - "Se flere samtaler" - "Se flere beskeder" - "Samtalen er slettet" - "Samtalen blev slettet. Tryk for at se en anden samtale i Beskeder." - "Blokeret" - "Blokering fjernet" - "Der er for lidt lagerplads. Nogle data gemmes muligvis ikke." - "Vælg vedhæftede filer" - "Bekræft valg" - "Der er valgt %d" - "Fjern en eller flere vedhæftede filer, og prøv igen." - "Du kan prøve at sende din besked, men den leveres muligvis ikke, medmindre du fjerner en eller flere vedhæftede filer." - "Du kan kun sende én video pr besked. Fjern flere videoer, og prøv igen." - "Beskeder kunne ikke indlæse den vedhæftede fil." - "Send alligevel" - "Samtalen kunne ikke startes" - "%1$s (%2$s)" - "%s er valgt" - diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml deleted file mode 100644 index c3e971f..0000000 --- a/res/values-de/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "kein Betreff" - "Kein Betreff" - - - "Ja" - "Nein" - "OK" - "Hehe" - "Vielen Dank!" - "Ich stimme zu." - "Wunderbar" - "Bin auf dem Weg." - "OK, ich melde mich später." - ":)" - ":(" - - diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml deleted file mode 100644 index 88b95c3..0000000 --- a/res/values-de/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "SMS/MMS" - "SMS/MMS" - "Konversation auswählen" - "Einstellungen" - "Nachricht senden" - "Anhang hinzufügen" - "Hilfe" - "Willkommen" - "Überspringen" - "Weiter >" - "Weiter" - "Beenden" - "Einstellungen >" - "Einstellungen" - "Die SMS/MMS App benötigt Berechtigungen für SMS, Telefon und Kontakte." - "Sie können die Berechtigungen unter \"Einstellungen\" > \"Apps\" > \"SMS/MMS\" > \"Berechtigungen\" ändern." - "Sie können die Berechtigungen unter \"Einstellungen\" > \"Apps\" > \"SMS/MMS\" > \"Berechtigungen\" ändern." - "Häufig genutzte Kontakte" - "Alle Kontakte" - "An %s senden" - "Bilder oder Videos aufnehmen" - "Bilder von diesem Gerät auswählen" - "Audio aufzeichnen" - "Foto auswählen" - "Die Medien sind ausgewählt." - "Die Auswahl der Medien ist aufgehoben." - "%d ausgewählt" - "Bild: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "Bild" - "Audio aufzeichnen" - "Teilen" - "Gerade eben" - "Jetzt" - - %d min - %d min - - - %d Stunden - %d Stunde - - - %d Tage - %d Tag - - - %d Wochen - eine Woche - - - %d Monate - ein Monat - - - %d Jahre - ein Jahr - - "Nachricht Klasse 0" - "Speichern" - "Das Gerät verfügt über wenig Speicherplatz. Die SMS/MMS App löscht ältere Nachrichten automatisch, um Speicherplatz freizugeben." - "Der Speicherplatz wird knapp" - "Die SMS/MMS App versendet oder empfängt möglicherweise keine Nachrichten, bis mehr Speicherplatz auf Ihrem Gerät zur Verfügung steht." - "Geringer SMS-Speicherplatz. Sie müssen eventuell Nachrichten löschen." - "Telefonnummer bestätigen" - "Mit dieser einmaligen Eingabe stellen Sie sicher, dass die SMS/MMS App Ihre Gruppennachrichten korrekt sendet." - "Telefonnummer" - "Alle Nachrichten mit Medien löschen" - "Nachrichten löschen, die älter als %s sind" - "Nachrichten, die älter als %s sind, automatisch löschen" - "Ignorieren" - "Alle Nachrichten mit Medien löschen?" - "Nachrichten löschen, die älter als %s sind?" - "Nachrichten löschen, die älter als %s sind, und automatisches Löschen aktivieren?" - "%s hat Folgendes gesagt:" - "Sie haben Folgendes gesagt:" - "Nachricht von %s" - "Sie haben eine Nachricht gesendet." - "Nachricht wird gesendet…" - "Nachricht wurde nicht gesendet. Zum erneuten Senden tippen." - "Nachricht wurde nicht gesendet. Neuer Versuch…" - "Erneut senden oder löschen" - "Bitte kontaktieren Sie die Notfalldienste per Sprachanruf. Ihre SMS konnte dieses Mal nicht zugestellt werden." - "Fehler" - "Neue MMS zum Herunterladen" - "Neue MMS" - "Fehler beim Download" - "Für neuen Versuch hier tippen" - "Zum Herunterladen tippen" - "Herunterladen oder löschen" - "Nachricht wird heruntergeladen…" - "Nachricht abgelaufen oder nicht verfügbar" - "Größe: %1$s, läuft ab: %2$s" - "Nachricht kann nicht gesendet werden. Der Empfänger ist ungültig." - "Dienst im Netzwerk nicht aktiviert" - "Nachricht kann aufgrund eines Netzwerkproblems nicht gesendet werden." - "Nachricht abgelaufen oder nicht verfügbar" - "(Kein Betreff)" - "Unbekannter Absender" - "Zugestellt" - "Nachricht \"%1$s\" von %2$s konnte nicht heruntergeladen werden." - "Datenbankoperation konnte aufgrund unzureichenden Speichers nicht abgeschlossen werden." - "Nachricht wurde nicht gesendet." - "Einige Nachrichten in der SMS/MMS App wurden nicht gesendet." - - %d Nachrichten in %d Unterhaltungen - %d Nachricht in einer Unterhaltung - - "Nachricht nicht heruntergeladen" - "Einige Nachrichten in der SMS/MMS App wurden nicht heruntergeladen." - - %d Nachrichten in %d Unterhaltungen - %d Nachricht in einer Unterhaltung - - "SMS an %1$s nicht gesendet" - "Bitte kontaktieren Sie die Notfalldienste per Sprachanruf. Ihre SMS an %1$s konnte dieses Mal nicht zugestellt werden." - - %d neue Nachrichten - Neue Nachricht - - "Starten" - "Kamera nicht verfügbar" - "Kamera nicht verfügbar" - "Videoaufnahme nicht verfügbar" - "Medien können nicht gespeichert werden." - "Keine Bildaufnahme möglich" - "Zurück" - "Archiviert" - "Löschen" - "Archivieren" - "Aus Archiv entfernen" - "Benachrichtigungen deaktivieren" - "Benachrichtigungen aktivieren" - "Kontakt hinzufügen" - "Herunterladen" - "Senden" - "Löschen" - "Diese Nachricht löschen?" - "Diese Aktion kann nicht rückgängig gemacht werden." - "Löschen" - - Diese Unterhaltungen löschen? - Diese Unterhaltung löschen? - - "Löschen" - "Abbrechen" - "An" - "Mehrere Bilder auswählen" - "Auswahl bestätigen" - "+%d" - "Die Audioaufzeichnung ist nicht möglich. Bitte versuchen Sie es erneut." - "Die Audiowiedergabe ist nicht möglich. Bitte versuchen Sie es erneut." - "Die Audionachricht konnte nicht gespeichert werden. Bitte versuchen Sie es erneut." - "Berühren und halten" - ", " - " " - ": " - " " - "Bild" - "Audioclip" - "Video" - "Kontaktkarte" - "Herunterladen" - "Per SMS antworten" - "Antwort per MMS" - "Antworten" - - %d Teilnehmer - %d Teilnehmer - - "Ich" - "Kontakt blockiert und archiviert" - "Blockierung des Kontakts aufgehoben & Kontakt aus dem Archiv entfernt" - "%d archiviert" - "%d aus dem Archiv entfernt" - "Benachrichtigungen deaktiviert" - "Benachrichtigungen aktiviert" - "Bereit. Zum erneuten Senden tippen." - "Die SMS/MMS App wurde als Standard-SMS-App festgelegt." - - Anhänge verwerfen - Anhang verwerfen - - "Audioanhang" - "Audioanhang wiedergeben" - "Pausieren" - "Nachricht von %s: %s." - "Fehler bei Nachricht von %s: %s. Uhrzeit: %s." - "Nachricht von %s: %s. Uhrzeit: %s." - "Nicht gesendete Nachricht an %s: %s. Uhrzeit: %s." - "Nachricht wird gesendet an %s: %s. Uhrzeit: %s." - "Fehler bei Nachricht an %s: %s. Uhrzeit: %s." - "Nachricht an %s: %s. Uhrzeit: %s." - "Fehler bei Nachricht von %s: %s. Uhrzeit: %s. %s." - "Nachricht von %s: %s. Uhrzeit: %s. %s." - "Nicht gesendete Nachricht an %s: %s. Uhrzeit: %s." - "Nachricht wird gesendet an %s: %s. Uhrzeit: %s." - "Fehler bei Nachricht an %s: %s. Uhrzeit: %s." - "Nachricht an %s: %s. Uhrzeit: %s." - "Fehler bei Nachricht. Zum Wiederholen tippen." - "Unterhaltung mit %s" - "Betreff löschen" - "Video aufnehmen" - "Standbild aufnehmen" - "Bild aufnehmen" - "Videoaufnahme starten" - "Zur Vollbildkamera wechseln" - "Zwischen Kamera auf Vorder- und Rückseite wechseln" - "Aufnahme beenden und Video anhängen" - "Videoaufzeichnung stoppen" - "Fotos in der SMS/MMS App" - - %d Fotos im Album \"%s\" gespeichert - %d Foto im Album \"%s\" gespeichert - - - %d Videos im Album \"%s\" gespeichert - %d Video im Album \"%s\" gespeichert - - - %d Anhänge im Album \"%s\" gespeichert - %d Anhang im Album \"%s\" gespeichert - - - %d Anhänge in \"Downloads\" gespeichert - %d Anhang in \"Downloads\" gespeichert - - - %d Anhänge wurden gespeichert. - %d Anhang wurde gespeichert. - - - %d Anhänge konnten nicht gespeichert werden. - %d Anhang konnte nicht gespeichert werden. - - "MMS-Anhang gespeichert" - "Einstellungen" - "Archiviert" - "Schließen" - "MMS" - "Erweitert" - "Fehlersuche" - "Benachrichtigungen" - "Ton" - "Lautlos" - "Vibrieren" - "Blockiert" - "SMS-Zustellberichte" - "Zustellbestätigung für jede gesendete SMS anfordern" - "Automatisch abrufen" - "MMS-Nachrichten automatisch abrufen" - "Automatischer Roaming-Abruf" - "MMS beim Roaming automatisch abrufen" - "SMS/MMS an Gruppe senden" - "Verwenden Sie MMS, um eine einzelne Nachricht an mehrere Empfänger zu versenden." - "Standard-SMS-App" - "Standard-SMS-App" - - - "Meine Telefonnummer" - "Unbekannt" - "Töne für ausgehende Nachrichten" - "SMS sichern" - "Erhaltene SMS-Rohdaten in externer Speicherdatei sichern" - "MMS sichern" - "Erhaltene MMS-Rohdaten in externer Speicherdatei sichern" - "Cell Broadcast-Warnungen" - "Nachrichtenoptionen" - "Text kopieren" - "Details ansehen" - "Löschen" - "Weiterleiten" - "Nachrichtendetails" - "Typ: " - "SMS" - "MMS" - "Von: " - "An: " - "Gesendet: " - "Empfangen: " - "Betreff: " - "Größe: " - "Priorität: " - "SIM: " - "Hoch" - "Normal" - "Niedrig" - "SIM %s" - "Ausgeblendete Senderadresse" - "Die Nachricht kann nicht gesendet werden, weil die Anhänge noch nicht geladen wurden." - "Anhang kann nicht geladen werden. Bitte versuchen Sie es erneut." - "Netzwerk ist nicht bereit. Bitte versuchen Sie es erneut." - "Text löschen" - "Zwischen Text- und Zifferneingabe wechseln" - "Weitere Teilnehmer hinzufügen" - "Teilnehmer bestätigen" - "Neue Unterhaltung starten" - "Dieses Element auswählen" - "Video abspielen" - "Personen & Optionen" - "Debuggen" - "Personen & Optionen" - "Allgemein" - "Personen in dieser Konversation" - "Anrufen" - "Nachricht senden" - "Nachricht senden<br/><small>von %s</small>" - - Fotos senden - Foto senden - - - Audioaufzeichnungen senden - Audioaufzeichnung senden - - - Videos senden - Video senden - - - Kontaktkarten senden - Kontaktkarte senden - - - Anhänge senden - Anhang senden - - - %d Anhänge zum Senden bereit - Ein Anhang zum Senden bereit - - "Feedback geben" - "Im Google Play Store ansehen" - "Versionsinformationen" - "Version: %1$s" - "Open-Source-Lizenzen" - "Benachrichtigungen" - "Anhanglimit erreicht" - "Anhang konnte nicht geladen werden" - "Zu Kontakten hinzufügen?" - "Kontakt hinzufügen" - "Betreff" - "Betreff: " - "%s%s" - "Kontaktkarte wird geladen." - "Kontaktkarte kann nicht geladen werden." - "Kontaktkarte ansehen" - - %d Kontakte - %d Kontakt - - "Kontaktkarten" - "Geburtstag" - "Notizen" - "Nachricht weiterleiten" - "Antworten" - "+1" - "+%d" - "SMS deaktiviert" - "Um Nachrichten zu senden, legen Sie die SMS/MMS App als Standard-SMS-App fest." - "Legen Sie die SMS/MMS App als Standard-SMS-App fest." - "Ändern" - "Um Nachrichten zu erhalten, legen Sie die SMS/MMS App als Standard-SMS-App fest." - "Keine bevorzugte SIM für das Senden von SMS ausgewählt" - "Diese App wird vom Geräteeigentümer nicht zugelassen." - "OK" - "Zu viele Teilnehmer in der Unterhaltung" - - Ungültige Kontakte - Ungültiger Kontakt - - "Kamerabild kann nicht geladen werden." - "Sie: " - "%s: " - "Entwurf" - "Wenn Sie eine neue Unterhaltung beginnen, wird diese hier angezeigt" - "Archivierte Unterhaltungen erscheinen hier." - "Unterhaltungen werden geladen…" - "Bild" - "Audioclip" - "Video" - "Kontaktkarte" - "MMS" - "Rückgängig" - "Erneut versuchen" - "Geben Sie einen Kontaktnamen oder eine Telefonnummer ein, um eine neue Nachricht zu beginnen." - "Blockieren" - "%s blockieren" - "Blockierung aufheben für %s" - "%s blockieren?" - "Sie erhalten weiterhin Nachrichten von dieser Nummer, werden jedoch nicht mehr benachrichtigt. Diese Unterhaltung wird archiviert." - "Blockierte Kontakte" - "Blockierung aufheben" - "Blockierte Kontakte" - "Bild aus der Dokumentenbibliothek auswählen" - "Nachricht wird gesendet" - "Nachricht gesendet" - "Mobilfunkdaten sind deaktiviert. Bitte überprüfen Sie Ihre Einstellungen." - "Im Flugmodus können keine Nachrichten gesendet werden." - "Die Nachricht konnte nicht gesendet werden." - "Nachricht heruntergeladen" - "Mobilfunkdaten sind deaktiviert. Bitte überprüfen Sie Ihre Einstellungen." - "Im Flugmodus können keine Nachrichten heruntergeladen werden." - "Die Nachricht konnte nicht heruntergeladen werden." - "Null" - "Eins" - "Zwei" - "Drei" - "Vier" - "Fünf" - "Sechs" - "Sieben" - "Acht" - "Neun" - "Nachricht kann nicht mit %1$s gesendet werden. Fehler %2$d." - "Nachricht kann nicht mit einem unbekannten Mobilfunkanbieter gesendet werden. Fehler %1$d." - "Fwd: %s" - "Nachricht wurde nicht gesendet: Der Dienst ist im Netzwerk nicht aktiviert." - "Nachricht nicht gesendet: ungültige Zieladresse" - "Nachricht nicht gesendet: ungültige Nachricht" - "Nachricht nicht gesendet: nicht unterstützter Inhalt" - "Nachricht nicht gesendet: nicht unterstützte Nachricht" - "Nachricht nicht gesendet: zu groß" - "Neue Nachricht" - "Ansehen" - "Bild" - "Keine geeignete App gefunden" - "Empfänger entfernen" - "Neue Nachricht" - "Abbrechen" - "Zugangspunkt bearbeiten" - "Nicht definiert" - "Name" - "APN" - "MMSC" - "MMS-Proxy" - "MMS-Port" - "MCC" - "MNC" - "APN-Typ" - "APN löschen" - "Neuer APN" - "Speichern" - "Verwerfen" - "Das Namensfeld darf nicht leer sein." - "APN darf nicht leer sein." - "Das MCC-Feld muss 3 Zeichen enthalten." - "Das MNC-Feld muss 2 oder 3 Zeichen enthalten." - "Standard-APN-Einstellungen werden wiederhergestellt" - "Auf Standard zurücksetzen" - "Zurücksetzen auf Standard-APN-Einstellungen abgeschlossen" - "Unbenannt" - "Zugriffspunktnamen" - "APNs" - "Neuer APN" - "Die Einstellungen für den Zugangspunkt sind für diesen Nutzer nicht verfügbar." - "In Zwischenablage kopieren?" - "Kopieren" - "an %s" - "Allgemein" - "Erweitert" - "Allgemeine Einstellungen" - "Erweiterte Einstellungen" - "SIM \"%s\"" - "Einzelne SMS-Nachrichten an alle Empfänger senden. Die Antworten erhalten nur Sie." - "Einzelne MMS an alle Empfänger senden" - "Unbekannte Nummer" - "Neue Nachricht" - "Neue Nachricht" - "SIM-Auswahl" - "%1$s ausgewählt, SIM-Auswahl" - "Betreff bearbeiten" - "SIM auswählen oder Betreff bearbeiten" - "Für Audioaufnahme berühren und halten" - "Neue Unterhaltung starten" - "SMS/MMS" - "Liste der Unterhaltungen" - "Nachricht" - "Neue Nachricht" - "Konversationsliste" - "Unterhaltungen werden geladen" - "Nachrichten werden geladen" - "Weitere Unterhaltungen anzeigen" - "Weitere Nachrichten ansehen" - "Konversation gelöscht" - "Unterhaltung gelöscht. Tippen Sie hier, um eine andere Unterhaltung zu sehen." - "Blockiert" - "Blockierung aufgehoben" - "Wenig freier Speicherplatz. Einige Daten gehen möglicherweise verloren." - "Anhänge auswählen" - "Auswahl bestätigen" - "%d ausgewählt" - "Bitte entfernen Sie mindestens einen Anhang und versuchen Sie es erneut." - "Sie können versuchen, die Nachricht zu senden, sie wird jedoch möglicherweise nicht zugestellt, wenn Sie nicht mindestens einen Anhang entfernen." - "Pro Nachricht kann nur ein Video gesendet werden. Bitte entfernen Sie überzählige Videos und versuchen Sie es erneut." - "Die SMS/MMS App konnte den Anhang nicht laden." - "Trotzdem senden" - "Die Unterhaltung konnte nicht gestartet werden." - "%1$s (%2$s)" - "%s ausgewählt" - diff --git a/res/values-el/arrays.xml b/res/values-el/arrays.xml deleted file mode 100644 index 280d8e9..0000000 --- a/res/values-el/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "χωρίς θέμα" - "χωρίς θέμα" - - - "Ναι" - "Όχι" - "ΟΚ" - "Χαχα" - "Σας ευχαριστούμε" - "Συμφωνώ" - "Ωραία" - "Έρχομαι" - "Εντάξει, θα επικοινωνήσω μαζί σου αργότερα" - ":)" - ":(" - - diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml deleted file mode 100644 index feade7b..0000000 --- a/res/values-el/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Ανταλλαγή μηνυμάτων" - "Ανταλλαγή μηνυμάτων" - "Επιλογή συνομιλίας" - "Ρυθμίσεις" - "Αποστολή μηνύματος" - "Προσθήκη συνημμένου αρχείου" - "Βοήθεια" - "Καλώς ήρθατε" - "Παράλειψη" - "Επόμενο >" - "Επόμενο" - "Έξοδος" - "Ρυθμίσεις >" - "Ρυθμίσεις" - "Η Ανταλλαγή μηνυμάτων χρειάζεται άδεια για SMS, Τηλέφωνο και Επαφές." - "Μπορείτε να αλλάξετε τα δικαιώματα στις Ρυθμίσεις > Εφαρμογές > Ανταλλαγή μηνυμάτων > Δικαιώματα." - "Μπορείτε να αλλάξετε τα δικαιώματα στις Ρυθμίσεις, Εφαρμογές, Μηνύματα, Δικαιώματα." - "Συχνή επικοινωνία" - "Όλες οι επαφές" - "Αποστολή σε %s" - "Λήψη εικόνων ή βίντεο" - "Επιλέξτε εικόνες από αυτήν τη συσκευή" - "Ηχογράφηση" - "Επιλογή φωτογραφίας" - "Το μέσο είναι επιλεγμένο." - "Το μέσο δεν είναι επιλεγμένο." - "%d επιλεγμένα" - "εικόνα %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "εικόνα" - "Ηχογράφηση" - "Κοινή χρήση" - "Μόλις τώρα" - "Τώρα" - - %d λεπτά - %d λεπτό - - - %d ώρες - %d ώρα - - - %d ημέρες - %d ημέρα - - - %d εβδομάδες - μία εβδομάδα - - - %d μήνες - ένας μήνας - - - %d έτη - ένα έτος - - "Μήνυμα κλάσης 0" - "Αποθήκευση" - "Ο αποθηκευτικός χώρος της συσκευής είναι περιορισμένος. Η Ανταλλαγή μηνυμάτων θα διαγράψει αυτόματα τα παλαιότερα μηνύματα για να απελευθερώσει χώρο." - "Ο χώρος αποθήκευσης εξαντλείται" - "Η Ανταλλαγή μηνυμάτων μπορεί να μην κάνει αποστολή ή λήψη μηνυμάτων μέχρι να ελευθερώσετε περισσότερο χώρο στη συσκευή σας." - "Περιορισμένος αποθηκευτικός χώρος SMS. Μπορεί να χρειαστεί να διαγράψετε μηνύματα." - "Επιβεβαιώστε τον αριθμό τηλεφώνου σας" - "Αυτό το μοναδικό βήμα θα εξασφαλίσει ότι η Ανταλλαγή μηνυμάτων θα παραδώσει σωστά τα ομαδικά μηνύματά σας." - "Αριθμός τηλεφώνου" - "Διαγραφή όλων των μηνυμάτων με πολυμέσα" - "Διαγραφή μηνυμάτων πριν από τις %s" - "Αυτόματη διαγραφή των μηνυμάτων πριν από %s" - "Παράβλεψη" - "Να γίνει διαγραφή όλων των μηνυμάτων που περιέχουν πολυμέσα;" - "Να γίνει διαγραφή των μηνυμάτων πριν από τις %s;" - "Να γίνει αυτόματη διαγραφή των μηνυμάτων πριν από %s και ενεργοποίηση της αυτόματης διαγραφής;" - "Ο χρήστης %s είπε" - "Είπατε" - "Μήνυμα από το χρήστη %s" - "Στείλατε ένα μήνυμα" - "Αποστολή…" - "Δεν εστάλη. Αγγίξτε για να δοκιμάσετε ξανά." - "Δεν εστάλη. Επανάληψη…" - "Εκ νέου αποστολή ή διαγραφή" - "Κάντε μια φωνητική κλήση στις υπηρεσίες έκτακτης ανάγκης. Δεν ήταν δυνατή η παράδοση του μηνύματος κειμένου σας αυτήν τη φορά." - "Αποτυχία" - "Νέο μήνυμα MMS για λήψη" - "Νέο μήνυμα MMS" - "Δεν ήταν δυνατή η λήψη" - "Αγγίξτε για να δοκιμάσετε ξανά" - "Αγγίξτε για λήψη" - "Λήψη ή διαγραφή" - "Λήψη…" - "Το μήνυμα έληξε ή δεν είναι διαθέσιμο" - "μέγεθος: %1$s, λήξη: %2$s" - "Δεν είναι δυνατή η αποστολή. Ο παραλήπτης δεν είναι έγκυρος." - "Η υπηρεσία δεν έχει ενεργοποιηθεί στο δίκτυο" - "Δεν ήταν δυνατή η αποστολή λόγω προβλήματος στο δίκτυο" - "Το μήνυμα έληξε ή δεν είναι διαθέσιμο." - "(Χωρίς θέμα)" - "Άγνωστος αποστολέας" - "Παραδόθηκε" - "Δεν ήταν δυνατή η λήψη του μηνύματος %1$s από %2$s." - "Δεν ήταν δυνατή η ολοκλήρωση της λειτουργίας της βάσης δεδομένων λόγω ανεπαρκούς μνήμης" - "Το μήνυμα δεν εστάλη" - "Ορισμένα μηνύματα δεν εστάλησαν στην Ανταλλαγή μηνυμάτων" - - %d μηνύματα σε %d συνομιλίες - %d μηνύματα σε μία συνομιλία - - "Δεν έγινε λήψη του μηνύματος" - "Δεν έγινε λήψη ορισμένων μηνυμάτων στην Ανταλλαγή μηνυμάτων" - - %d μηνύματα σε %d συνομιλίες - %d μηνύματα σε μία συνομιλία - - "Το μήνυμα στον αριθμό %1$s δεν εστάλη" - "Κάντε μια φωνητική κλήση στις υπηρεσίες έκτακτης ανάγκης. Δεν ήταν δυνατή η παράδοση του μηνύματος κειμένου σας στον αριθμό %1$s αυτήν τη φορά." - - %d νέα μηνύματα - Νέο μήνυμα - - "Έναρξη" - "Η φωτογραφική μηχανή δεν είναι διαθέσιμη" - "Η φωτογραφική μηχανή δεν είναι διαθέσιμη" - "Η εγγραφή βίντεο δεν είναι διαθέσιμη" - "Δεν είναι δυνατή η αποθήκευση πολυμέσων" - "Δεν είναι δυνατή η λήψη φωτογραφίας" - "Πίσω" - "Αρχειοθετημένο" - "Διαγραφή" - "Αρχειοθέτηση" - "Κατάργηση αρχειοθέτησης" - "Απενεργοποίηση ειδοποιήσεων" - "Ενεργοποίηση ειδοποιήσεων" - "Προσθήκη επαφής" - "Λήψη" - "Αποστολή" - "Διαγραφή" - "Να διαγραφεί αυτό το μήνυμα;" - "Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας." - "Διαγραφή" - - Διαγραφή αυτών των συνομιλιών; - Διαγραφή αυτής της συνομιλίας; - - "Διαγραφή" - "Ακύρωση" - "Προς" - "Επιλογή πολλαπλών εικόνων" - "Επιβεβαίωση επιλογής" - "+%d" - "Δεν είναι δυνατή η αποθήκευση ήχου. Δοκιμάστε ξανά." - "Δεν είναι δυνατή η αναπαραγωγή ήχου. Δοκιμάστε ξανά." - "Δεν ήταν δυνατή η αποθήκευση ήχου. Δοκιμάστε ξανά." - "Άγγιγμα & κράτημα" - ", " - " " - ": " - " " - "Εικόνα" - "Κλιπ ήχου" - "Βίντεο" - "Κάρτα επικοινωνίας" - "Λήψη" - "Απάντηση μέσω SMS" - "Απάντηση μέσω MMS" - "Απάντηση" - - %d συμμετέχοντες - %d συμμετέχων - - "Εγώ" - "Η επαφή αποκλείστηκε και αρχειοθετήθηκε" - "Έγινε κατάργηση αποκλεισμού και αρχειοθέτησης της επαφής" - "Έχουν αρχειοθετηθεί %d" - "%d αφαιρέθηκαν από το αρχείο" - "Οι ειδοποιήσεις απενεργοποιήθηκαν" - "Οι ειδοποιήσεις ενεργοποιήθηκαν" - "Έχουν ρυθμιστεί όλα. Αγγίξτε ξανά την επιλογή \"Αποστολή\"." - "Η Ανταλλαγή μηνυμάτων ορίστηκε επιτυχώς ως η προεπιλεγμένη εφαρμογή SMS." - - Απόρριψη συνημμένων - Απόρριψη συνημμένου - - "Συνημμένο ήχου" - "Αναπαραγωγή συνημμένου ήχου" - "Παύση" - "Μήνυμα από %s: %s." - "Αποτυχία μηνύματος από %s: %s. Ώρα: %s" - "Μήνυμα από %s: %s. Ώρα: %s" - "Μη απεσταλμένο μήνυμα προς %s: %s. Ώρα: %s" - "Αποστολή μηνύματος προς %s: %s. Ώρα: %s." - "Αποτυχία μηνύματος προς %s: %s. Ώρα: %s" - "Μήνυμα προς %s: %s. Ώρα: %s." - "Αποτυχία μηνύματος από %s: %s. Ώρα: %s. %s" - "Μήνυμα από %s: %s. Ώρα: %s. %s" - "Μη απεσταλμένο μήνυμα προς %s: %s. Ώρα: %s" - "Αποστολή μηνύματος σε %s: %s. Ώρα: %s" - "Αποτυχία μηνύματος προς %s: %s. Ώρα: %s" - "Μήνυμα προς %s: %s. Ώρα: %s" - "Αποτυχία μηνύματος. Αγγίξτε για επανάληψη." - "Συνομιλία με %s" - "Διαγραφή θέματος" - "Λήψη βίντεο" - "Τραβήξτε μια στατική εικόνα" - "Τραβήξτε μια φωτογραφία" - "Έναρξη εγγραφής βίντεο" - "Εναλλαγή σε κάμερα πλήρους οθόνης" - "Εναλλαγή μεταξύ μπροστινής και της πίσω φωτογραφικής μηχανής" - "Διακοπή εγγραφής και επισύναψη βίντεο" - "Διακοπή εγγραφής βίντεο" - "Φωτογραφίες Ανταλλαγής μηνυμάτων" - - %d φωτογραφίες αποθηκεύτηκαν στο λεύκωμα \"%s\" - %d φωτογραφία αποθηκεύτηκε στο λεύκωμα \"%s\" - - - %d βίντεο αποθηκεύτηκαν στο λεύκωμα \"%s\" - %d βίντεο αποθηκεύτηκε στο λεύκωμα \"%s\" - - - %d συνημμένα αποθηκεύτηκαν στο λεύκωμα \"%s\" - %d συνημμένο αποθηκεύτηκε στο λεύκωμα \"%s\" - - - %d συνημμένα αποθηκεύτηκαν στις \"Λήψεις\" - %d συνημμένο αποθηκεύτηκε στις \"Λήψεις\" - - - Αποθηκεύτηκε %d συνημμένων αρχείων - Αποθηκεύτηκε %d συνημμένο αρχείο - - - Δεν ήταν δυνατή η αποθήκευση %d συνημμένων αρχείων - Δεν ήταν δυνατή η αποθήκευση %d συνημμένου αρχείου - - "Αποθηκευμένο συνημμένο MMS" - "Ρυθμίσεις" - "Αρχειοθετημένο" - "Κλείσιμο" - "MMS" - "Σύνθετες ρυθμίσεις" - "Εντοπισμός σφαλμάτων" - "Ειδοποιήσεις" - "Ήχος" - "Σίγαση" - "Δόνηση" - "Αποκλεισμένο" - "Αναφορές παράδοσης SMS" - "Αίτηση αναφοράς παράδοσης για κάθε SMS που στέλνετε" - "Αυτόματη ανάκτηση" - "Αυτόματη ανάκτηση μηνυμάτων MMS" - "Αυ/τη ανάκτηση περιαγ." - "Αυτόματη ανάκτηση MMS κατά την περιαγωγή" - "Ομαδική ανταλλαγή μηνυμάτων" - "Χρησιμοποιήστε το MMS για να στείλετε ένα μήνυμα σε πολλούς παραλήπτες" - "Προεπιλεγμένη εφαρμογή SMS" - "Προεπιλεγμένη εφαρμογή SMS" - - - "Ο αριθμός τηλεφώνου σας" - "Άγνωστo" - "Ήχοε εξερχόμενου μηνύματος" - "Απόρριψη SMS" - "Απόρριψη μη επεξεργασμένων δεδομένων ληφθέντων SMS σε αρχείο εξωτερικού αποθηκευτικού χώρου" - "Απόρριψη MMS" - "Απόρριψη μη επεξεργασμένων δεδομένων ληφθέντων MMS σε αρχείο εξωτερικού αποθηκευτικού χώρου" - "Ασύρματες ειδοποιήσεις" - "Επιλογές μηνύματος" - "Αντιγραφή κειμένου" - "Προβολή λεπτομερειών" - "Διαγραφή" - "Προώθηση" - "Λεπτομέρειες μηνύματος" - "Τύπος: " - "Μήνυμα κειμένου" - "Μήνυμα πολυμέσων" - "Από: " - "Προς: " - "Εστάλη: " - "Ελήφθη: " - "Θέμα: " - "Μέγεθος: " - "Προτεραιότητα: " - "SIM: " - "Υψηλή" - "Κανονική" - "Χαμηλή" - "SIM %s" - "Διεύθυνση κρυφού αποστολέα" - "Δεν είναι δυνατή η αποστολή του μηνύματος ενώ φορτώνουν τα συνημμένα." - "Δεν είναι δυνατή η φόρτωση της επισύναψης. Δοκιμάστε ξανά." - "Το δίκτυο δεν είναι έτοιμο. Δοκιμάστε ξανά." - "Διαγραφή κειμένου" - "Εναλλαγή μεταξύ εισαγωγής κειμένου και αριθμών" - "Προσθήκη περισσότερων συμμετεχόντων" - "Επιβεβαίωση συμμετεχόντων" - "Έναρξη νέας συνομιλίας" - "Επιλέξτε αυτό το στοιχείο" - "Αναπαραγωγή βίντεο" - "Άτομα και επιλογές" - "Εντοπισμός σφαλμάτων" - "Άτομα και επιλογές" - "Γενικά" - "Άτομα σε αυτήν τη συνομιλία" - "Πραγματοποίηση κλήσης" - "Αποστολή μηνύματος" - "Αποστολή μηνύματος<br/><small>από %s</small>" - - Αποστολή φωτογραφιών - Αποστολή φωτογραφίας - - - Αποστολή αρχείων ήχου - Αποστολή αρχείου ήχου - - - Αποστολή αρχείων βίντεο - Αποστολή αρχείου βίντεο - - - Αποστολή καρτών επαφών - Αποστολή κάρτας επαφής - - - Αποστολή συνημμένων - Αποστολή συνημμένου - - - %d συνημμένα έτοιμα για αποστολή - Ένα συνημμένο έτοιμο για αποστολή - - "Αποστολή σχολίων" - "Προβολή στο Google Play Store" - "Πληροφορίες έκδοσης" - "Έκδοση %1$s" - "Άδειες λογισμικού ανοικτού κώδικα" - "Ειδοποιήσεις" - "Συμπλήρωση ορίου συνημμένων" - "Αποτυχία φόρτωσης συνημμένου." - "Προσθήκη στις επαφές;" - "Προσθήκη επαφής" - "Θέμα" - "Θέμα: " - "%s%s" - "Φόρτωση κάρτας επαφής" - "Δεν ήταν δυνατή η φόρτωση της κάρτας επαφής" - "Προβολή κάρτας επαφής" - - %d επαφές - %d επαφή - - "Κάρτες επαφών" - "Γενέθλια" - "Σημειώσεις" - "Προώθηση μηνύματος" - "Απάντηση" - "+1" - "+%d" - "Τα SMS απενεργοποιήθηκαν" - "Για αποστολή, ορίστε την Ανταλλαγή μηνυμάτων ως προεπιλεγμένη εφαρμογή SMS" - "Ορισμός Ανταλλαγής μηνυμάτων ως προεπιλεγμένης εφαρμογής SMS" - "Αλλαγή" - "Για να λαμβάνετε μηνύματα, ορίστε την Ανταλλαγή μηνυμάτων ως προεπιλεγμένη εφαρμογή SMS" - "Δεν έχει επιλεγεί προτιμώμενη SIM για την αποστολή μηνυμάτων SMS" - "Αυτή η εφαρμογή δεν επιτρέπεται από τον κάτοχο της συσκευής." - "ΟΚ" - "Πάρα πολλοί συμμετέχοντες σε μια συνομιλία" - - Μη έγκυρες επαφές - Μη έγκυρη επαφή - - "Δεν ήταν δυνατή η φόρτωση της εικόνας φωτογραφικής μηχανής" - "Εσείς: " - "%s: " - "Πρόχειρο" - "Αφού ξεκινήσετε μια νέα συνομιλία, θα εμφανίζεται εδώ" - "Οι αρχειοθετημένες συνομιλίες εμφανίζονται εδώ" - "Φόρτωση συνομιλιών…" - "Εικόνα" - "Κλιπ ήχου" - "Βίντεο" - "Κάρτα επαφής" - "MMS" - "Αναίρεση" - "Επανάληψη" - "Εισαγάγετε ένα όνομα επαφής ή έναν αριθμό τηλεφώνου για να ξεκινήσετε ένα νέο μήνυμα" - "Αποκλεισμός" - "Αποκλεισμός επαφής %s" - "Κατάργηση αποκλεισμού της επαφής %s" - "Αποκλεισμός %s;" - "Θα εξακολουθήσετε να λαμβάνετε μηνύματα από αυτόν τον αριθμό, αλλά δεν θα ειδοποιείστε πια. Αυτή η συνομιλία θα αρχειοθετηθεί." - "Αποκλεισμένες επαφές" - "ΚΑΤΑΡΓΗΣΗ ΑΠΟΚΛΕΙΣΜΟΥ" - "Αποκλεισμένες επαφές" - "Επιλέξτε μια εικόνα από τη βιβλιοθήκη εγγράφων" - "Αποστολή μηνύματος" - "Το μήνυμα εστάλη" - "Τα δεδομένα κινητής τηλεφωνίας έχουν απενεργοποιηθεί. Ελέγξτε τις ρυθμίσεις σας." - "Δεν είναι δυνατή η αποστολή μηνυμάτων σε Λειτουργία πτήσης" - "Αδυναμία αποστολής μηνύματος" - "Έγινε λήψη του μηνύματος" - "Τα δεδομένα κινητής τηλεφωνίας απενεργοποιήθηκαν. Ελέγξτε τις ρυθμίσεις σας." - "Δεν είναι δυνατή η λήψη μηνυμάτων σε λειτουργία πτήσης" - "Δεν ήταν δυνατή η λήψη του μηνύματος" - "Μηδέν" - "Ένα" - "Δύο" - "Τρία" - "Τέσσερα" - "Πέντε" - "Έξι" - "Επτά" - "Οκτώ" - "Εννέα" - "Δεν είναι δυνατή η αποστολή του μηνύματος με %1$s, σφάλμα %2$d" - "Δεν είναι δυνατή η αποστολή του μηνύματος με άγνωστη εταιρεία κινητής τηλεφωνίας, σφάλμα %1$d" - "Προώθ.: %s" - "Το μήνυμα δεν εστάλη: Η υπηρεσία δεν ενεργοποιήθηκε στο δίκτυο" - "Το μήνυμα δεν εστάλη: μη έγκυρη διεύθυνση προορισμού" - "Το μήνυμα δεν εστάλη: μη έγκυρο μήνυμα" - "Το μήνυμα δεν εστάλη: μη υποστηριζόμενο περιεχόμενο" - "Το μήνυμα δεν εστάλη: μη υποστηριζόμενο μήνυμα" - "Το μήνυμα δεν εστάλη: πάρα πολύ μεγάλο" - "Νέο μήνυμα" - "Προβολή" - "Εικόνα" - "Δεν ήταν δυνατή η εύρεση της κατάλληλης εφαρμογής" - "Κατάργηση παραλήπτη" - "Νέο μήνυμα" - "Ακύρωση" - "Επεξ/σία σημ. πρόσβ." - "Δεν έχει ρυθμιστεί" - "Όνομα" - "APN" - "MMSC" - "Διακομιστής μεσολάβησης MMS" - "Θύρα MMS" - "MCC" - "MNC" - "Τύπος APN" - "Διαγραφή APN" - "Νέο APN" - "Αποθήκευση" - "Απόρριψη" - "Το πεδίο Όνομα δεν μπορεί να είναι κενό." - "Το APN δεν μπορεί να είναι κενό." - "Το πεδίο MCC πρέπει να αποτελείται από 3 ψηφία." - "Το πεδίο MNC πρέπει να αποτελείται από 2 ή 3 ψηφία." - "Επαναφορά προεπιλεγμένων ρυθμίσεων APN." - "Επαναφορά προεπιλογών" - "Η επαναφορά των εργοστασιακών ρυθμίσεων APN ολοκληρώθηκε." - "Χωρίς τίτλο" - "Ονόματα σημείου πρόσβασης" - "APN" - "Νέο APN" - "Οι ρυθμίσεις ονόματος σημείου πρόσβασης δεν είναι διαθέσιμες γι\' αυτόν το χρήστη" - "Αντιγραφή στο πρόχειρο;" - "Αντιγραφή" - "σε %s" - "Γενικά" - "Σύνθετες ρυθμίσεις" - "Γενικές ρυθμίσεις" - "Σύνθετες ρυθμίσεις" - "\"%s\" SIM" - "Αποστολή μεμονωμένων μηνυμάτων SMS σε όλους τους παραλήπτες. Μόνο εσείς θα λαμβάνετε τις απαντήσεις" - "Αποστολή μεμονωμένου MMS σε όλους τους παραλήπτες" - "Άγνωστος αριθμός" - "Νέο μήνυμα" - "Νέο μήνυμα." - "Επιλογέας SIM" - "Επιλέχτηκε %1$s, επιλογέας SIM" - "Επεξεργασία θέματος" - "Επιλέξτε SIM ή επεξεργαστείτε το θέμα" - "Πατήστε παρατεταμένα για εγγραφή ήχου" - "Έναρξη νέας συνομιλίας" - "Ανταλλαγή μηνυμάτων" - "Λίστα Ανταλλαγής μηνυμάτων" - "Ανταλλαγή μηνυμάτων" - "Νέο μήνυμα" - "Λίστα συνομιλιών" - "Φόρτωση συνομιλιών" - "Φόρτωση μηνυμάτων" - "Προβολή περισσότερων συνομιλιών" - "Εμφάνιση περισσότερων μηνυμάτων" - "Η συνομιλία διαγράφηκε" - "Διαγραφή συνομιλίας. Αγγίξτε για να εμφανίσετε μια διαφορετική συνομιλία στην Ανταλλαγή μηνυμάτων" - "Αποκλείστηκε" - "Ο αποκλεισμός καταργήθηκε" - "Ο αποθηκευτικός χώρος είναι περιορισμένος. Ορισμένα στοιχεία ενδέχεται να χαθούν." - "Επιλέξτε συνημμένα" - "Επιβεβαίωση επιλογής" - "%d επιλ/μένα" - "Καταργήστε ένα ή περισσότερα συνημμένα και δοκιμάστε ξανά." - "Μπορείτε να δοκιμάσετε να στείλετε το μήνυμά σας, αλλά ενδέχεται να μην παραδοθεί αν δεν καταργήσετε ένα ή περισσότερα συνημμένα." - "Μπορείτε να στείλετε μόνο ένα βίντεο ανά μήνυμα. Καταργήστε τα επιπλέον βίντεο και δοκιμάστε ξανά." - "Αποτυχία φόρτωσης συνημμένου από την Ανταλλαγή μηνυμάτων." - "Αποστολή ούτως ή άλλως" - "Δεν ήταν δυνατή η έναρξη της συνομιλίας" - "%1$s (%2$s)" - "Επιλέχτηκε %s" - diff --git a/res/values-en-rAU/arrays.xml b/res/values-en-rAU/arrays.xml deleted file mode 100644 index 94d5aff..0000000 --- a/res/values-en-rAU/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "no subject" - "nosubject" - - - "Yes" - "No" - "OK" - "Hehe" - "Thanks" - "I agree" - "Nice" - "On my way" - "OK, let me get back to you later" - ":)" - ":(" - - diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml deleted file mode 100644 index 11048a5..0000000 --- a/res/values-en-rAU/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messaging" - "Messaging" - "Select conversation" - "Settings" - "Send Message" - "Add an attachment" - "Help" - "Welcome" - "Skip" - "Next >" - "Next" - "Exit" - "Settings >" - "Settings" - "Messaging needs permission to SMS, Phone and Contacts." - "You can change permissions in Settings > Apps > Messaging > Permissions." - "You can change permissions in Settings, Apps, Messaging, Permissions." - "Frequents" - "All contacts" - "Send to %s" - "Capture pictures or video" - "Choose images from this device" - "Record audio" - "Choose photo" - "The media is selected." - "The media is unselected." - "%d selected" - "image %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "image" - "Record audio" - "Share" - "Just now" - "Now" - - %d mins - %d min - - - %d hours - %d hour - - - %d days - %d day - - - %d weeks - a week - - - %d months - a month - - - %d years - a year - - "Class 0 message" - "Save" - "Device is low on space. Messaging will automatically delete older messages to free up space." - "Storage space running out" - "Messaging might not send or receive messages until more space is available on your device." - "Low SMS storage. You may need to delete messages." - "Confirm your phone number" - "This one-off step will ensure Messaging will deliver your group messages properly." - "Phone number" - "Delete all messages with media" - "Delete messages older than %s" - "Auto-delete messages older than %s" - "Ignore" - "Delete all messages with media?" - "Delete messages older than %s?" - "Delete messages older than %s and turn on auto-delete?" - "%s said" - "You said" - "Message from %s" - "You sent a message" - "Sending…" - "Not sent. Touch to try again." - "Not sent. Trying again…" - "Resend or delete" - "Please make a voice call to emergency services. Your text message could not be delivered at this time." - "Failed" - "New MMS message to download" - "New MMS message" - "Couldn\'t download" - "Touch to try again" - "Touch to download" - "Download or delete" - "Downloading…" - "Message expired or not available" - "size: %1$s, expiration: %2$s" - "Can\'t send. Recipient not valid." - "Service not activated on network" - "Couldn\'t send due to network problem" - "Message expired or not available" - "(No subject)" - "Unknown sender" - "Delivered" - "Couldn\'t download message %1$s from %2$s." - "Couldn\'t complete database operation due to low memory" - "Message not sent" - "Some messages not sent in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message not downloaded" - "Some messages not downloaded in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message to %1$s not sent" - "Please make a voice call to emergency services. Your text message to %1$s could not be delivered at this time." - - %d new messages - New message - - "Start" - "Camera not available" - "Camera not available" - "Video capture not available" - "Can\'t save media" - "Can\'t take picture" - "Back" - "Archived" - "Delete" - "Archive" - "Unarchive" - "Turn off notifications" - "Turn on notifications" - "Add contact" - "Download" - "Send" - "Delete" - "Delete this message" - "This action cannot be undone." - "Delete" - - Delete these conversations? - Delete this conversation? - - "Delete" - "Cancel" - "To" - "Select multiple images" - "Confirm selection" - "+%d" - "Can\'t record audio. Try again." - "Can\'t play audio. Try again." - "Couldn\'t save audio. Try again." - "Touch & hold" - ", " - " " - ": " - " " - "Picture" - "Audio clip" - "Video" - "Contact card" - "Download" - "Reply via SMS" - "Reply via MMS" - "Reply" - - %d participants - %d participant - - "Me" - "Contact blocked & archived" - "Contact unblocked & unarchived" - "%d archived" - "%d unarchived" - "Notifications turned off" - "Notifications turned on" - "All set. Touch Send again." - "Messaging successfully set as the default SMS app." - - Discard attachments - Discard attachment - - "Audio attachment" - "Play audio attachment" - "Pause" - "Message from %s: %s." - "Failed message from %s: %s. Time: %s." - "Message from %s: %s. Time: %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message from %s: %s. Time: %s. %s." - "Message from %s: %s. Time: %s. %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message. Touch to retry." - "Conversation with %s" - "Delete subject" - "Capture video" - "Capture a still image" - "Take picture" - "Start recording video" - "Switch to full screen camera" - "Switch between front and back camera" - "Stop recording and attach video" - "Stop recording video" - "Messaging photos" - - %d photos saved to \"%s\" album - %d photo saved to \"%s\" album - - - %d videos saved to \"%s\" album - %d video saved to \"%s\" album - - - %d attachments saved to \"%s\" album - %d attachment saved to \"%s\" album - - - %d attachments saved to \"Downloads\" - %d attachment saved to \"Downloads\" - - - %d attachments saved - %d attachment saved - - - Couldn\'t save %d attachments - Couldn\'t save %d attachment - - "Saved MMS attachment" - "Settings" - "Archived" - "Close" - "MMS" - "Advanced" - "Debug" - "Notifications" - "Sound" - "Silent" - "Vibrate" - "Blocked" - "SMS delivery reports" - "Request a delivery report for each SMS that you send" - "Auto-retrieve" - "Automatically retrieve MMS messages" - "Roaming auto-retrieve" - "Automatically retrieve MMS when roaming" - "Group messaging" - "Use MMS to send a single message when there are multiple recipients" - "Default SMS app" - "Default SMS app" - - - "Your phone number" - "Unknown" - "Outgoing message sounds" - "Dump SMS" - "Dump received SMS raw data into external storage file" - "Dump MMS" - "Dump received MMS raw data into external storage file" - "Wireless alerts" - "Message options" - "Copy text" - "View details" - "Delete" - "Forward" - "Message details" - "Type: " - "Text message" - "Multimedia message" - "From: " - "To: " - "Sent: " - "Received: " - "Subject: " - "Size: " - "Priority: " - "SIM: " - "High" - "Normal" - "Low" - "SIM %s" - "Hidden sender address" - "Can\'t send message while loading attachments." - "Can\'t load attachment. Try again." - "Network is not ready. Try again." - "Delete text" - "Switch between entering text and numbers" - "Add more participants" - "Confirm participants" - "Start new conversation" - "Select this item" - "Play video" - "People & Options" - "Debug" - "People & Options" - "General" - "People in this conversation" - "Make a call" - "Send message" - "Send message<br/><small>from %s</small>" - - Send photos - Send photo - - - Send audios - Send audio - - - Send videos - Send video - - - Send contact cards - Send contact card - - - Send attachments - Send attachment - - - %d attachments ready to send - One attachment ready to send - - "Send feedback" - "View in Google Play Store" - "Version info" - "Version %1$s" - "Open-source licences" - "Notifications" - "Attachment limit reached" - "Failed to load attachment." - "Add to Contacts?" - "Add Contact" - "Subject" - "Subject: " - "%s%s" - "Loading contact card" - "Couldn\'t load contact card" - "View contact card" - - %d contacts - %d contact - - "Contact cards" - "Birthday" - "Notes" - "Forward message" - "Reply" - "+1" - "+%d" - "SMS disabled" - "To send, set Messaging as the default SMS app" - "Set Messaging as the default SMS app" - "Change" - "To receive messages, set Messaging as the default SMS app" - "No preferred SIM selected for sending SMS messages" - "This app isn\'t allowed by the device owner." - "OK" - "Too many participants in a conversation" - - Invalid contacts - Invalid contact - - "Couldn\'t load camera image" - "You: " - "%s: " - "Draft" - "Once you start a new conversation, you’ll see it listed here" - "Archived conversations appear here" - "Loading conversations…" - "Picture" - "Audio clip" - "In-stream video" - "Contact card" - "MMS" - "Undo" - "Retry" - "Enter a contact name or phone number to start a new message" - "Block" - "Block %s" - "Unblock %s" - "Block %s?" - "You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived." - "Blocked contacts" - "UNBLOCK" - "Blocked contacts" - "Choose image from document library" - "Sending message" - "Message sent" - "Mobile data is turned off. Check your settings." - "Can\'t send messages in Aeroplane mode" - "Message could not be sent" - "Message downloaded" - "Mobile data is turned off. Check your settings." - "Can\'t download messages in Aeroplane mode" - "Message could not be downloaded" - "Zero" - "One" - "Two" - "Three" - "Four" - "Five" - "Six" - "Seven" - "Eight" - "Nine" - "Can\'t send message with %1$s, error %2$d" - "Can\'t send message with unknown operator, error %1$d" - "Fwd: %s" - "Message not sent: service not activated on network" - "Message not sent: invalid destination address" - "Message not sent: invalid message" - "Message not sent: unsupported content" - "Message not sent: unsupported message" - "Message not sent: too large" - "New message" - "View" - "Image" - "Could not find an appropriate application" - "Remove recipient" - "New message" - "Cancel" - "Edit access point" - "Not set" - "Named" - "APN" - "MMSC" - "MMS proxy" - "MMS port" - "MCC" - "MNC" - "APN type" - "Delete APN" - "New APN" - "Save" - "Discard" - "The Name field can\'t be empty." - "The APN cannot be empty." - "MCC field must be 3 digits." - "MNC field must be 2 or 3 digits." - "Restoring default APN settings." - "Reset to default" - "Reset default APN settings completed" - "Untitled" - "Access point names" - "APNs" - "New APN" - "Access Point Name settings are not available for this user" - "Copy to clipboard?" - "Copy" - "to %s" - "General" - "Advanced" - "General settings" - "Advanced settings" - "\"%s\" SIM" - "Send individual SMS messages to all recipients. Only you will get any replies" - "Send a single MMS to all recipients" - "Unknown number" - "New message" - "New message" - "SIM selector" - "%1$s selected, SIM selector" - "Edit subject" - "Select SIM or edit subject" - "Touch and hold to record audio" - "Start new conversation" - "Messaging" - "Messaging List" - "Messaging" - "New message" - "Conversation list" - "Loading conversations" - "Loading messages" - "View more conversations" - "View more messages" - "Conversation deleted" - "Conversation deleted. Touch to show a different Messaging conversation" - "Blocked" - "Unblocked" - "Storage space is low. Some data may be lost." - "Select attachments" - "Confirm selection" - "%d selected" - "Please remove one or more attachments and try again." - "You can try sending your message, but it might not be delivered unless you remove one or more attachments." - "You can only send one video per message. Please remove additional videos and try again." - "Messaging failed to load attachment." - "Send anyway" - "Could not start conversation" - "%1$s (%2$s)" - "%s selected" - diff --git a/res/values-en-rGB/arrays.xml b/res/values-en-rGB/arrays.xml deleted file mode 100644 index 94d5aff..0000000 --- a/res/values-en-rGB/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "no subject" - "nosubject" - - - "Yes" - "No" - "OK" - "Hehe" - "Thanks" - "I agree" - "Nice" - "On my way" - "OK, let me get back to you later" - ":)" - ":(" - - diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml deleted file mode 100644 index 11048a5..0000000 --- a/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messaging" - "Messaging" - "Select conversation" - "Settings" - "Send Message" - "Add an attachment" - "Help" - "Welcome" - "Skip" - "Next >" - "Next" - "Exit" - "Settings >" - "Settings" - "Messaging needs permission to SMS, Phone and Contacts." - "You can change permissions in Settings > Apps > Messaging > Permissions." - "You can change permissions in Settings, Apps, Messaging, Permissions." - "Frequents" - "All contacts" - "Send to %s" - "Capture pictures or video" - "Choose images from this device" - "Record audio" - "Choose photo" - "The media is selected." - "The media is unselected." - "%d selected" - "image %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "image" - "Record audio" - "Share" - "Just now" - "Now" - - %d mins - %d min - - - %d hours - %d hour - - - %d days - %d day - - - %d weeks - a week - - - %d months - a month - - - %d years - a year - - "Class 0 message" - "Save" - "Device is low on space. Messaging will automatically delete older messages to free up space." - "Storage space running out" - "Messaging might not send or receive messages until more space is available on your device." - "Low SMS storage. You may need to delete messages." - "Confirm your phone number" - "This one-off step will ensure Messaging will deliver your group messages properly." - "Phone number" - "Delete all messages with media" - "Delete messages older than %s" - "Auto-delete messages older than %s" - "Ignore" - "Delete all messages with media?" - "Delete messages older than %s?" - "Delete messages older than %s and turn on auto-delete?" - "%s said" - "You said" - "Message from %s" - "You sent a message" - "Sending…" - "Not sent. Touch to try again." - "Not sent. Trying again…" - "Resend or delete" - "Please make a voice call to emergency services. Your text message could not be delivered at this time." - "Failed" - "New MMS message to download" - "New MMS message" - "Couldn\'t download" - "Touch to try again" - "Touch to download" - "Download or delete" - "Downloading…" - "Message expired or not available" - "size: %1$s, expiration: %2$s" - "Can\'t send. Recipient not valid." - "Service not activated on network" - "Couldn\'t send due to network problem" - "Message expired or not available" - "(No subject)" - "Unknown sender" - "Delivered" - "Couldn\'t download message %1$s from %2$s." - "Couldn\'t complete database operation due to low memory" - "Message not sent" - "Some messages not sent in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message not downloaded" - "Some messages not downloaded in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message to %1$s not sent" - "Please make a voice call to emergency services. Your text message to %1$s could not be delivered at this time." - - %d new messages - New message - - "Start" - "Camera not available" - "Camera not available" - "Video capture not available" - "Can\'t save media" - "Can\'t take picture" - "Back" - "Archived" - "Delete" - "Archive" - "Unarchive" - "Turn off notifications" - "Turn on notifications" - "Add contact" - "Download" - "Send" - "Delete" - "Delete this message" - "This action cannot be undone." - "Delete" - - Delete these conversations? - Delete this conversation? - - "Delete" - "Cancel" - "To" - "Select multiple images" - "Confirm selection" - "+%d" - "Can\'t record audio. Try again." - "Can\'t play audio. Try again." - "Couldn\'t save audio. Try again." - "Touch & hold" - ", " - " " - ": " - " " - "Picture" - "Audio clip" - "Video" - "Contact card" - "Download" - "Reply via SMS" - "Reply via MMS" - "Reply" - - %d participants - %d participant - - "Me" - "Contact blocked & archived" - "Contact unblocked & unarchived" - "%d archived" - "%d unarchived" - "Notifications turned off" - "Notifications turned on" - "All set. Touch Send again." - "Messaging successfully set as the default SMS app." - - Discard attachments - Discard attachment - - "Audio attachment" - "Play audio attachment" - "Pause" - "Message from %s: %s." - "Failed message from %s: %s. Time: %s." - "Message from %s: %s. Time: %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message from %s: %s. Time: %s. %s." - "Message from %s: %s. Time: %s. %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message. Touch to retry." - "Conversation with %s" - "Delete subject" - "Capture video" - "Capture a still image" - "Take picture" - "Start recording video" - "Switch to full screen camera" - "Switch between front and back camera" - "Stop recording and attach video" - "Stop recording video" - "Messaging photos" - - %d photos saved to \"%s\" album - %d photo saved to \"%s\" album - - - %d videos saved to \"%s\" album - %d video saved to \"%s\" album - - - %d attachments saved to \"%s\" album - %d attachment saved to \"%s\" album - - - %d attachments saved to \"Downloads\" - %d attachment saved to \"Downloads\" - - - %d attachments saved - %d attachment saved - - - Couldn\'t save %d attachments - Couldn\'t save %d attachment - - "Saved MMS attachment" - "Settings" - "Archived" - "Close" - "MMS" - "Advanced" - "Debug" - "Notifications" - "Sound" - "Silent" - "Vibrate" - "Blocked" - "SMS delivery reports" - "Request a delivery report for each SMS that you send" - "Auto-retrieve" - "Automatically retrieve MMS messages" - "Roaming auto-retrieve" - "Automatically retrieve MMS when roaming" - "Group messaging" - "Use MMS to send a single message when there are multiple recipients" - "Default SMS app" - "Default SMS app" - - - "Your phone number" - "Unknown" - "Outgoing message sounds" - "Dump SMS" - "Dump received SMS raw data into external storage file" - "Dump MMS" - "Dump received MMS raw data into external storage file" - "Wireless alerts" - "Message options" - "Copy text" - "View details" - "Delete" - "Forward" - "Message details" - "Type: " - "Text message" - "Multimedia message" - "From: " - "To: " - "Sent: " - "Received: " - "Subject: " - "Size: " - "Priority: " - "SIM: " - "High" - "Normal" - "Low" - "SIM %s" - "Hidden sender address" - "Can\'t send message while loading attachments." - "Can\'t load attachment. Try again." - "Network is not ready. Try again." - "Delete text" - "Switch between entering text and numbers" - "Add more participants" - "Confirm participants" - "Start new conversation" - "Select this item" - "Play video" - "People & Options" - "Debug" - "People & Options" - "General" - "People in this conversation" - "Make a call" - "Send message" - "Send message<br/><small>from %s</small>" - - Send photos - Send photo - - - Send audios - Send audio - - - Send videos - Send video - - - Send contact cards - Send contact card - - - Send attachments - Send attachment - - - %d attachments ready to send - One attachment ready to send - - "Send feedback" - "View in Google Play Store" - "Version info" - "Version %1$s" - "Open-source licences" - "Notifications" - "Attachment limit reached" - "Failed to load attachment." - "Add to Contacts?" - "Add Contact" - "Subject" - "Subject: " - "%s%s" - "Loading contact card" - "Couldn\'t load contact card" - "View contact card" - - %d contacts - %d contact - - "Contact cards" - "Birthday" - "Notes" - "Forward message" - "Reply" - "+1" - "+%d" - "SMS disabled" - "To send, set Messaging as the default SMS app" - "Set Messaging as the default SMS app" - "Change" - "To receive messages, set Messaging as the default SMS app" - "No preferred SIM selected for sending SMS messages" - "This app isn\'t allowed by the device owner." - "OK" - "Too many participants in a conversation" - - Invalid contacts - Invalid contact - - "Couldn\'t load camera image" - "You: " - "%s: " - "Draft" - "Once you start a new conversation, you’ll see it listed here" - "Archived conversations appear here" - "Loading conversations…" - "Picture" - "Audio clip" - "In-stream video" - "Contact card" - "MMS" - "Undo" - "Retry" - "Enter a contact name or phone number to start a new message" - "Block" - "Block %s" - "Unblock %s" - "Block %s?" - "You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived." - "Blocked contacts" - "UNBLOCK" - "Blocked contacts" - "Choose image from document library" - "Sending message" - "Message sent" - "Mobile data is turned off. Check your settings." - "Can\'t send messages in Aeroplane mode" - "Message could not be sent" - "Message downloaded" - "Mobile data is turned off. Check your settings." - "Can\'t download messages in Aeroplane mode" - "Message could not be downloaded" - "Zero" - "One" - "Two" - "Three" - "Four" - "Five" - "Six" - "Seven" - "Eight" - "Nine" - "Can\'t send message with %1$s, error %2$d" - "Can\'t send message with unknown operator, error %1$d" - "Fwd: %s" - "Message not sent: service not activated on network" - "Message not sent: invalid destination address" - "Message not sent: invalid message" - "Message not sent: unsupported content" - "Message not sent: unsupported message" - "Message not sent: too large" - "New message" - "View" - "Image" - "Could not find an appropriate application" - "Remove recipient" - "New message" - "Cancel" - "Edit access point" - "Not set" - "Named" - "APN" - "MMSC" - "MMS proxy" - "MMS port" - "MCC" - "MNC" - "APN type" - "Delete APN" - "New APN" - "Save" - "Discard" - "The Name field can\'t be empty." - "The APN cannot be empty." - "MCC field must be 3 digits." - "MNC field must be 2 or 3 digits." - "Restoring default APN settings." - "Reset to default" - "Reset default APN settings completed" - "Untitled" - "Access point names" - "APNs" - "New APN" - "Access Point Name settings are not available for this user" - "Copy to clipboard?" - "Copy" - "to %s" - "General" - "Advanced" - "General settings" - "Advanced settings" - "\"%s\" SIM" - "Send individual SMS messages to all recipients. Only you will get any replies" - "Send a single MMS to all recipients" - "Unknown number" - "New message" - "New message" - "SIM selector" - "%1$s selected, SIM selector" - "Edit subject" - "Select SIM or edit subject" - "Touch and hold to record audio" - "Start new conversation" - "Messaging" - "Messaging List" - "Messaging" - "New message" - "Conversation list" - "Loading conversations" - "Loading messages" - "View more conversations" - "View more messages" - "Conversation deleted" - "Conversation deleted. Touch to show a different Messaging conversation" - "Blocked" - "Unblocked" - "Storage space is low. Some data may be lost." - "Select attachments" - "Confirm selection" - "%d selected" - "Please remove one or more attachments and try again." - "You can try sending your message, but it might not be delivered unless you remove one or more attachments." - "You can only send one video per message. Please remove additional videos and try again." - "Messaging failed to load attachment." - "Send anyway" - "Could not start conversation" - "%1$s (%2$s)" - "%s selected" - diff --git a/res/values-en-rIN/arrays.xml b/res/values-en-rIN/arrays.xml deleted file mode 100644 index 94d5aff..0000000 --- a/res/values-en-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "no subject" - "nosubject" - - - "Yes" - "No" - "OK" - "Hehe" - "Thanks" - "I agree" - "Nice" - "On my way" - "OK, let me get back to you later" - ":)" - ":(" - - diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml deleted file mode 100644 index 11048a5..0000000 --- a/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messaging" - "Messaging" - "Select conversation" - "Settings" - "Send Message" - "Add an attachment" - "Help" - "Welcome" - "Skip" - "Next >" - "Next" - "Exit" - "Settings >" - "Settings" - "Messaging needs permission to SMS, Phone and Contacts." - "You can change permissions in Settings > Apps > Messaging > Permissions." - "You can change permissions in Settings, Apps, Messaging, Permissions." - "Frequents" - "All contacts" - "Send to %s" - "Capture pictures or video" - "Choose images from this device" - "Record audio" - "Choose photo" - "The media is selected." - "The media is unselected." - "%d selected" - "image %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "image" - "Record audio" - "Share" - "Just now" - "Now" - - %d mins - %d min - - - %d hours - %d hour - - - %d days - %d day - - - %d weeks - a week - - - %d months - a month - - - %d years - a year - - "Class 0 message" - "Save" - "Device is low on space. Messaging will automatically delete older messages to free up space." - "Storage space running out" - "Messaging might not send or receive messages until more space is available on your device." - "Low SMS storage. You may need to delete messages." - "Confirm your phone number" - "This one-off step will ensure Messaging will deliver your group messages properly." - "Phone number" - "Delete all messages with media" - "Delete messages older than %s" - "Auto-delete messages older than %s" - "Ignore" - "Delete all messages with media?" - "Delete messages older than %s?" - "Delete messages older than %s and turn on auto-delete?" - "%s said" - "You said" - "Message from %s" - "You sent a message" - "Sending…" - "Not sent. Touch to try again." - "Not sent. Trying again…" - "Resend or delete" - "Please make a voice call to emergency services. Your text message could not be delivered at this time." - "Failed" - "New MMS message to download" - "New MMS message" - "Couldn\'t download" - "Touch to try again" - "Touch to download" - "Download or delete" - "Downloading…" - "Message expired or not available" - "size: %1$s, expiration: %2$s" - "Can\'t send. Recipient not valid." - "Service not activated on network" - "Couldn\'t send due to network problem" - "Message expired or not available" - "(No subject)" - "Unknown sender" - "Delivered" - "Couldn\'t download message %1$s from %2$s." - "Couldn\'t complete database operation due to low memory" - "Message not sent" - "Some messages not sent in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message not downloaded" - "Some messages not downloaded in Messaging" - - %d messages in %d conversations - %d messages in one conversation - - "Message to %1$s not sent" - "Please make a voice call to emergency services. Your text message to %1$s could not be delivered at this time." - - %d new messages - New message - - "Start" - "Camera not available" - "Camera not available" - "Video capture not available" - "Can\'t save media" - "Can\'t take picture" - "Back" - "Archived" - "Delete" - "Archive" - "Unarchive" - "Turn off notifications" - "Turn on notifications" - "Add contact" - "Download" - "Send" - "Delete" - "Delete this message" - "This action cannot be undone." - "Delete" - - Delete these conversations? - Delete this conversation? - - "Delete" - "Cancel" - "To" - "Select multiple images" - "Confirm selection" - "+%d" - "Can\'t record audio. Try again." - "Can\'t play audio. Try again." - "Couldn\'t save audio. Try again." - "Touch & hold" - ", " - " " - ": " - " " - "Picture" - "Audio clip" - "Video" - "Contact card" - "Download" - "Reply via SMS" - "Reply via MMS" - "Reply" - - %d participants - %d participant - - "Me" - "Contact blocked & archived" - "Contact unblocked & unarchived" - "%d archived" - "%d unarchived" - "Notifications turned off" - "Notifications turned on" - "All set. Touch Send again." - "Messaging successfully set as the default SMS app." - - Discard attachments - Discard attachment - - "Audio attachment" - "Play audio attachment" - "Pause" - "Message from %s: %s." - "Failed message from %s: %s. Time: %s." - "Message from %s: %s. Time: %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message from %s: %s. Time: %s. %s." - "Message from %s: %s. Time: %s. %s." - "Unsent message to %s: %s. Time: %s." - "Sending message to %s: %s. Time: %s." - "Failed message to %s: %s. Time: %s." - "Message to %s: %s. Time: %s." - "Failed message. Touch to retry." - "Conversation with %s" - "Delete subject" - "Capture video" - "Capture a still image" - "Take picture" - "Start recording video" - "Switch to full screen camera" - "Switch between front and back camera" - "Stop recording and attach video" - "Stop recording video" - "Messaging photos" - - %d photos saved to \"%s\" album - %d photo saved to \"%s\" album - - - %d videos saved to \"%s\" album - %d video saved to \"%s\" album - - - %d attachments saved to \"%s\" album - %d attachment saved to \"%s\" album - - - %d attachments saved to \"Downloads\" - %d attachment saved to \"Downloads\" - - - %d attachments saved - %d attachment saved - - - Couldn\'t save %d attachments - Couldn\'t save %d attachment - - "Saved MMS attachment" - "Settings" - "Archived" - "Close" - "MMS" - "Advanced" - "Debug" - "Notifications" - "Sound" - "Silent" - "Vibrate" - "Blocked" - "SMS delivery reports" - "Request a delivery report for each SMS that you send" - "Auto-retrieve" - "Automatically retrieve MMS messages" - "Roaming auto-retrieve" - "Automatically retrieve MMS when roaming" - "Group messaging" - "Use MMS to send a single message when there are multiple recipients" - "Default SMS app" - "Default SMS app" - - - "Your phone number" - "Unknown" - "Outgoing message sounds" - "Dump SMS" - "Dump received SMS raw data into external storage file" - "Dump MMS" - "Dump received MMS raw data into external storage file" - "Wireless alerts" - "Message options" - "Copy text" - "View details" - "Delete" - "Forward" - "Message details" - "Type: " - "Text message" - "Multimedia message" - "From: " - "To: " - "Sent: " - "Received: " - "Subject: " - "Size: " - "Priority: " - "SIM: " - "High" - "Normal" - "Low" - "SIM %s" - "Hidden sender address" - "Can\'t send message while loading attachments." - "Can\'t load attachment. Try again." - "Network is not ready. Try again." - "Delete text" - "Switch between entering text and numbers" - "Add more participants" - "Confirm participants" - "Start new conversation" - "Select this item" - "Play video" - "People & Options" - "Debug" - "People & Options" - "General" - "People in this conversation" - "Make a call" - "Send message" - "Send message<br/><small>from %s</small>" - - Send photos - Send photo - - - Send audios - Send audio - - - Send videos - Send video - - - Send contact cards - Send contact card - - - Send attachments - Send attachment - - - %d attachments ready to send - One attachment ready to send - - "Send feedback" - "View in Google Play Store" - "Version info" - "Version %1$s" - "Open-source licences" - "Notifications" - "Attachment limit reached" - "Failed to load attachment." - "Add to Contacts?" - "Add Contact" - "Subject" - "Subject: " - "%s%s" - "Loading contact card" - "Couldn\'t load contact card" - "View contact card" - - %d contacts - %d contact - - "Contact cards" - "Birthday" - "Notes" - "Forward message" - "Reply" - "+1" - "+%d" - "SMS disabled" - "To send, set Messaging as the default SMS app" - "Set Messaging as the default SMS app" - "Change" - "To receive messages, set Messaging as the default SMS app" - "No preferred SIM selected for sending SMS messages" - "This app isn\'t allowed by the device owner." - "OK" - "Too many participants in a conversation" - - Invalid contacts - Invalid contact - - "Couldn\'t load camera image" - "You: " - "%s: " - "Draft" - "Once you start a new conversation, you’ll see it listed here" - "Archived conversations appear here" - "Loading conversations…" - "Picture" - "Audio clip" - "In-stream video" - "Contact card" - "MMS" - "Undo" - "Retry" - "Enter a contact name or phone number to start a new message" - "Block" - "Block %s" - "Unblock %s" - "Block %s?" - "You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived." - "Blocked contacts" - "UNBLOCK" - "Blocked contacts" - "Choose image from document library" - "Sending message" - "Message sent" - "Mobile data is turned off. Check your settings." - "Can\'t send messages in Aeroplane mode" - "Message could not be sent" - "Message downloaded" - "Mobile data is turned off. Check your settings." - "Can\'t download messages in Aeroplane mode" - "Message could not be downloaded" - "Zero" - "One" - "Two" - "Three" - "Four" - "Five" - "Six" - "Seven" - "Eight" - "Nine" - "Can\'t send message with %1$s, error %2$d" - "Can\'t send message with unknown operator, error %1$d" - "Fwd: %s" - "Message not sent: service not activated on network" - "Message not sent: invalid destination address" - "Message not sent: invalid message" - "Message not sent: unsupported content" - "Message not sent: unsupported message" - "Message not sent: too large" - "New message" - "View" - "Image" - "Could not find an appropriate application" - "Remove recipient" - "New message" - "Cancel" - "Edit access point" - "Not set" - "Named" - "APN" - "MMSC" - "MMS proxy" - "MMS port" - "MCC" - "MNC" - "APN type" - "Delete APN" - "New APN" - "Save" - "Discard" - "The Name field can\'t be empty." - "The APN cannot be empty." - "MCC field must be 3 digits." - "MNC field must be 2 or 3 digits." - "Restoring default APN settings." - "Reset to default" - "Reset default APN settings completed" - "Untitled" - "Access point names" - "APNs" - "New APN" - "Access Point Name settings are not available for this user" - "Copy to clipboard?" - "Copy" - "to %s" - "General" - "Advanced" - "General settings" - "Advanced settings" - "\"%s\" SIM" - "Send individual SMS messages to all recipients. Only you will get any replies" - "Send a single MMS to all recipients" - "Unknown number" - "New message" - "New message" - "SIM selector" - "%1$s selected, SIM selector" - "Edit subject" - "Select SIM or edit subject" - "Touch and hold to record audio" - "Start new conversation" - "Messaging" - "Messaging List" - "Messaging" - "New message" - "Conversation list" - "Loading conversations" - "Loading messages" - "View more conversations" - "View more messages" - "Conversation deleted" - "Conversation deleted. Touch to show a different Messaging conversation" - "Blocked" - "Unblocked" - "Storage space is low. Some data may be lost." - "Select attachments" - "Confirm selection" - "%d selected" - "Please remove one or more attachments and try again." - "You can try sending your message, but it might not be delivered unless you remove one or more attachments." - "You can only send one video per message. Please remove additional videos and try again." - "Messaging failed to load attachment." - "Send anyway" - "Could not start conversation" - "%1$s (%2$s)" - "%s selected" - diff --git a/res/values-es-rUS/arrays.xml b/res/values-es-rUS/arrays.xml deleted file mode 100644 index 4a3fbb7..0000000 --- a/res/values-es-rUS/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sin asunto" - "sinasunto" - - - "Sí" - "No" - "Aceptar" - "Je, je" - "Gracias" - "Acepto" - "Genial" - "Estoy en camino." - "De acuerdo, te llamo más tarde." - ":)" - ":(" - - diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml deleted file mode 100644 index 1da2400..0000000 --- a/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Centro de mensajes" - "Centro de mensajes" - "Seleccionar conversación" - "Configuración" - "Enviar mensaje" - "Agregar un archivo adjunto" - "Ayuda" - "Te damos la bienvenida" - "Omitir" - "Siguiente >" - "Siguiente" - "Salir" - "Configuración >" - "Configuración" - "Centro de Mensajes necesita permiso para acceder a SMS, Teléfono y Contactos." - "Puedes cambiar los permisos en Configuración > Aplicaciones > Centro de Mensajes > Permisos." - "Puedes cambiar los permisos en Configuración, Aplicaciones, Centro de Mensajes, Permisos." - "Frecuentes" - "Todos los contactos" - "Enviar a %s" - "Capturar imágenes o video" - "Seleccionar imágenes de este dispositivo" - "Grabar audio" - "Elegir foto" - "El medio está seleccionado." - "Los medios no están seleccionados." - "Seleccionado: %d" - "imagen %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imagen" - "Grabar audio" - "Compartir" - "Recién" - "Recién" - - %d minutos - %d minuto - - - %d horas - %d hora - - - %d días - %d día - - - %d semanas - una semana - - - %d meses - un mes - - - %d años - un año - - "Mensaje clase 0" - "Guardar" - "El dispositivo tiene poco espacio de almacenamiento. El Centro de Mensajes borrará automáticamente los mensajes antiguos para liberar espacio." - "Queda poco espacio de almacenamiento" - "Es posible que Centro de Mensajes no envíe ni reciba mensajes hasta que se libere más espacio en el dispositivo." - "Como queda poco espacio para SMS, tal vez debas eliminar mensajes." - "Confirmar tu número de teléfono" - "Este paso, que se realiza por única vez, asegurará que Centro de Mensajes envíe los mensajes grupales de forma correcta." - "Número de teléfono" - "Eliminar todos los mensajes con contenido multimedia" - "Eliminar mensajes de más de %s" - "Eliminar automáticamente mensajes de más de %s" - "Ignorar" - "¿Eliminar todos los mensajes con contenido multimedia?" - "¿Eliminar mensajes de más de %s?" - "¿Eliminar mensajes de más de %s y activar la eliminación automática?" - "%s dijo" - "Tú dijiste" - "Mensaje de %s" - "Enviaste un mensaje" - "Enviando…" - "El mensaje no se envió. Toca para volver a intentarlo." - "El mensaje no se envió. Reintentando…" - "Volver a enviar o eliminar" - "Realiza una llamada de voz a los servicios de emergencia. No se pudo enviar el mensaje de texto esta vez." - "Error" - "Nuevo mensaje MMS para descargar" - "Nuevo mensaje MMS" - "El mensaje no se pudo descargar." - "Toca para volver a intentarlo." - "Toca para descargar." - "Descargar o eliminar" - "Descargando…" - "El mensaje venció o no está disponible." - "tamaño: %1$s; vencimiento: %2$s" - "No se puede enviar porque el destinatario no es válido." - "No se activó el servicio en la red." - "No se pudo enviar debido a un problema de la red." - "El mensaje venció o no está disponible." - "(Sin asunto)" - "Remitente desconocido" - "Entregado" - "No se pudo descargar el mensaje %1$s de %2$s." - "No se pudo completar la operación de la base de datos por falta de memoria." - "Mensaje no enviado" - "No se pudieron enviar algunos mensajes en Centro de Mensajes" - - %d mensajes en %d conversaciones - %d mensajes en una conversación - - "Mensaje no descargado" - "No se pudieron descargar algunos mensajes en Centro de Mensajes" - - %d mensajes en %d conversaciones - %d mensaje en una conversación - - "Mensaje al %1$s no enviado" - "Realiza una llamada de voz a los servicios de emergencia. No se pudo enviar el mensaje de texto a %1$s esta vez." - - %d mensajes nuevos - Un mensaje nuevo - - "Iniciar" - "Cámara no disponible" - "Cámara no disponible" - "Captura de video no disponible" - "No se puede guardar el contenido multimedia." - "No se puede tomar la foto." - "Atrás" - "Archivadas" - "Eliminar" - "Archivo" - "Eliminar del archivo" - "Desactivar notificaciones" - "Activar notificaciones" - "Agregar contacto" - "Descargar" - "Enviar" - "Eliminar" - "¿Deseas eliminar este mensaje?" - "Esta acción no se puede deshacer." - "Eliminar" - - ¿Deseas borrar estas conversaciones? - ¿Deseas borrar esta conversación? - - "Eliminar" - "Cancelar" - "Para" - "Seleccionar varias imágenes" - "Confirmar selección" - "%d más" - "No se puede grabar el audio. Vuelve a intentarlo." - "No se puede reproducir el audio. Vuelve a intentarlo." - "No se pudo guardar el audio. Vuelve a intentarlo." - "Mantener presionado" - ", " - " " - ": " - " " - "Imagen" - "Clip de audio" - "Video" - "Tarjeta de contacto" - "Descargar" - "Responder con SMS" - "Responder (MMS)" - "Responder" - - %d participantes - %d participante - - "Yo" - "Contacto bloqueado y archivado" - "Contacto desbloqueado y sin archivar" - "%d archivada(s)" - "%d no archivada(s)" - "Notificaciones desactivadas" - "Notificaciones activadas" - "Todo listo. Vuelve a tocar Enviar." - "Se estableció de forma correcta Centro de Mensajes como tu aplicación de SMS predeterminada." - - Descartar archivos adjuntos - Descartar archivo adjunto - - "Archivo adjunto de audio" - "Reproducir archivo adjunto de audio" - "Pausa" - "Mensaje de %s: %s" - "Error del mensaje de %s: %s Hora: %s" - "Mensaje de %s: %s Hora: %s" - "Mensaje no enviado a %s: %s Hora: %s" - "Enviando mensaje a %s: %s Hora: %s" - "Error de mensaje a %s: %s Hora: %s" - "Mensaje a %s: %s Hora: %s" - "Error del mensaje de %s: %s Hora: %s %s" - "Mensaje de %s: %s Hora: %s %s" - "Mensaje no enviado a %s: %s Hora: %s" - "Enviando mensaje a %s: %s Hora: %s" - "Error del mensaje a %s: %s Hora: %s" - "Mensaje a %s: %s Hora: %s" - "Se produjo un error en el mensaje. Toca para volver a intentarlo." - "Conversación con %s" - "Eliminar asunto" - "Capturar video" - "Capturar una imagen fija" - "Tomar fotografía" - "Iniciar grabación de video" - "Cambiar a cámara en pantalla completa" - "Alternar entre cámara frontal y trasera" - "Detener la grabación y adjuntar video" - "Detener la grabación de video" - "Fotos de Centro de Mensajes" - - Se guardaron %d fotos en el álbum \"%s\" - Se guardó %d foto en el álbum \"%s\" - - - Se guardaron %d videos en el álbum \"%s\" - Se guardó %d video en el álbum \"%s\" - - - Se guardaron %d archivos adjuntos en el álbum \"%s\" - Se guardó %d archivo adjunto en el álbum \"%s\" - - - Se guardaron %d archivos adjuntos en \"Descargas\" - Se guardó %d archivo adjunto en \"Descargas\" - - - Se guardaron %d archivos adjuntos - Se guardó %d archivo adjunto - - - No se pudieron guardar %d archivos adjuntos - No se pudo guardar %d archivo adjunto - - "Archivo adjunto de MMS guardado" - "Configuración" - "Archivadas" - "Cerrar" - "MMS" - "Avanzada" - "Depurar" - "Notificaciones" - "Sonido" - "Silencio" - "Vibrar" - "Bloqueado" - "Informes de entrega de SMS" - "Solicitar un informe de entrega al enviar un SMS" - "Recuperación automática" - "Recuperar mensajes MMS automáticamente" - "Recuperación automática en roaming" - "Recuperar MMS automáticamente en roaming" - "Mensajes grupales" - "Utiliza MMS para enviar un mismo mensaje a varios destinatarios." - "Aplicación de SMS predeterminada" - "Aplicación de SMS predeterminada" - - - "Tu número de teléfono" - "Desconocido" - "Sonidos de mensaje saliente" - "Volcar SMS" - "Volcar datos sin procesar de SMS en el archivo de almacenamiento externo" - "Volcar MMS" - "Volcar datos sin procesar de MMS recibidos en un archivo de almacenamiento externo" - "Alertas inalámbricas" - "Opciones de mensaje" - "Copiar el texto" - "Ver detalles" - "Eliminar" - "Reenviar" - "Detalles del mensaje" - "Tipo: " - "Mensaje de texto" - "Mensaje multimedia" - "De: " - "Para: " - "Enviado: " - "Fecha de recepción: " - "Asunto: " - "Tamaño: " - "Prioridad: " - "SIM: " - "Alta" - "Normal" - "Baja" - "SIM %s" - "Dirección de remitente oculta" - "No se pueden enviar mensajes mientras se cargan adjuntos." - "No se puede cargar el archivo adjunto. Vuelve a intentarlo." - "La red no está lista. Vuelve a intentarlo." - "Suprimir texto" - "Cambiar entre ingresar texto y números" - "Agregar más participantes" - "Confirmar participantes" - "Comenzar una nueva conversación" - "Seleccionar este elemento" - "Reproducir video" - "Contactos y opciones" - "Depurar" - "Contactos y opciones" - "General" - "Contactos en esta conversación" - "Hacer una llamada" - "Enviar mensaje" - "Enviar mensaje<br/><small>de %s</small>" - - Enviar fotos - Enviar foto - - - Enviar audios - Enviar audio - - - Enviar videos - Enviar video - - - Enviar tarjetas de contacto - Enviar tarjeta de contacto - - - Enviar archivos adjuntos - Enviar archivo adjunto - - - %d archivos adjuntos listos para enviar - Un archivo adjunto listo para enviar - - "Enviar comentarios" - "Ver en Google Play Store" - "Información de la versión" - "Versión %1$s" - "Licencias de código abierto" - "Notificaciones" - "Se alcanzó el límite de archivos adjuntos." - "Se produjo un error al cargar el archivo adjunto." - "¿Agregar a contactos?" - "Agregar contacto" - "Asunto" - "Asunto: " - "%s%s" - "Cargando tarjeta de contacto" - "No se pudo cargar la tarjeta de contacto." - "Ver tarjeta de contacto" - - %d contactos - %d contacto - - "Tarjetas de contactos" - "Fecha de nacimiento" - "Notas" - "Reenviar mensaje" - "Responder" - "Hacer +1" - "+%d" - "SMS inhabilitados" - "Establecer Centro de mensajes como la aplicación de SMS predeterminada para enviar mensajes" - "Establecer Centro de Mensajes como la aplicación de SMS predeterminada" - "Cambiar" - "Establecer Centro de mensajes como la aplicación de SMS predeterminada para recibir mensajes" - "No se seleccionó ninguna SIM preferida para el envío de mensajes SMS." - "El propietario del dispositivo no permite esta aplicación." - "Aceptar" - "Demasiados participantes en una conversación" - - Contactos no válidos - Contacto no válido - - "No se pudo cargar la imagen de la cámara." - "Tú: " - "%s: " - "Borrador" - "Cuando inicies una conversación nueva, aparecerá aquí." - "Las conversaciones archivadas aparecen aquí." - "Cargando conversaciones…" - "Imagen" - "Clip de audio" - "Video" - "Tarjeta de contacto" - "MMS" - "Deshacer" - "Volver a intentar" - "Ingresa un nombre de contacto o número de teléfono para crear un nuevo mensaje." - "Bloquear" - "Bloquear a %s" - "Desbloquear a %s" - "¿Bloquear a %s?" - "Seguirás recibiendo mensajes de este número, pero no verás notificaciones. Esta conversación se archivará." - "Contactos bloqueados" - "DESBLOQUEAR" - "Contactos bloqueados" - "Seleccionar imagen de la galería de documentos" - "Enviando el mensaje" - "Se envió el mensaje." - "Los datos móviles están desactivados. Comprueba la configuración." - "No se pueden enviar mensajes en modo de avión." - "No se pudo enviar el mensaje." - "Se descargó el mensaje." - "Los datos móviles están desactivados. Comprueba la configuración." - "No se pueden descargar mensajes en modo de avión." - "No se pudo descargar el mensaje." - "Cero" - "Uno" - "Dos" - "Tres" - "Cuatro" - "Cinco" - "Seis" - "Siete" - "Ocho" - "Nueve" - "Error %2$d. No se puede enviar el mensaje con %1$s." - "Error %1$d. No se puede enviar el mensaje con un proveedor desconocido." - "Rv: %s" - "No se envió el mensaje porque el servicio no está activado en la red." - "Mensaje no enviado: dirección de destino no válida" - "Mensaje no enviado: mensaje no válido" - "Mensaje no enviado: contenido no admitido" - "Mensaje no enviado: mensaje no admitido" - "Mensaje no enviado: demasiado largo" - "Mensaje nuevo" - "Ver" - "Imagen" - "No se pudo encontrar una aplicación adecuada." - "Eliminar destinatario" - "Mensaje nuevo" - "Cancelar" - "Editar el punto de acceso" - "Sin establecer" - "Nombre" - "APN" - "MMSC" - "Proxy de MMS" - "Puerto MMS" - "MCC" - "MNC" - "Tipo de APN" - "Eliminar APN" - "APN nuevo" - "Guardar" - "Descartar" - "El campo Nombre no puede estar vacío." - "El APN no puede estar vacío." - "El campo de MCC debe contener tres dígitos." - "El campo MNC debe contener dos o tres dígitos." - "Restaurando la configuración APN predeterminada" - "Restablecer parámetros predeterminados" - "Se restableció la configuración predeterminada de APN." - "Sin título" - "Nombres de puntos de acceso" - "APN" - "APN nuevo" - "La configuración del nombre de punto de acceso no está disponible para este usuario." - "¿Copiar en el portapapeles?" - "Copiar" - "a %s" - "General" - "Avanzada" - "Configuración general" - "Configuración avanzada" - "SIM \"%s\"" - "Enviar mensajes de SMS individuales a todos los destinatarios (solo tú recibirás las respuestas)." - "Enviar un único MMS a todos los destinatarios" - "Número desconocido" - "Mensaje nuevo" - "Mensaje nuevo" - "Selector de SIM" - "Se seleccionó %1$s, selector de SIM." - "Editar asunto" - "Seleccionar SIM o editar asunto" - "Mantén presionado para grabar audio." - "Comenzar una nueva conversación" - "Centro de mensajes" - "Lista de Centro de Mensajes" - "Centro de mensajes" - "Mensaje nuevo" - "Lista de conversaciones" - "Cargando conversaciones" - "Cargando mensajes…" - "Ver más conversaciones" - "Ver más mensajes" - "Se eliminó la conversación." - "Se borró la conversación. Toca para mostrar otra conversación de Centro de Mensajes" - "Bloqueado" - "Desbloqueado" - "Hay poco espacio de almacenamiento. Podrían perderse algunos datos." - "Seleccionar archivos adjuntos" - "Confirmar selección" - "Seleccionado: %d" - "Elimina uno o más archivos adjuntos y vuelve a intentarlo." - "Puedes intentar enviar el mensaje, pero podría no enviarse a menos que elimines uno más archivos adjuntos." - "Solo puedes enviar un video por mensaje. Quita los videos adicionales y vuelve a intentarlo." - "Centro de Mensajes no pudo cargar el archivo adjunto." - "Enviar de todas formas" - "No se pudo iniciar la conversación." - "%1$s (%2$s)" - "Se seleccionó %s." - diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml deleted file mode 100644 index ca8e06b..0000000 --- a/res/values-es/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sin asunto" - "sinasunto" - - - "Sí" - "No" - "Ok" - "Je, je" - "Gracias" - "Acepto" - "Genial" - "Voy de camino" - "Vale, te llamo más tarde" - ":)" - ":(" - - diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml deleted file mode 100644 index 78ffe68..0000000 --- a/res/values-es/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mensajes" - "Mensajes" - "Seleccionar conversación" - "Ajustes" - "Enviar mensaje" - "Adjuntar un archivo" - "Ayuda" - "Te damos la bienvenida" - "Saltar" - "Siguiente >" - "Siguiente" - "Salir" - "Ajustes >" - "Ajustes" - "Mensajes necesita permiso para acceder a los SMS, al teléfono y a los contactos." - "Puedes cambiar los permisos en Ajustes > Aplicaciones > Mensajes > Permisos." - "Puedes cambiar los permisos en Ajustes, Aplicaciones, Mensajes, Permisos." - "Frecuentes" - "Todos los contactos" - "Enviar a %s" - "Capturar imágenes o vídeo" - "Seleccionar imágenes de este dispositivo" - "Grabar audio" - "Elegir foto" - "Contenido multimedia seleccionado." - "Contenido multimedia no seleccionado." - "Seleccionado: %d" - "imagen del %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imagen" - "Grabar audio" - "Compartir" - "Ahora mismo" - "Ahora" - - %d min - %d min - - - %d horas - %d hora - - - %d días - %d día - - - %d semanas - una semana - - - %d meses - un mes - - - %d años - un año - - "Mensaje de clase 0" - "Guardar" - "Casi no queda espacio en el dispositivo. La aplicación Mensajes eliminará automáticamente los mensajes más antiguos para liberar espacio." - "Queda poco espacio" - "Es posible que Mensajes no envíe ni reciba mensajes hasta que haya más espacio disponible en el dispositivo." - "Queda poco espacio para recibir SMS. Es posible que tengas que eliminar mensajes." - "Confirma tu número de teléfono" - "Este paso único garantiza que Mensajes enviará tus mensajes de grupo correctamente." - "Número de teléfono" - "Eliminar todos los mensajes con archivos multimedia" - "Eliminar mensajes anteriores a %s" - "Eliminar automáticamente mensajes anteriores a %s" - "Ignorar" - "¿Eliminar todos los mensajes con archivos multimedia?" - "¿Eliminar mensajes anteriores a %s?" - "¿Eliminar mensajes anteriores a %s y activar eliminación automática?" - "%s ha dicho" - "Has dicho" - "Mensaje de %s" - "Has enviado un mensaje" - "Enviando..." - "No enviado. Toca para volver a intentarlo." - "No enviado. Volviendo a intentar…" - "Volver a enviar o eliminar" - "Llama a los servicios de emergencias. Tu mensaje de texto no se puede entregar en estos momentos." - "Error" - "Nuevo mensaje MMS que descargar" - "Nuevo mensaje MMS" - "Error al descargar" - "Toca para volver a intentarlo" - "Toca para descargar" - "Descargar o eliminar" - "Descargando..." - "El mensaje ha caducado o no está disponible" - "tamaño: %1$s, vencimiento: %2$s" - "No se puede enviar porque el destinatario no es válido." - "No se ha activado el servicio en la red" - "No se ha podido enviar por un problema de red" - "El mensaje ha caducado o no está disponible" - "(Sin asunto)" - "Destinatario desconocido" - "Entregado" - "No se ha podido descargar el mensaje %1$s de %2$s." - "No se ha podido completar la operación de la base de datos por falta de memoria" - "Mensaje no enviado" - "Hay algunos mensajes no enviados en Mensajes" - - %d mensajes en %d conversaciones - %d mensajes en una conversación - - "Mensaje no descargado" - "Algunos mensajes no se han descargado en Mensajes" - - %d mensajes en %d conversaciones - %d mensajes en una conversación - - "Mensaje para %1$s no enviado" - "Llama a los servicios de emergencias. Tu mensaje de texto enviado al número %1$s no se puede entregar en estos momentos." - - %d mensajes nuevos - Mensaje nuevo - - "Iniciar" - "Cámara no disponible" - "Cámara no disponible" - "Captura de vídeo no disponible" - "No se puede guardar el archivo multimedia" - "No se pueden hacer fotos" - "Atrás" - "Archivado" - "Eliminar" - "Archivar" - "No archivar" - "Desactivar notificaciones" - "Activar notificaciones" - "Añadir contacto" - "Descargar" - "Enviar" - "Eliminar" - "¿Eliminar este mensaje?" - "Esta acción no se puede deshacer." - "Eliminar" - - ¿Eliminar estas conversaciones? - ¿Eliminar esta conversación? - - "Eliminar" - "Cancelar" - "Para" - "Seleccionar varias imágenes" - "Confirmar selección" - "+%d" - "No se puede grabar el audio. Vuelve a intentarlo." - "No se puede reproducir el audio. Vuelve a intentarlo." - "No se ha podido guardar el audio. Vuelve a intentarlo." - "Mantener pulsado" - ", " - " " - ": " - " " - "Imagen" - "Fragmento de audio" - "Vídeo" - "Tarjeta de contacto" - "Descargar" - "Responder con SMS" - "Responder por MMS" - "Responder" - - %d participantes - %d participante - - "Yo" - "Contacto bloqueado y archivado" - "Contacto desbloqueado y no archivado" - "%d archivadas" - "%d no archivadas" - "Notificaciones desactivadas" - "Notificaciones activadas" - "Listo. Toca Enviar de nuevo." - "Mensajes establecida como aplicación de SMS predeterminada." - - Descartar archivos adjuntos - Descartar archivo adjunto - - "Archivo de audio adjunto" - "Reproducir archivo de audio adjunto" - "Pausar" - "Mensaje de %s: %s." - "Error del mensaje de %s: %s. Hora: %s." - "Mensaje de %s: %s. Hora: %s." - "Mensaje no enviado a %s: %s. Hora: %s." - "Enviando mensaje a %s: %s. Hora: %s." - "Error del mensaje para %s: %s. Hora: %s." - "Mensaje para %s: %s. Hora: %s." - "Error del mensaje de %s: %s. Hora: %s. %s." - "Mensaje de %s: %s. Hora: %s. %s." - "Mensaje no enviado a %s: %s. Hora: %s." - "Enviando mensaje a %s: %s. Hora: %s." - "Error del mensaje para %s: %s. Hora: %s." - "Mensaje para %s: %s. Hora: %s." - "Error del mensaje. Toca para volver a intentarlo." - "Conversación con %s" - "Eliminar asunto" - "Capturar vídeo" - "Capturar una imagen fija" - "Hacer foto" - "Iniciar grabación de vídeo" - "Cambiar a cámara en pantalla completa" - "Cambiar de la cámara frontal a la trasera" - "Dejar de grabar y adjuntar vídeo" - "Detener grabación de vídeo" - "Fotos de Mensajes" - - %d fotos guardadas en el álbum %s - %d foto guardada en el álbum %s - - - %d vídeos guardados en el álbum %s - %d vídeo guardado en el álbum %s - - - %d archivos adjuntos guardados en el álbum %s - %d archivo adjunto guardado en el álbum %s - - - %d archivos adjuntos guardados en Descargas - %d archivo adjunto guardado en Descargas - - - %d archivos adjuntos guardados - %d archivo adjunto guardado - - - No se han podido guardar %d archivos adjuntos - No se ha podido guardar %d archivo adjunto - - "Adjunto de MMS guardado" - "Ajustes" - "Archivado" - "Cerrar" - "MMS" - "Opciones avanzadas" - "Depurar" - "Notificaciones" - "Sonido" - "Silencio" - "Vibrar" - "Bloqueado" - "Informes de entrega de SMS" - "Solicitar un informe de entrega al enviar un SMS" - "Recuperar automáticamente" - "Recuperar automáticamente mensajes MMS" - "Autorrecuperar en itinerancia" - "Recuperar MMS automáticamente en itinerancia" - "Mensaje de grupo" - "Usar MMS para enviar un único mensaje a varios destinatarios" - "Aplicación de SMS predeterminada" - "Aplicación de SMS predeterminada" - - - "Tu número de teléfono" - "Desconocido" - "Sonidos de mensajes enviados" - "Volcar SMS" - "Volcar datos sin procesar de SMS recibidos en un archivo del almacenamiento externo" - "Volcar MMS" - "Volcar datos sin procesar de MMS recibidos en un archivo del almacenamiento externo" - "Alertas inalámbricas" - "Opciones de mensaje" - "Copiar texto" - "Ver detalles" - "Eliminar" - "Reenviar" - "Detalles de mensaje" - "Tipo: " - "Mensaje de texto" - "Mensaje multimedia" - "De: " - "A: " - "Enviado: " - "Recibido: " - "Asunto: " - "Tamaño: " - "Prioridad: " - "SIM: " - "Alta" - "Normal" - "Baja" - "SIM %s" - "Dirección de remitente oculta" - "No se pueden enviar mensajes mientras se cargan adjuntos." - "No se ha podido cargar el archivo adjunto. Vuelve a intentarlo." - "La red no está lista. Inténtalo de nuevo." - "Eliminar texto" - "Alternar entre introducir texto y números" - "Añadir más participantes" - "Confirmar participantes" - "Iniciar nueva conversación" - "Seleccionar este elemento" - "Reproducir vídeo" - "Contactos y opciones" - "Depurar" - "Contactos y opciones" - "Generales" - "Contactos en esta conversación" - "Hacer una llamada" - "Enviar mensaje" - "Enviar mensaje<br/><small>de %s</small>" - - Enviar fotos - Enviar foto - - - Enviar archivos de audio - Enviar archivo de audio - - - Enviar vídeos - Enviar vídeo - - - Enviar tarjetas de contacto - Enviar tarjeta de contacto - - - Enviar archivos adjuntos - Enviar archivo adjunto - - - %d archivos adjuntos listos para enviar - Un archivo adjunto listo para enviar - - "Danos tu opinión" - "Ver en Google Play Store" - "Información de la versión" - "Versión %1$s" - "Licencias código abierto" - "Notificaciones" - "Límite de archivos adjuntos alcanzado" - "Error al cargar archivo adjunto." - "¿Añadir a contactos?" - "Añadir contacto" - "Asunto" - "Asunto: " - "%s%s" - "Cargando tarjeta de contacto" - "No se ha podido cargar la tarjeta de contacto" - "Ver tarjeta de contacto" - - %d contactos - %d contacto - - "Tarjetas de contacto" - "Cumpleaños" - "Notas" - "Reenviar mensaje" - "Responder" - "+1" - "+%d" - "SMS inhabilitados" - "Para enviar mensajes, establece Mensajes como aplicación de SMS predeterminada" - "Establecer Mensajes como aplicación de SMS predeterminada" - "Cambiar" - "Para recibir mensajes, establece Mensajes como aplicación de SMS predeterminada" - "No se ha seleccionado la SIM preferida para enviar mensajes SMS" - "El propietario del dispositivo no permite esta aplicación." - "Aceptar" - "Demasiados participantes en una conversación" - - Contactos no válidos - Contacto no válido - - "No se ha podido cargar la imagen de la cámara" - "Tú: " - "%s: " - "Borrador" - "Cuando inicies una conversación nueva, aparecerá aquí" - "Aquí aparecen las conversaciones archivadas" - "Cargando conversaciones..." - "Imagen" - "Fragmento de audio" - "Vídeo" - "Tarjeta de contacto" - "MMS" - "Deshacer" - "Volver a intentar" - "Escribe el nombre de un contacto o un número de teléfono para iniciar un mensaje nuevo" - "Bloquear" - "Bloquear a %s" - "Desbloquear a %s" - "¿Bloquear a %s?" - "Seguirás recibiendo mensajes de este número pero no volverás a recibir notificaciones. Esta conversación se archivará." - "Contactos bloqueados" - "DESBLOQUEAR" - "Contactos bloqueados" - "Selecciona imagen de la biblioteca de documentos" - "Enviando mensaje" - "Mensaje enviado" - "Los datos móviles están desactivados. Comprueba los ajustes." - "No se puede enviar mensajes en modo avión" - "Error al enviar mensaje" - "Mensaje descargado" - "Los datos móviles están desactivados. Comprueba los ajustes." - "No se pueden descargar mensajes en modo avión" - "No se ha podido descargar el mensaje" - "Cero" - "Uno" - "Dos" - "Tres" - "Cuatro" - "Cinco" - "Seis" - "Siete" - "Ocho" - "Nueve" - "No se puede enviar el mensaje con %1$s (error %2$d)" - "No se puede enviar el mensaje con un operador desconocido (error %1$d)" - "Rv: %s" - "Mensaje no enviado porque el servicio no se ha activado en la red" - "Mensaje no enviado: dirección de destino no válida" - "Mensaje no enviado: mensaje no válido" - "Mensaje no enviado: contenido no admitido" - "Mensaje no enviado: mensaje no admitido" - "Mensaje no enviado: demasiado largo" - "Mensaje nuevo" - "Ver" - "Imagen" - "No se ha podido encontrar una aplicación adecuada" - "Eliminar destinatario" - "Mensaje nuevo" - "Cancelar" - "Editar punto de acceso" - "No definida" - "Nombre" - "APN" - "MMSC" - "Proxy para MMS" - "Puerto para MMS" - "MCC" - "MNC" - "Tipo de APN" - "Eliminar APN" - "Nuevo APN" - "Guardar" - "Descartar" - "El campo Nombre no puede estar vacío." - "El campo APN no puede estar vacío." - "El campo MCC debe contener 3 dígitos." - "El campo MNC debe contener 2 o 3 dígitos." - "Restaurando la configuración de APN predeterminada" - "Restablecer ajustes" - "Se ha restablecido la configuración de APN predeterminada." - "Sin título" - "Nombre de puntos de acceso" - "APN" - "Nuevo APN" - "Los ajustes del nombre de punto de acceso no están disponibles para este usuario" - "¿Copiar al portapapeles?" - "Copiar" - "para %s" - "General" - "Opciones avanzadas" - "Ajustes generales" - "Ajustes avanzados" - "SIM \"%s\"" - "Enviar mensajes SMS individuales. Solo tú recibirás las respuestas" - "Enviar un único MMS a todos los destinatarios" - "Número desconocido" - "Mensaje nuevo" - "Mensaje nuevo." - "Selector de SIM" - "%1$s seleccionada, selector de SIM" - "Editar asunto" - "Seleccionar SIM o editar asunto" - "Mantener pulsado para grabar audio" - "Iniciar nueva conversación" - "Mensajes" - "Lista de mensajes" - "Mensajes" - "Mensaje nuevo" - "Lista de conversaciones" - "Cargando conversaciones" - "Cargando mensajes" - "Ver más conversaciones" - "Ver más mensajes" - "Conversación eliminada" - "Conversación eliminada. Toca para ver otra conversación de Mensajes" - "Bloqueado" - "Desbloqueado" - "Queda poco espacio de almacenamiento. Es posible que se pierdan algunos datos." - "Seleccionar adjuntos" - "Confirmar selección" - "Seleccionado: %d" - "Elimina uno o varios archivos adjuntos y vuelve a intentarlo." - "Puedes probar a enviar el mensaje, pero quizás no se envíe hasta que elimines uno o varios archivos adjuntos." - "Solo puedes enviar un vídeo por mensaje. Elimina más vídeos y vuelve a intentarlo." - "El mensaje no ha podido cargar el archivo adjunto." - "Enviar de todos modos" - "No se ha podido iniciar la conversación" - "%1$s (%2$s)" - "%s seleccionada" - diff --git a/res/values-et-rEE/arrays.xml b/res/values-et-rEE/arrays.xml deleted file mode 100644 index 16e1744..0000000 --- a/res/values-et-rEE/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "teema puudub" - "teema puudub" - - - "Jah" - "Ei" - "OK" - "Hehe" - "Aitäh" - "Nõustun" - "Tore" - "Olen teel" - "OK, vastan sulle hiljem" - ":)" - ":(" - - diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml deleted file mode 100644 index 79097fe..0000000 --- a/res/values-et-rEE/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Sõnumside" - "Sõnumside" - "Vestluse valimine" - "Seaded" - "Saada sõnum" - "Manuse lisamine" - "Abi" - "Tere tulemast" - "Vahelejätmine" - "Järgmine >" - "Järgmine" - "Väljumine" - "Seaded >" - "Seaded" - "Sõnumside vajab luba SMS-idele, telefonile ja kontaktidele juurdepääsemiseks." - "Saate muuta lube jaotises Seaded > Rakendused > Sõnumside > Load." - "Lubade muutmiseks tehke valikud Seaded, Rakendused, Sõnumside, Load." - "Sagedased" - "Kõik kontaktid" - "Saada numbrile %s" - "Jäädvustage pilte või videoid" - "Kujutiste valimine sellest seadmest" - "Heli salvestamine" - "Foto valimine" - "Meedia on valitud." - "Meediat pole valitud." - "%d on valitud" - "pilt kuupäevaga %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "pilt" - "Heli salvestamine" - "Jagamine" - "Hetk tagasi" - "Praegu" - - %d min - %d min - - - %d tundi - %d tund - - - %d päeva - %d päev - - - %d nädalat - nädal - - - %d kuud - kuu - - - %d aastat - aasta - - "0-klassi sõnum" - "Salvesta" - "Seadmes on vähe ruumi. Sõnumside kustutab ruumi vabastamiseks automaatselt vanemad sõnumid." - "Talletusruum hakkab täis saama" - "Sõnumside ei pruugi sõnumeid saata ega vastu võtta seni, kuni seadmes on rohkem vaba ruumi." - "SMS-ide jaoks on vähe salvestusruumi. Peate võib-olla mõned sõnumid kustutama." - "Kinnitage oma telefoninumber" - "See ühekordne toiming tagab, et rakendus Sõnumside edastab teie grupisõnumid õigesti." - "Telefoninumber" - "Kustuta kõik meediumifaile sisaldavad sõnumid" - "Kustuta sõnumid, mis on vanemad kui %s" - "Kustuta automaatselt sõnumid, mis on vanemad kui %s" - "Eira" - "Kas kustutada kõik meediaga sõnumid?" - "Kas kustutada sõnumid, mis on vanemad kui %s?" - "Kas kustutada automaatselt sõnumid, mis on vanemad kui %s, ja lülitada sisse automaatne kustutamine?" - "%s ütles:" - "Ütlesite:" - "Sõnum saatjalt %s" - "Saatsite sõnumi" - "Saatmine ..." - "Ei saadetud. Puudutage uuesti proovimiseks." - "Ei saadetud. Proovitakse uuesti ..." - "Saada uuesti või kustuta" - "Helistage hädaabiteenusele. Teie tekstsõnumit ei õnnestunud praegu edastada." - "Ebaõnnestus" - "Uus MMS-sõnum allalaadimiseks" - "Uus MMS-sõnum" - "Ei saanud alla laadida" - "Puudutage uuesti proovimiseks" - "Puudutage allalaadimiseks" - "Laadi alla või kustuta" - "Allalaadimine ..." - "Sõnum on aegunud või pole saadaval" - "suurus: %1$s, aegumine: %2$s" - "Ei õnnestu saata. Saaja ei ole õige." - "Teenus pole võrgus aktiveeritud" - "Võrguprobleemi tõttu ei õnnestunud saata" - "Sõnum on aegunud või pole saadaval" - "(Teema puudub.)" - "Tundmatu saatja" - "Kohale toimetatud" - "Ei saa laadida alla sõnumit %1$s, mille saatis %2$s." - "Vähese mälu tõttu ei õnnestunud andmebaasi toimingut lõpule viia" - "Sõnumit ei saadetud" - "Mõnda sõnumit ei saadetud rakenduses Sõnumside" - - %d sõnumit %d vestluses - %d sõnumit ühes vestluses - - "Sõnumit ei laaditud alla" - "Mõnda sõnumit ei laaditud alla rakenduses Sõnumside" - - %d sõnumit %d vestluses - %d sõnumit ühes vestluses - - "Sõnumit numbrile %1$s ei saadetud" - "Helistage hädaabiteenusele. Teie tekstsõnumit numbrile %1$s ei õnnestunud praegu edastada." - - %d uut sõnumit - Uus sõnum - - "Käivita" - "Kaamera pole saadaval" - "Kaamera pole saadaval" - "Video jäädvustamine pole saadaval" - "Meediat ei saa salvestada" - "Pilti ei saa jäädvustada" - "Tagasi" - "Arhiivitud" - "Kustutamine" - "Arhiivimine" - "Arhiivimise tühistamine" - "Märguannete väljalülitamine" - "Märguannete sisselülitamine" - "Kontakti lisamine" - "Laadi alla" - "Saada" - "Kustuta" - "Kas kustutada see sõnum?" - "Seda toimingut ei saa tagasi võtta." - "Kustuta" - - Kas kustutada need vestlused? - Kas kustutada see vestlus? - - "Kustuta" - "Tühista" - "Sihtkohta" - "Mitme kujutise valimine" - "Valiku kinnitamine" - "ja veel %d" - "Heli ei õnnestu salvestada. Proovige uuesti." - "Heli ei saa esitada. Proovige uuesti." - "Heli ei õnnestunud salvestada. Proovige uuesti." - "Puudutage pikalt" - ", " - " " - ": " - " " - "Pilt" - "Heliklipp" - "Video" - "Kontaktikaart" - "Allalaadimine" - "Vasta SMS-iga" - "Vastam. MMS-iga" - "Vasta" - - %d osalejat - %d osaleja - - "Mina" - "Kontakt on blokeeritud ja arhiivitud" - "Kontakt deblokeeriti ja eemaldati arhiivist" - "%d on arhiivitud" - "%d üksuse arhiivimine on tühistatud" - "Märguanded on välja lülitatud" - "Märguanded on sisse lülitatud" - "Kõik on valmis. Puudutage uuesti käsku Saada." - "Sõnumside määramine SMS-i vaikerakenduseks õnnestus." - - Manustest loobumine - Manusest loobumine - - "Helimanus" - "Helimanuste esitamine" - "Peata" - "Sõnum saatjalt %s: %s." - "Sõnumi saamine kontaktilt %s ebaõnnestus: %s. Aeg: %s." - "Sõnum kontaktilt %s: %s. Aeg: %s." - "Saatmata sõnum kontaktile %s: %s. Aeg: %s." - "Sõnumi saatmine kontaktile %s: %s. Aeg: %s." - "Sõnumi saatmine kontaktile %s ebaõnnestus: %s. Aeg: %s." - "Sõnum kontaktile %s: %s. Aeg: %s." - "Sõnumi saamine kontaktilt %s ebaõnnestus: %s. Aeg: %s. %s." - "Sõnum kontaktilt %s: %s. Aeg: %s. %s." - "Saatmata sõnum kontaktile %s: %s. Aeg: %s." - "Sõnumi saatmine kontaktile %s: %s. Aeg: %s." - "Sõnumi saatmine kontaktile %s ebaõnnestus: %s. Aeg: %s." - "Sõnum kontaktile %s: %s. Aeg: %s." - "Sõnumi saatmine ebaõnnestus. Puudutage uuesti proovimiseks." - "Vestlus osalejatega %s" - "Teema kustutamine" - "Video jäädvustamine" - "Foto jäädvustamine" - "Pildistamine" - "Video salvestamise alustamine" - "Lülitamine täisekraaniga kaamerale" - "Esi- ja tagakaamera vahetamine" - "Salvestamise peatamine ja video lisamine" - "Lõpeta video salvestamine" - "Sõnumside fotod" - - %d fotot salvestati albumisse „%s - %d foto salvestati albumisse „%s - - - %d videot salvestati albumisse „%s - %d video salvestati albumisse „%s - - - %d manust salvestati albumisse „%s - %d manus salvestati albumisse „%s - - - %d manust salvestati kausta „Allalaadimised” - %d manus salvestati kausta „Allalaadimised” - - - %d manust salvestati - %d manus salvestati - - - %d manust ei saanud salvestada - %d manust ei saanud salvestada - - "Salvestati MMS-manus" - "Seaded" - "Arhiivitud" - "Sule" - "MMS" - "Täpsem" - "Silumine" - "Märguanded" - "Heli" - "Hääletu" - "Vibratsioon" - "Blokeeritud" - "SMS-i edastamisaruanded" - "Edastamisaruande taotlus iga saadetud SMS-i kohta" - "Automaatne toomine" - "MMS-sõnumite automaatne vastuvõtmine" - "Automaatne vastuvõtmine rändlusel" - "MMS-ide automaatne toomine rändluse ajal" - "Rühma sõnumside" - "MMS-i kasutamine, kui mitmele kontaktile tuleb saata üks sõnum" - "SMS-i vaikerakendus" - "SMS-i vaikerakendus" - - - "Teie telefoninumber" - "Teadmata" - "Väljuva sõnumi helid" - "SMS-i tõmmistamine" - "Saadud SMS-ide lähteandmete tõmmistamine välisesse talletusfaili" - "MMS-i tõmmistamine" - "Saadud MMS-ide lähteandmete tõmmistamine välisesse talletusfaili" - "Raadiosidehoiatused" - "Sõnumi valikud" - "Kopeeri tekst" - "Kuva üksikasjad" - "Kustuta" - "Edastamine" - "Sõnumi üksikasjad" - "Tüüp: " - "Tekstisõnum" - "Multimeediumsõnum" - "Saatja: " - "Saaja: " - "Saadetud: " - "Vastu võetud: " - "Teema: " - "Suurus: " - "Prioriteet: " - "SIM: " - "Suur" - "Tavaline" - "Madal" - "SIM %s" - "Varjatud saatja aadress" - "Sõnumit ei saa manuste laadimise ajal saata." - "Manust ei õnnestu laadida. Proovige uuesti." - "Võrk pole valmis. Proovige uuesti." - "Kustuta tekst" - "Teksti ja numbrite vaheldumisi sisestamine" - "Lisa rohkem osalejaid" - "Osalejate kinnitamine" - "Uue vestluse alustamine" - "Selle üksuse valimine" - "Video esitamine" - "Inimesed ja valikud" - "Silu" - "Inimesed ja valikud" - "Üldine" - "Selles vestluses osalevad inimesed" - "Helista" - "Saatke sõnum" - "Sõnumi saatmine<br/><small>SIM-kaardilt %s</small>" - - Saada fotod - Saada foto - - - Saada helifailid - Saada helifail - - - Saada videod - Saada video - - - Saada kontaktikaardid - Saada kontaktikaart - - - Saada manused - Saada manus - - - %d manust on saatmiseks valmis - Üks manus on saatmiseks valmis - - "Tagasiside saatmine" - "Kuvamine Google Play poes" - "Versiooni teave" - "Versioon %1$s" - "Avatud lähtekoodi litsentsid" - "Märguanded" - "Manuselimiit on täis" - "Manuse laadimine ebaõnnestus." - "Kas lisada kontaktidesse?" - "Kontaktisiku lisamine" - "Teema" - "Teema: " - "%s%s" - "Kontakti kaardi laadimine" - "Kontaktkaarti ei õnnestunud laadida" - "Kontakti kaardi kuvamine" - - %d kontakti - %d kontakt - - "Kontaktkaardid" - "Sünnipäev" - "Märkmed" - "Sõnumi edastamine" - "Vastamine" - "+1" - "+%d" - "SMS on keelatud" - "Saatmiseks määrake rakendus Sõnumside SMS-i vaikerakenduseks" - "Määrake rakendus Sõnumside SMS-i vaikerakenduseks" - "Muuda" - "Sõnumite vastuvõtmiseks määrake rakendus Sõnumside SMS-i vaikerakenduseks" - "SMS-sõnumite saatmiseks pole eelistatud SIM-kaarti valitud" - "Seadme omanik pole seda rakendust lubanud." - "OK" - "Vestluses on liiga palju osalejaid" - - Sobimatud kontaktid - Sobimatu kontakt - - "Kaamera kujutist ei õnnestunud laadida" - "Teie: " - "%s: " - "Mustand" - "Kui alustate uut vestlust, loetletakse see siin" - "Arhiivitud vestlused kuvatakse siin" - "Vestluste laadimine …" - "Pilt" - "Heliklipp" - "Video" - "Kontaktikaart" - "MMS" - "Tagasivõtmine" - "Proovi uuesti" - "Uue sõnumi alustamiseks sisestage kontakti nimi või telefoninumber" - "Blokeerimine" - "Blokeeri %s" - "Deblokeeri %s" - "Kas blokeerida %s?" - "Saate sellelt numbrilt ikka sõnumeid, kuid te ei saa enam märguandeid. Vestlus arhiivitakse." - "Blokeeritud kontaktid" - "DEBLOKEERIMINE" - "Blokeeritud kontaktid" - "Dokumendikogust kujutise valimine" - "Sõnumi saatmine" - "Sõnum on saadetud" - "Mobiilne andmeside on välja lülitatud. Kontrollige seadeid." - "Lennukirežiimis ei saa sõnumeid saata" - "Sõnumit ei saanud saata" - "Sõnum on alla laaditud" - "Mobiilne andmeside on välja lülitatud. Kontrollige seadeid." - "Lennukireżiimis ei saa sõnumeid alla laadida" - "Sõnumit ei saanud alla laadida" - "Null" - "Üks" - "Kaks" - "Kolm" - "Neli" - "Viis" - "Kuus" - "Seitse" - "Kaheksa" - "Üheksa" - "Operaatori %1$s kaudu ei õnnestu sõnumit saata, viga %2$d" - "Tundmatu operaatori kaudu ei õnnestu sõnumit saata, viga %1$d" - "Edastamine: %s" - "Sõnumit ei saadetud: teenus pole võrgus aktiveeritud" - "Sõnumit ei saadetud: kehtetu sihtaadress" - "Sõnumit ei saadetud: vigane sõnum" - "Sõnumit ei saadetud: toetuseta sisu" - "Sõnumit ei saadetud: toetuseta sõnum" - "Sõnumit ei saadetud: liiga suur" - "Uus sõnum" - "Kuvamine" - "Kujutis" - "Sobivat rakendust ei õnnestunud leida" - "Saaja eemaldamine" - "Uus sõnum" - "Tühista" - "Pääsupunkti muutmine" - "Määramata" - "Nimi" - "APN" - "MMSC" - "MMS-i puhverserver" - "MMS-i port" - "MCC" - "MNC" - "APN-i tüüp" - "Kustuta APN" - "Uus APN" - "Salvesta" - "Tühista" - "Nimeväli ei saa olla tühi." - "APN ei saa olla tühi." - "MCC-väli peab olema kolme numbriga." - "MNC-väli peab olema kahe või kolme numbriga." - "APN-i vaikeseadete taastamine." - "Lähtesta vaikeseadetele" - "APN-i vaikeseadete lähtestamine on lõpule viidud." - "Pealkirjata" - "Pääsupunktide nimed" - "APN-id" - "Uus APN" - "Pääsupunkti nime seaded pole selle kasutaja jaoks saadaval" - "Kas kopeerida lõikelauale?" - "Kopeeri" - "SIM-kaardile %s" - "Üldised" - "Täpsemad" - "Üldseaded" - "Täpsemad seaded" - "SIM-kaart „%s”" - "Individuaalsed SMS-id saadetakse kõikidele adressaatidele. Ainult teie saate vastuseid" - "Üks MMS saadetakse kõikidele adressaatidele" - "Tundmatu number" - "Uus sõnum" - "Uus sõnum." - "SIM-kaardi valija" - "%1$s on valitud, SIM-kaardi valija" - "Teema muutmine" - "SIM-kaardi valimine või objekti valimine" - "Puudutage pikalt heli salvestamiseks" - "Uue vestluse alustamine" - "Sõnumside" - "Sõnumside loend" - "Sõnumside" - "Uus sõnum" - "Vestlusloend" - "Vestluste laadimine …" - "Sõnumite laadimine ..." - "Kuva rohkem vestlusi" - "Kuva rohkem sõnumeid" - "Vestlus on kustutatud" - "Vestlus kustutati. Rakenduse Sõnumside eri vestluste kuvamiseks puudutage" - "Blokeeritud" - "Deblokeeritud" - "Talletusruumi on vähe. Osa andmetest võib kaduma minna." - "Manuste valimine" - "Valiku kinnitamine" - "%d on valitud" - "Eemaldage üks või mitu manust ja proovige uuesti." - "Võite proovida sõnumit saata, aga selle edastamine võib ebaõnnestuda, kui te ei eemalda üht või mitut manust." - "Saate sõnumis saata vaid ühe video. Eemaldage täiendavad videod ja proovige uuesti." - "Rakendusel Sõnumside ei õnnestunud manust laadida." - "Saada ikkagi" - "Vestlust ei õnnestunud alustada" - "%1$s (%2$s)" - "%s on valitud" - diff --git a/res/values-eu-rES/arrays.xml b/res/values-eu-rES/arrays.xml deleted file mode 100644 index 5ea97f0..0000000 --- a/res/values-eu-rES/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "gairik ez" - "gairik ez" - - - "Bai" - "Ez" - "Ados" - "Kar-kar" - "Eskerrik asko" - "Ados" - "Ederki" - "Banoa" - "Ados, geroago erantzungo dizut" - ":)" - ":(" - - diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml deleted file mode 100644 index 24813fe..0000000 --- a/res/values-eu-rES/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mezuak" - "Mezuak" - "Hautatu elkarrizketa" - "Ezarpenak" - "Bidali mezua" - "Gehitu eranskina" - "Laguntza" - "Ongi etorri" - "Saltatu" - "Hurrengoa >" - "Hurrengoa" - "Irten" - "Ezarpenak >" - "Ezarpenak" - "Mezuak aplikazioak baimena behar du SMS mezuak bidaltzeko eta jasotzeko, deiak egiteko eta kontaktuak atzitzeko." - "Baimenak aldatzeko, zoaz Ezarpenak > Aplikazioak > Mezuak > Baimenak atalera." - "Baimenak aldatzeko, zoaz Ezarpenak, Aplikazioak, Mezuak, Baimenak atalera." - "Maiz erabilitakoak" - "Kontaktu guztiak" - "Bidali hona: %s" - "Atera irudiak edo grabatu bideoa" - "Aukeratu gailu honetako irudiak" - "Grabatu audioa" - "Aukeratu argazkia" - "Euskarria hautatu da." - "Euskarria desautatu da." - "%d hautatuta" - "Irudiaren data: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "irudia" - "Grabatu audioa" - "Partekatu" - "Oraintxe" - "Orain" - - %d min - %d min - - - %d ordu - %d ordu - - - %d egun - %d egun - - - %d aste - Astebete - - - %d hilabete - Hilabete - - - %d urte - Urtebete - - "0 klaseko mezua" - "Gorde" - "Toki gutxi dago gailuan. Tokia egiteko, Mezuak aplikazioak automatikoki ezabatuko ditu mezu zaharragoak." - "Memoria betetzen ari da" - "Gailuan toki gehiago izan arte, baliteke Mezuak aplikazioak mezurik ez bidaltzea edo jasotzea." - "SMS mezuetarako memoria gutxi dago. Baliteke mezuak ezabatu behar izatea." - "Berretsi telefono-zenbakia" - "Behin bakarrik egin beharreko urrats honi esker, Mezuak aplikazioak taldeko mezuak behar bezala bidaliko dituela ziurtatuko duzu." - "Telefono-zenbakia" - "Ezabatu multimedia-elementuak dituzten mezu guztiak" - "Ezabatu %s baino zaharragoak diren mezuak" - "Ezabatu automatikoki %s baino zaharragoak diren mezuak" - "Egin ez ikusi" - "Multimedia-elementuak dauzkaten mezu guztiak ezabatu nahi dituzu?" - "%s baino zaharragoak diren mezuak ezabatu nahi dituzu?" - "%s baino zaharragoak diren mezuak ezabatu eta ezabaketa automatikoa aktibatu nahi duzu?" - "%s erabiltzaileak esan du" - "Zuk esan duzu" - "%s erabiltzailearen mezua:" - "Mezu bat bidali duzu" - "Bidaltzen…" - "Ez da bidali. Berriro saiatzeko, ukitu hau." - "Ez da bidali. Berriro saiatzen…" - "Birbidali edo ezabatu" - "Deitu larrialdi-zerbitzuetara. Ezin izan dugu entregatu testu-mezua." - "Huts egin du" - "MMS mezu berria deskargatzeko prest" - "MMS mezu berria" - "Ezin izan da deskargatu" - "Berriro saiatzeko, ukitu hau" - "Deskargatzeko, ukitu hau" - "Deskargatu edo ezabatu" - "Deskargatzen…" - "Mezua iraungi da edo ez dago erabilgarri" - "Tamaina: %1$s. Iraungitze-data: %2$s." - "Ezin da bidali. Hartzaileak ez du balio." - "Zerbitzua ez dago sare honetan aktibatuta" - "Ezin izan da bidali sareko arazo baten ondorioz" - "Mezua iraungi da edo ez dago erabilgarri" - "(Gairik ez)" - "Igorle ezezaguna" - "Bidalita" - "Ezin izan da %2$s erabiltzailearen %1$s mezua deskargatu." - "Ezin izan da datu-basearen eragiketa osatu memoria gutxi dagoelako" - "Ez da bidali mezua" - "Ez dira bidali Mezuak aplikazioko mezu batzuk" - - %d mezu daude %d elkarrizketatan - %d mezu daude elkarrizketa batean - - "Ez da deskargatu mezua" - "Ez dira deskargatu Mezuak aplikazioko mezu batzuk" - - %d mezu daude %d elkarrizketatan - %d mezu daude elkarrizketa batean - - "Mezua ez da bidali %1$s zenbakira" - "Deitu larrialdi-zerbitzuetara. Ezin izan dugu entregatu %1$s zenbakira bidalitako testu-mezua." - - %d mezu berri - Mezu berria - - "Hasi" - "Kamera ez dago erabilgarri" - "Kamera ez dago erabilgarri" - "Bideo-grabaketa ez dago erabilgarri" - "Ezin da gorde multimedia-elementua" - "Ezin da atera argazkia" - "Atzera" - "Artxibatutakoak" - "Ezabatu" - "Artxibatu" - "Kendu artxibotik" - "Desaktibatu jakinarazpenak" - "Aktibatu jakinarazpenak" - "Gehitu kontaktua" - "Deskargatu" - "Bidali" - "Ezabatu" - "Mezua ezabatu nahi duzu?" - "Ezin da ekintza hori desegin." - "Ezabatu" - - Elkarrizketa hauek ezabatu nahi dituzu? - Elkarrizketa ezabatu nahi duzu? - - "Ezabatu" - "Utzi" - "Hartzailea" - "Hautatu hainbat irudi" - "Berretsi hautapena" - "+%d" - "Ezin da grabatu audioa. Saiatu berriro." - "Ezin da erreproduzitu audioa. Saiatu berriro." - "Ezin izan da gorde audioa. Saiatu berriro." - "Eduki ukituta" - ", " - " " - ": " - " " - "Argazkia" - "Audio-klipa" - "Bideoa" - "Kontaktu-txartela" - "Deskargatu" - "Erantzun SMS bidez" - "Erantzun MMS bidez" - "Erantzun" - - %d parte-hartzaile - %d parte-hartzaile - - "Ni" - "Kontaktua blokeatu eta artxibatu egin da" - "Kontaktua desblokeatu da eta artxibotik kendu da" - "%d artxibatuta" - "%d artxibotik kenduta" - "Jakinarazpenak desaktibatuta daude" - "Jakinarazpenak aktibatuta daude" - "Dena ezarri da. Ukitu berriro Bidali." - "Mezuak aplikazioa behar bezala ezarri da SMS mezuetarako aplikazio lehenetsi gisa." - - Baztertu eranskinak - Baztertu eranskina - - "Audio-eranskina" - "Erreproduzitu audio-eranskina" - "Pausatu" - "%s erabiltzailearen mezua: %s." - "%s igorlearen mezuak huts egin du: %s. Ordua: %s" - "%s igorlearen mezua: %s. Ordua: %s" - "Ez zaio bidali mezua %s erabiltzaileari: %s. Ordua: %s" - "%s erabiltzaileari mezua bidaltzen: %s. Ordua: %s" - "Ezin izan zaio bidali mezua %s erabiltzaileari: %s. Ordua: %s" - "%s erabiltzailearentzako mezua: %s. Ordua: %s" - "%s igorlearen mezuak huts egin du: %s. Ordua: %s. %s" - "%s igorlearen mezua: %s. Ordua: %s. %s" - "Ez da bidali mezua %s taldera: %s. Ordua: %s" - "%s taldera mezua bidaltzen: %s. Ordua: %s" - "Ezin izan da bidali mezua %s taldera: %s. Ordua: %s" - "%s taldearentzako mezua: %s. Ordua: %s" - "Mezuak huts egin du. Ukitu berriro saiatzeko." - "Elkarrizketa erabiltzaile hauekin (%s)" - "Ezabatu gaia" - "Grabatu bideoa" - "Atera irudi finkoa" - "Atera argazkia" - "Hasi bideoa grabatzen" - "Aldatu pantaila osoko kamerara" - "Aldatu aurreko eta atzeko kameren artean" - "Utzi grabatzeari eta erantsi bideoa" - "Utzi bideoa grabatzeari" - "Mezuak aplikazioko argazkiak" - - %d argazki gorde dira \"%s\" albumean - %d argazki gorde da \"%s\" albumean - - - %d bideo gorde dira \"%s\" albumean - %d bideo gorde da \"%s\" albumean - - - %d eranskin gorde dira \"%s\" albumean - %d eranskin gorde da \"%s\" albumean - - - %d eranskin gorde dira \"Deskargak\" atalean - %d eranskin gorde da \"Deskargak\" atalean - - - Gorde dira %d eranskin - Gorde da %d eranskin - - - Ezin izan dira gorde %d eranskin - Ezin izan da gorde %d eranskin - - "MMSko eranskina gordeta dago" - "Ezarpenak" - "Artxibatuta" - "Itxi" - "MMS" - "Ezarpen aurreratuak" - "Araztu" - "Jakinarazpenak" - "Soinua" - "Isilik" - "Egin dar-dar" - "Blokeatuta" - "SMSak entregatzearen txostenak" - "Eskatu bidaltzen duzun SMS bakoitzaren entrega-txostena" - "Eskuratu automatikoki" - "Eskuratu MMS mezuak automatikoki" - "Eskuratu automatikoki ibiltaritzan" - "Eskuratu MMS mezuak automatikoki ibiltaritzan" - "Talde-mezularitza" - "Erabili MMSak mezu bakarra bidaltzeko hartzaile ugari daudenean" - "SMS mezuetarako aplikazio lehenetsia" - "SMS mezuetarako aplikazio lehenetsia" - - - "Zure telefono-zenbakia" - "Ezezaguna" - "Irteerako mezuen soinuak" - "Irauli SMSak" - "Irauli jasotako SMS mezuen prozesatu gabeko datuak kanpoko memoriako fitxategira" - "Irauli MMSak" - "Irauli jasotako MMS mezuen prozesatu gabeko datuak kanpoko memoriako fitxategira" - "Hari gabeko alertak" - "Mezuaren aukerak" - "Kopiatu testua" - "Ikusi xehetasunak" - "Ezabatu" - "Birbidali" - "Mezuaren xehetasunak" - "Mota: " - "Testu-mezua" - "Multimedia-mezua" - "Igorlea: " - "Hartzailea: " - "Bidaltze-data: " - "Jasotze-data: " - "Gaia: " - "Tamaina: " - "Lehentasuna: " - "SIM txartela: " - "Handia" - "Normala" - "Txikia" - "%s. SIM txartela" - "Igorlearen helbidea ezkutatuta badago" - "Ezin da mezua bidali eranskinak kargatzen ari diren bitartean." - "Ezin da kargatu eranskina. Saiatu berriro." - "Sarea ez dago prest. Saiatu berriro." - "Ezabatu testua" - "Aldatu testu eta zenbaki moduen artean" - "Gehitu parte-hartzaile gehiago" - "Berretsi parte-hartzaileak" - "Hasi elkarrizketa bat" - "Hautatu elementu hau" - "Erreproduzitu bideoa" - "Jendea eta aukerak" - "Araztu" - "Jendea eta aukerak" - "Orokorra" - "Elkarrizketa honetako jendea" - "Deitu" - "Bidali mezua" - "Bidali mezua <br/><small>%s txarteletik</small>" - - Bidali argazkiak - Bidali argazkia - - - Bidali audioak - Bidali audioa - - - Bidali bideoak - Bidali bideoa - - - Bidali kontaktu-txartelak - Bidali kontaktu-txartela - - - Bidali eranskinak - Bidali eranskina - - - %d eranskin bidaltzeko prest daude - Eranskin bat bidaltzeko prest dago - - "Bidali iritzia" - "Ikusi Google Play Store dendan" - "Bertsioari buruzko informazioa" - "%1$s bertsioa" - "Kode irekiko lizentziak" - "Jakinarazpenak" - "Eranskinen mugara iritsi zara" - "Ezin izan da kargatu eranskina." - "Kontaktuetan gehitu nahi duzu?" - "Gehitu kontaktua" - "Gaia" - "Gaia: " - "%s%s" - "Kontaktu-txartela kargatzen" - "Ezin izan da kargatu kontaktu-txartela" - "Ikusi kontaktu-txartela" - - %d kontaktu - %d kontaktu - - "Kontaktu-txartelak" - "Urtebetetzea" - "Oharrak" - "Birbidali mezua" - "Erantzun" - "+1" - "+%d" - "SMSak desgaituta" - "Mezuak bidaltzeko, ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa" - "Ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa" - "Aldatu" - "Mezuak jasotzeko, ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa" - "Ez da hautatu SIM txartel hobetsirik SMS mezuak bidaltzeko" - "Gailuaren jabeak ez du aplikazio hau erabiltzea baimendu." - "Ados" - "Parte-hartzaile gehiegi daude elkarrizketan" - - Kontaktuek ez dute balio - Kontaktuak ez du balio - - "Ezin izan da kargatu kamerako irudia" - "Zu: " - "%s: " - "Zirriborroa" - "Elkarrizketak hasten dituzunean, hemen agertuko dira" - "Artxibatutako elkarrizketak hemen ageri dira" - "Elkarrizketak kargatzen…" - "Argazkia" - "Audio-klipa" - "Bideoa" - "Kontaktu-txartela" - "MMS" - "Desegin" - "Saiatu berriro" - "Mezu berria hasteko, idatzi kontaktu baten izena edo telefono-zenbakia." - "Blokeatu" - "Blokeatu %s" - "Desblokeatu %s" - "%s blokeatu nahi duzu?" - "Zenbaki horren mezuak jasotzen jarraituko duzu, baina ez duzu horren jakinarazpenik jasoko aurrerantzean. Elkarrizketa artxibatu egingo da." - "Blokeatutako kontaktuak" - "DESBLOKEATU" - "Blokeatutako kontaktuak" - "Aukeratu irudia dokumentuen liburutegitik" - "Mezua bidaltzen" - "Mezua bidali da" - "Mugikorreko datuak desaktibatuta daude. Egiaztatu ezarpenak." - "Ezin dira bidali mezuak hegaldi moduan" - "Ezin izan da bidali mezua" - "Mezua deskargatu da" - "Mugikorreko datuak desaktibatuta daude. Egiaztatu ezarpenak." - "Ezin da deskargatu mezua hegaldi moduan" - "Ezin izan da deskargatu mezua" - "Zero" - "Bat" - "Bi" - "Hiru" - "Lau" - "Bost" - "Sei" - "Zazpi" - "Zortzi" - "Bederatzi" - "Ezin da bidali mezua %1$s operadorearekin. Errorea: %2$d" - "Ezin da bidali mezua operadore ezezagunarekin. Errorea: %1$d" - "Fwd: %s" - "Ez da bidali mezua: zerbitzua ez dago aktibatuta sarean" - "Mezua ez da bidali. Helmugaren helbideak ez du balio." - "Mezua ez da bidali. Ez du balio." - "Mezua ez da bidali. Edukia ez da onartzen." - "Mezua ez da bidali. Ez da onartzen." - "Ez da bidali mezua: handiegia da" - "Mezu berria" - "Ikusi" - "Irudia" - "Ezin izan da aurkitu aplikazio egokia" - "Kendu hartzailea" - "Mezu berria" - "Utzi" - "Editatu sarbide-puntua" - "Ezarri gabe" - "Izena" - "APN" - "MMSC" - "MMS proxya" - "MMS ataka" - "MCC" - "MNC" - "APN mota" - "Ezabatu APNa" - "APN berria" - "Gorde" - "Baztertu" - "\"Izena\" eremua ezin da hutsik utzi." - "\"APN\" eremua ezin da hutsik utzi." - "\"MCC\" eremuak 3 digitu izan behar ditu." - "\"MNC\" eremuak 2 edo 3 digitu izan behar ditu." - "APN ezarpen lehenetsiak leheneratzen." - "Berrezarri ezarpen lehenetsiak" - "APN ezarpen lehenetsiak berrezarri dira." - "Izengabea" - "Sarbide-puntuen izenak" - "APNak" - "APN berria" - "Erabiltzaile honek ezin ditu APN ezarpenak atzitu" - "Arbelean kopiatu nahi duzu?" - "Kopiatu" - "%s txartelean" - "Orokorra" - "Ezarpen aurreratuak" - "Ezarpen orokorrak" - "Ezarpen aurreratuak" - "\"%s\" SIM txartela" - "Bidali SMS mezu bana hartzaile guztiei. Zuk bakarrik jasoko dituzu erantzunak" - "Bidali MMS mezu bakarra hartzaile guztiei" - "Zenbaki ezezaguna" - "Mezu berria" - "Mezu berria." - "SIM hautatzailea" - "%1$s hautatuta, SIM hautatzailea" - "Editatu gaia" - "Hautatu SIM txartela edo editatu gaia" - "Eduki sakatuta audioa grabatzeko" - "Hasi elkarrizketa bat" - "Mezuak" - "Mezuak aplikazioko zerrenda" - "Mezuak" - "Mezu berria" - "Elkarrizketa-zerrenda" - "Elkarrizketak kargatzen" - "Mezuak kargatzen" - "Ikusi elkarrizketa gehiago" - "Ikusi mezu gehiago" - "Elkarrizketa ezabatu egin da" - "Ezabatu egin da elkarrizketa. Ukitu hau Mezuak aplikazioko beste elkarrizketa bat erakusteko." - "Blokeatu da" - "Desblokeatu da" - "Toki gutxi geratzen da. Agian ez dira gordeko datu batzuk." - "Hautatu eranskinak" - "Berretsi hautapena" - "%d hautatuta" - "Kendu eranskin bat edo gehiago, eta saiatu berriro." - "Hala eta guztiz ere, mezua bidaltzen saia zaitezke, baina agian ez da entregatuko, eranskinetako batzuk kendu ezean." - "Bideo bakarra bidal dezakezu mezu bakoitzeko. Kendu gainerako bideoak eta saiatu berriro." - "Mezuak aplikazioak ezin izan du eranskina kargatu." - "Bidali halere" - "Ezin izan da hasi elkarrizketa" - "%1$s (%2$s)" - "%s hautatu da" - diff --git a/res/values-fa/arrays.xml b/res/values-fa/arrays.xml deleted file mode 100644 index 01b6f3e..0000000 --- a/res/values-fa/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "بدون موضوع" - "بدون موضوع" - - - "بله" - "خیر" - "تأیید" - "هه‌هه" - "سپاسگزارم" - "موافقم" - "خوب" - "در راهم" - "بسیار خوب، بگذارید بعداً با شما تماس بگیرم" - ":)" - ":(" - - diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml deleted file mode 100644 index 6a78532..0000000 --- a/res/values-fa/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "پیام‌رسانی" - "پیام‌رسانی" - "انتخاب مکالمه" - "تنظیمات" - "ارسال پیام" - "افزودن پیوست" - "راهنما" - "خوش آمدید" - "رد شدن" - "‏بعدی >" - "بعدی" - "خروج" - "‏تنظیمات >" - "تنظیمات" - "«پیام‌رسانی» به مجوز دسترسی به پیامک، تلفن و مخاطبین نیاز دارد." - "‏می‌توانید مجوزها را در تنظیمات > برنامه‌ها > پیام‌رسانی > مجوزها تغییر دهید." - "می‌توانید مجوزها را در تنظیمات، برنامه‌ها، پیام‌رسانی، مجوزها تغییر دهید." - "مکرر" - "همه مخاطبین" - "ارسال به %s" - "گرفتن عکس یا ویدیو" - "انتخاب تصاویر از این دستگاه" - "ضبط صدا" - "انتخاب عکس" - "رسانه انتخاب شده است." - "رسانه انتخاب نشده است." - "%d مورد انتخاب شد" - "تاریخ تصویر %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "تصویر" - "ضبط صدا" - "اشتراک‌گذاری" - "هم‌اکنون" - "اکنون" - - %d دقیقه - %d دقیقه - - - %d ساعت - %d ساعت - - - %d روز - %d روز - - - %d هفته - %d هفته - - - %d ماه - %d ماه - - - %d سال - %d سال - - "پیام رده ۰" - "ذخیره‌" - "فضای ذخیره‌سازی دستگاه کم است. «پیام‌رسانی» برای آزاد کردن فضا، پیام‌های قدیمی‌تر را به‌طور خودکار پاک می‌کند." - "فضای ذخیره‌سازی رو به پایان است" - "شاید تا وقتی فضای بیشتری در دستگاهتان در دسترس نباشد، «پیام‌رسانی» پیام ارسال یا دریافت نکند." - "فضای ذخیره‌سازی پیامک کم است. ممکن است لازم باشد پیام‌ها را حذف کنید." - "شماره تلفنتان را تأیید کنید" - "با انجام این مرحله یک‌باره مطمئن می‌شوید «پیام‌رسانی» پیام‌های گروهی شما را به‌درستی تحویل می‌دهد." - "شماره تلفن" - "حذف همه پیام‌های دارای رسانه" - "حذف پیام‌های قدیمی‌تر از %s" - "حذف خودکار پیام‌های قدیمی‌تر از %s" - "نادیده گرفتن" - "همه پیام‌های دارای رسانه حذف شوند؟" - "پیام‌های قدیمی‌تر از %s حذف شوند؟" - "پیام‌های قدیمی‌تر از %s حذف شود و حذف خودکار روشن شود؟" - "%s گفت" - "گفتید" - "پیام از %s:" - "پیامی ارسال کردید" - "در حال ارسال…" - "ارسال نشد. برای امتحان مجدد لمس کنید." - "ارسال نشد. در حال امتحان مجدد…" - "ارسال مجدد یا حذف" - "لطفاً یک تماس صوتی با خدمات اضطراری برقرار کنید. در حال حاضر تحویل پیامک شما ممکن نیست." - "انجام نشد" - "‏پیام MMS جدید برای دانلود" - "‏پیام MMS جدید" - "دانلود نشد" - "برای امتحان مجدد لمس کنید" - "برای دانلود لمس کنید" - "دانلود یا حذف" - "در حال دانلود…" - "پیام منقضی شده است یا در دسترس نیست" - "اندازه: %1$s، تاریخ انقضا: %2$s" - "ارسال نمی‌شود. گیرنده معتبر نیست." - "سرویس در شبکه فعال نشده است" - "به علت مشکل شبکه ارسال نشد" - "پیام منقضی شده است یا در دسترس نیست" - "‫(بدون موضوع)‏" - "فرستنده ناشناس" - "تحویل داده شد" - "‫پیام %1$s از %2$s دانلود نشد.‏" - "به دلیل حافظه کم، عملیات پایگاه داده انجام نشد" - "پیام ارسال نشد" - "بعضی از پیام‌ها در «پیام‌رسانی» ارسال نشدند" - - %d پیام در %d مکالمه - %d پیام در %d مکالمه - - "پیام دانلود نشد" - "بعضی از پیام‌ها در «پیام‌رسانی» دانلود نشدند" - - %d پیام در %d مکالمه - %d پیام در %d مکالمه - - "پیام به %1$s ارسال نشد" - "لطفاً یک تماس صوتی با خدمات اضطراری برقرار کنید. در حال حاضر تحویل پیامک شما به %1$s ممکن نیست." - - %d پیام جدید - %d پیام جدید - - "شروع" - "دوربین در دسترس نیست" - "دوربین در دسترس نیست" - "ضبط ویدیو در دسترس نیست" - "رسانه ذخیره نمی‌شود" - "عکس نمی‌گیرد" - "برگشت" - "بایگانی شده" - "حذف" - "بایگانی" - "لغو بایگانی" - "خاموش کردن اعلان‌ها" - "روشن کردن اعلان‌ها" - "افزودن مخاطب" - "دانلود" - "ارسال" - "حذف" - "این پیام حذف شود؟" - "این عملکرد قابل واگرد نیست." - "حذف" - - این مکالمه حذف شود؟ - این مکالمه‌ها حذف شود؟ - - "حذف" - "لغو" - "به" - "انتخاب چند تصویر" - "تأیید انتخاب" - "+%d" - "صدا ضبط نمی‌شود. دوباره امتحان کنید." - "صدا پخش نمی‌شود. دوباره امتحان کنید." - "صدا ذخیره نشد. دوباره امتحان کنید." - "لمس کنید و نگه‌دارید" - "، " - " " - ": " - " " - "تصویر" - "کلیپ صوتی" - "ویدیو" - "کارت ویزیت" - "دانلود" - "پاسخ از طریق پیامک" - "‏پاسخ از طریق MMS" - "پاسخ" - - %d شرکت‌کننده - %d شرکت‌کننده - - "من" - "مخاطب مسدود و بایگانی شد" - "انسداد مخاطب برطرف شد و از بایگانی خارج شد" - "%d بایگانی شد" - "%d از بایگانی خارج شد" - "اعلان‌ها خاموش شد" - "اعلان‌ها روشن شد" - "همه چیز تنظیم شد. ارسال را دوباره لمس کنید." - "«پیام‌رسانی» با موفقیت به‌عنوان برنامه پیش‌فرض پیامک تنظیم شد." - - صرف‌‌نظر از پیوست - صرف‌‌نظر از پیوست‌ها - - "پیوست صوتی" - "پخش پیوست صوتی" - "توقف موقت" - "پیام از %s: ‏%s." - "پیام ناموفق از %s: ‏%s. زمان: %s." - "پیام از %s: ‏%s. زمان: %s." - "پیام ارسال نشده به %s: ‏%s. زمان: %s." - "ارسال پیام به %s: ‏%s. زمان: %s." - "پیام ناموفق به %s: %s. زمان: %s." - "پیام به %s: ‏%s. زمان: %s." - "پیام ناموفق از %s: ‏%s. زمان: %s. ‏%s." - "پیام از %s: ‏%s. زمان: %s. ‏%s." - "پیام ارسال نشده به %s: ‏%s. زمان: %s." - "ارسال پیام به %s: ‏%s. زمان: %s." - "پیام ناموفق به %s: %s. زمان: %s." - "پیام به %s: ‏%s. زمان: %s." - "پیام ناموفق. برای امتحان مجدد، لمس کنید." - "مکالمه با %s" - "حذف موضوع" - "فیلم‌برداری" - "ضبط تصویر ثابت" - "عکس گرفتن" - "شروع فیلم‌برداری" - "تغییر به دوربین تمام صفحه" - "جابه‌جایی بین دوربین جلو و عقب" - "توقف ضبط و پیوست ویدیو" - "توقف ضبط ویدیو" - "عکس‌های «پیام‌رسانی»" - - %d عکس در آلبوم «%s» ذخیره شد - %d عکس در آلبوم «%s» ذخیره شد - - - %d ویدیو در آلبوم «%s» ذخیره شد - %d ویدیو در آلبوم «%s» ذخیره شد - - - %d پیوست در آلبوم «%s» ذخیره شد - %d پیوست در آلبوم «%s» ذخیره شد - - - %d پیوست در «دانلودها» ذخیره شد - %d پیوست در «دانلودها» ذخیره شد - - - %d پیوست ذخیره شد - %d پیوست ذخیره شد - - - %d پیوست ذخیره نشد - %d پیوست ذخیره نشد - - "‏پیوست MMS ذخیره شد" - "تنظیمات" - "بایگانی شد" - "بستن" - "MMS" - "پیشرفته" - "اشکال‌زدایی" - "اعلان‌ها" - "صدا" - "بی‌صدا" - "لرزش" - "مسدود شده" - "گزارش‌های تحویل پیامک" - "درخواست گزارش تحویل برای هر پیامکی که ارسال می‌کنید" - "بازیابی خودکار" - "‏بازیابی خودکار پیام‌های MMS" - "بازیابی خودکار در طول رومینگ" - "‏بازیابی خودکار MMS هنگام رومینگ" - "پیام‌رسانی گروهی" - "‏در صورت وجود چند گیرنده، برای ارسال یک پیام از MMS استفاده شود" - "برنامه پیامک پیش‌فرض" - "برنامه پیامک پیش‌فرض" - - - "شماره تلفن شما" - "نامشخص" - "صداهای پیام خروجی" - "نسخه‌برداری پیامک" - "نسخه‌برداری اطلاعات خام پیامک دریافتی در فایل فضای ذخیره‌سازی خارجی" - "‏نسخه‌برداری MMS" - "‏نسخه‌برداری از اطلاعات خام MMS دریافتی در فایل فضای ذخیره‌سازی خارجی" - "هشدارهای بی‌سیم" - "گزینه‌های پیام" - "کپی کردن نوشتار" - "مشاهده جزئیات" - "حذف" - "بازارسال" - "جزئیات پیام" - "نوع: " - "پیامک" - "پیام چندرسانه‌ای" - "از: " - "به: " - "ارسال شده: " - "دریافت شده: " - "موضوع: " - "اندازه: " - "اولویت: " - "سیم‌کارت: " - "زیاد" - "عادی" - "کم" - "سیم‌کارت %s" - "مخفی کردن آدرس فرستنده" - "نمی‌توانید در حال بارگیری پیوست‌ها، پیام ارسال کنید." - "پیوست بارگیری نمی‌شود. دوباره امتحان کنید." - "شبکه آماده نیست. دوباره امتحان کنید." - "حذف نوشتار" - "جابه‌جایی بین وارد کردن نوشتار و عدد" - "افزودن شرکت‌کننده‌های بیشتر" - "تأیید شرکت‌کنندگان" - "شروع مکالمه جدید" - "انتخاب این مورد" - "پخش ویدیو" - "افراد و گزینه‌ها" - "اشکال‌زدایی" - "افراد و گزینه‌ها" - "کلی" - "افراد در این مکالمه" - "برقراری تماس تلفنی" - "ارسال پیام" - "‏ارسال پیام<br/><small>از %s</small>" - - ارسال عکس - ارسال عکس‌ها - - - ارسال فایل صوتی - ارسال فایل‌های صوتی - - - ارسال ویدیو - ارسال ویدیوها - - - ارسال کارت تماس - ارسال کارت‌های تماس - - - ارسال پیوست - ارسال پیوست‌ها - - - %d پیوست آماده ارسال است - %d پیوست آماده ارسال است - - "ارسال بازخورد" - "‏نمایش در فروشگاه Google Play" - "اطلاعات نسخه" - "‏نسخه %1$s" - "مجوزهای منبع باز" - "اعلان‌ها" - "به حد مجاز پیوست رسیدید" - "پیوست بارگیری نشد." - "به مخاطبین افزوده شود؟" - "افزودن مخاطب" - "موضوع" - "موضوع: " - "%s%s" - "در حال بارگیری کارت مخاطب" - "کارت مخاطب بارگیری نشد" - "مشاهده کارت مخاطب" - - %d مخاطب - %d مخاطب - - "کارت‌های مخاطب" - "تاریخ تولد" - "یادداشت‌ها" - "بازارسال پیام" - "پاسخ" - "1+" - "+%d" - "پیامک غیرفعال شد" - "برای ارسال، «پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید" - "«پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید" - "تغییر" - "برای دریافت پیام، «پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید" - "هیچ سیم‌کارت برگزیده‌ای برای ارسال پیامک‌ها انتخاب نشده است" - "مالک دستگاه این برنامه را مجاز نکرده است." - "تأیید" - "شرکت‌کننده‌های خیلی زیادی در مکالمه وجود دارد" - - مخاطب نامعتبر - مخاطبین نامعتبر - - "تصویر دوربین بارگیری نشد" - "شما: " - "%s: " - "پيش‌نويس" - "وقتی مکالمه جدیدی را شروع کنید، آن را در این لیست مشاهده می‌کنید" - "مکالمات بایگانی‌شده در اینجا نشان داده می‌شود" - "در حال بارگیری مکالمات…" - "تصویر" - "کلیپ صوتی" - "ویدیو" - "کارت ویزیت" - "MMS" - "واگرد" - "امتحان مجدد" - "نام یا شماره تلفن مخاطب را برای شروع یک پیام جدید وارد کنید" - "مسدود کردن" - "مسدود کردن %s" - "رفع انسداد %s" - "%s مسدود شود؟" - "همچنان از این شماره پیام‌ها را دریافت می‌کنید، اما دیگر به شما اطلاع داده نمی‌شود. این مکالمه بایگانی می‌شود." - "مخاطبین مسدود شده" - "رفع انسداد" - "مخاطبین مسدود شده" - "انتخاب تصویر از کتابخانه سند" - "در حال ارسال پیام" - "پیام ارسال شد" - "داده شبکه تلفن همراه خاموش است. تنظیماتتان را بررسی کنید." - "در حالت هواپیما پیام‌ها ارسال نمی‌شوند" - "پیام ارسال نشد" - "پیام دانلود شد" - "داده سلولی خاموش است. تنظیماتتان را بررسی کنید." - "پیام‌ در حالت هواپیما دانلود نمی‌شود" - "پیام دانلود نشد" - "صفر" - "یک" - "دو" - "سه" - "چهار" - "پنج" - "شش" - "هفت" - "هشت" - "نه" - "ارسال پیام با %1$s امکان‌پذیر نیست، خطای %2$d" - "ارسال پیام با شرکت مخابراتی ناشناس امکان‌پذیر نیست، خطای %1$d" - "بازارسال: %s" - "پیام ارسال نشد: سرویس در شبکه فعال نیست" - "پیام ارسال نشد: آدرس مقصد نامعتبر است" - "پیام ارسال نشد: پیام نامعتبر است" - "پیام ارسال نشد: محتوا پشتیبانی نمی‌شود" - "پیام ارسال نشد: پیام پشتیبانی نمی‌شود" - "پیام ارسال نشد: بیش از حد بزرگ است" - "پیام جدید" - "مشاهده" - "تصویر" - "برنامه مناسبی یافت نشد" - "حذف گیرنده" - "پیام جدید" - "لغو" - "ویرایش نقطه دستیابی" - "تنظیم نشده" - "نام" - "نام نقطه دستیابی" - "MMSC" - "پروکسی فراپیام" - "درگاه فراپیام" - "MCC" - "MNC" - "نوع نام نقطه دستیابی" - "حذف نام نقطه دستیابی" - "نام نقطه دستیابی جدید" - "ذخیره‌" - "صرف‌نظر کردن" - "فیلد نام نباید خالی باشد." - "نام نقطه دستیابی نباید خالی باشد." - "‏فیلد MCC باید ۳ رقمی باشد." - "‏فیلد MNC باید ۲ یا ۳ رقمی باشد." - "درحال بازیابی تنظیمات پیش‌فرض نام نقطه دستیابی." - "بازنشانی به پیش‌فرض" - "تنظیمات پیش‌فرض نام نقطه دستیابی بازنشانی شد." - "بدون عنوان" - "نام‌های نقطه دسترسی" - "نام‌های نقاط دستیابی" - "نام نقطه دستیابی جدید" - "تنظیمات نام تقطه دسترسی برای این کاربر در دسترس نیست" - "کپی در بریده‌دان؟" - "کپی" - "به %s" - "کلی" - "پیشرفته" - "تنظیمات کلی" - "تنظیمات پیشرفته" - "سیم کارت «%s»" - "ارسال پیامک‌های جداگانه به همه گیرندگان. فقط شما جواب‌ها را دریافت می‌کنید" - "‏ارسال یک MMS تکی به همه گیرندگان" - "شماره ناشناس" - "پیام جدید" - "پیام جدید." - "انتخاب سیم" - "%1$s انتخاب شد، انتخاب‌گر سیم‌کارت" - "ویرایش موضوع" - "انتخاب سیم کارت یا ویرایش موضوع" - "برای ضبط کردن صدا، لمس کنید و نگه‌دارید" - "شروع مکالمه جدید" - "پیام‌رسانی" - "فهرست «پیام‌رسانی»" - "پیام‌رسانی" - "پیام جدید" - "فهرست مکالمات" - "در حال بارگیری مکالمات" - "در حال بارگیری پیام‌ها" - "مشاهده مکالمات بیشتر" - "مشاهده پیام‌های دیگر" - "مکالمه حذف شد" - "مکالمه حذف شد. لمس کنید تا مکالمه «پیام‌رسانی» دیگری نشان داده شود." - "مسدود شد" - "گشوده شد" - "فضای ذخیره‌سازی کم است. ممکن است برخی از اطلاعات از دست بروند." - "انتخاب پیوست‌ها" - "تأیید انتخاب" - "%d مورد انتخاب شد" - "لطفاً یک یا چند پیوست را بردارید و دوباره امتحان کنید." - "می‌توانید سعی کنید پیامتان را ارسال کنید اما شاید تا وقتی که یک یا چند پیوست را برندارید، تحویل داده نشود." - "فقط می‌توانید یک ویدیو در هر پیام ارسال کنید. لطفاً ویدیوهای اضافی را حذف کنید و دوباره امتحان نمایید." - "«پیام‌رسانی» پیوست را بارگیری نکرد." - "در هر حال فرستاده شود" - "مکالمه را نمی‌توان شروع کرد" - "%1$s‏ (%2$s)" - "%s انتخاب شد" - diff --git a/res/values-fi/arrays.xml b/res/values-fi/arrays.xml deleted file mode 100644 index 95d4f23..0000000 --- a/res/values-fi/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ei aihetta" - "ei aihetta" - - - "Kyllä" - "Ei" - "OK" - "Hehe" - "Kiitos" - "Olen samaa mieltä" - "Kiva" - "Matkalla" - "OK, palaan asiaan" - ":)" - ":(" - - diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml deleted file mode 100644 index 47ef00f..0000000 --- a/res/values-fi/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Viestit" - "Viestit" - "Valitse keskustelu" - "Asetukset" - "Lähetä viesti" - "Lisää liite" - "Ohje" - "Tervetuloa" - "Ohita" - "Seuraava >" - "Seuraava" - "Sulje" - "Asetukset >" - "Asetukset" - "Viestit tarvitsee käyttöoikeuden tekstiviesteihin, puhelimeen ja yhteystietoihin." - "Voit muuttaa käyttöoikeuksia kohdassa Asetukset > Sovellukset > Viestit > Käyttöoikeudet." - "Voit muuttaa käyttöoikeuksia kohdassa Asetukset, Sovellukset, Viestit, Käyttöoikeudet." - "Usein käytetyt" - "Yhteystiedot" - "Lähetä numeroon %s" - "Tallenna kuva tai video" - "Valitse kuvia tästä laitteesta" - "Tallenna ääntä" - "Valitse valokuva" - "Media on valittu." - "Median valinta on poistettu." - "%d valittu" - "kuva %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "kuva" - "Tallenna ääntä" - "Jaa" - "Juuri nyt" - "Nyt" - - %d min - %d minuutti - - - %d tuntia - %d tunti - - - %d päivää - %d päivä - - - %d viikkoa - viikko - - - %d kuukautta - kuukausi - - - %d vuotta - vuosi - - "Luokan 0 viesti" - "Tallenna" - "Laitteen tila on vähissä. Viestit poistaa automaattisesti vanhempia viestejä vapauttaakseen tilaa." - "Tallennustila vähissä" - "Viestit ei ehkä lähetä tai vastaanota viestejä ennen kuin laitteessa on enemmän vapaata tilaa." - "Tekstiviestien tallennustila on vähissä. Harkitse viestien poistamista." - "Vahvista puhelinnumerosi" - "Tekemällä tämän kertaluontoisen vaiheen varmistat, että Viestit toimittaa ryhmäviestisi oikein." - "Puhelinnumero" - "Poista kaikki mediaa sisältävät viestit" - "Poista viestit, joiden ikä on yli %s" - "Poista automaattisesti viestit, jotka ovat vanhempia kuin %s" - "Ohita" - "Poistetaanko kaikki mediaa sisältävät viestit?" - "Poistetaanko viestit, jotka ovat vanhempia kuin %s?" - "Poistetaanko viestit, jotka ovat vanhempia kuin %s, ja otetaanko automaattinen poisto käyttöön?" - "%s sanoi" - "Sinä sanoit" - "Viesti käyttäjältä %s" - "Lähetit viestin" - "Lähetetään…" - "Ei lähetetty. Yritä uudelleen koskettamalla." - "Ei lähetetty. Yritetään uudelleen…" - "Lähetä uudelleen tai poista" - "Ota yhteyttä hätäpalveluihin soittamalla äänipuhelu. Tekstiviestiäsi ei voida toimittaa tällä hetkellä." - "Epäonnistui" - "Uusi multimediaviesti ladattavissa" - "Uusi multimediaviesti" - "Lataus epäonnistui" - "Yritä uudelleen koskettamalla" - "Lataa koskettamalla" - "Lataa tai poista" - "Ladataan…" - "Viesti on vanhentunut tai ei ole käytettävissä" - "koko: %1$s, vanhenee: %2$s" - "Ei voi lähettää. Vastaanottaja ei kelpaa." - "Palvelua ei ole aktivoitu verkossa" - "Ei voitu lähettää verkkovian takia" - "Viesti on vanhentunut tai ei ole käytettävissä" - "(Ei aihetta)" - "Tuntematon lähettäjä" - "Toimitettu" - "Lähettäjän %2$s viestin %1$s lataaminen epäonnistui." - "Tietokantatoimintoa ei voi suorittaa: muisti on vähissä" - "Viestiä ei lähetetty" - "Joitakin viestejä ei lähetetty Viesteissä." - - %d viestiä %d keskustelussa - %d viesti yhdessä keskustelussa - - "Viestiä ei ladattu." - "Joitakin viestejä ei ladattu Viesteissä." - - %d viestiä %d keskustelussa - %d viesti yhdessä keskustelussa - - "Viestiä numeroon %1$s ei lähetetty." - "Ota yhteyttä hätäpalveluihin soittamalla äänipuhelu. Tekstiviestiäsi numeroon %1$s ei voida toimittaa tällä hetkellä." - - %d uutta viestiä - Uusi viesti - - "Käynnistä" - "Kamera ei käytettävissä" - "Kamera ei käytettävissä" - "Videon kaappaus ei käytettävissä" - "Mediaa ei voi tallentaa" - "Kuvan ottaminen ei onnistu." - "Edellinen" - "Arkistoitu" - "Poista" - "Arkistoi" - "Poista arkistosta" - "Poista ilmoitukset käytöstä" - "Ota ilmoitukset käyttöön" - "Lisää kontakti" - "Lataa" - "Lähetä" - "Poista" - "Poistetaanko tämä viesti?" - "Tätä toimintoa ei voi kumota." - "Poista" - - Poistetaanko nämä keskustelut? - Poistetaanko tämä keskustelu? - - "Poista" - "Peruuta" - "Vastaanottaja" - "Valitse useita kuvia" - "Vahvista valinta" - "+ %d" - "Ääntä ei voi tallentaa. Yritä uudelleen." - "Ääntä ei voi toistaa. Yritä uudelleen." - "Ääntä ei voitu tallentaa. Yritä uudelleen." - "Kosketa pitkään" - ", " - " " - ": " - " " - "Kuva" - "Äänileike" - "Video" - "Yhteystietokortti" - "Lataa" - "Vastaa tekstiviestitse" - "Vastaa MMS-viestillä" - "Vastaa" - - %d osallistujaa - %d osallistuja - - "Minä" - "Yhteystieto estetty ja arkistoitu" - "Yhteystiedon esto on poistettu, ja se on poistettu arkistosta." - "%d arkistoitua" - "%d poistettiin arkistosta" - "Ilmoitukset on poistettu käytöstä." - "Ilmoitukset on otettu käyttöön." - "Kaikki valmiina. Kosketa Lähetä uudelleen." - "Viestit asetettiin oletustekstiviestisovellukseksi." - - Hylkää liitteet - Hylkää liite - - "Ääniliite" - "Toista ääniliite" - "Tauko" - "Viesti lähettäjältä %s: %s." - "Epäonnistunut viesti lähettäjältä %s: %s. Aika: %s." - "Viesti lähettäjältä %s: %s. Aika: %s." - "Lähettämätön viesti vastaanottajalle %s: %s. Aika: %s." - "Lähetetään viestiä vastaanottajalle %s: %s. Aika: %s." - "Epäonnistunut viesti vastaanottajalle %s: %s. Aika: %s." - "Viesti vastaanottajalle %s: %s. Aika: %s." - "Epäonnistunut viesti lähettäjältä %s: %s. Aika: %s. %s." - "Viesti lähettäjältä %s: %s. Aika: %s. %s." - "Lähettämätön viesti vastaanottajalle %s: %s. Aika: %s." - "Lähetetään viestiä vastaanottajalle %s: %s. Aika: %s." - "Epäonnistunut viesti vastaanottajalle %s: %s. Aika: %s." - "Viesti vastaanottajalle %s: %s. Aika: %s." - "Viesti epäonnistui. Yritä uudelleen koskettamalla." - "Keskustelu henkilöiden %s kanssa" - "Poista aihe" - "Tallenna video" - "Ota kuva" - "Ota kuva" - "Aloita videon tallentaminen" - "Siirry koko näytön kameraan" - "Vaihda etu- ja takakameran välillä" - "Lopeta tallennus ja liitä video" - "Lopeta videon tallentaminen" - "Viestien valokuvat" - - %d valokuvaa tallennettu albumiin %s - %d valokuva tallennettu albumiin %s - - - %d videota tallennettu albumiin %s - %d video tallennettu albumiin %s - - - %d liitettä tallennettu albumiin %s - %d liite tallennettu albumiin %s - - - %d liitettä tallennettu Lataukset-kansioon - %d liite tallennettu Lataukset-kansioon - - - %d liitettä tallennettiin - %d liite tallennettiin. - - - %d liitteen tallentaminen epäonnistui. - %d liitteen tallentaminen epäonnistui. - - "Multimedialiite tallennettiin" - "Asetukset" - "Arkistoitu" - "Sulje" - "Multimediaviesti" - "Lisäasetukset" - "Vianetsintä" - "Ilmoitukset" - "Ääni" - "Äänetön" - "Värinä" - "Estetty" - "Tekstiviestin lähetystiedot" - "Pyydä lähetystiedot kaikista lähettämistäsi tekstiviesteistä" - "Automaattilataus" - "Lataa MMS-viestit automaattisesti" - "Automaattilataus roaming-tilassa" - "Hae MMS-viestit automaattisesti roaming-tilassa." - "Ryhmäviestit" - "Jaa yksi multimediaviesti useille vastaanottajille" - "Oletusarvoinen tekstiviestisovellus" - "Oletusarvoinen tekstiviestisovellus" - - - "Puhelinnumerosi" - "Tuntematon" - "Lähtevien viestien äänet" - "Tekstiviestivedos" - "Vedosta vastaanotetut tekstiviestien raakatiedot ulkoiseen tallennustiedostoon" - "MMS-vedos" - "Vedosta vastaanotetut MMS-raakatiedot ulkoiseen tallennustiedostoon" - "Langattoman verkon ilmoitukset" - "Viestiasetukset" - "Kopioi teksti" - "Näytä tiedot" - "Poista" - "Lähetä edelleen" - "Viestin tiedot" - "Tyyppi: " - "Tekstiviesti" - "Multimediaviesti" - "Lähettäjä: " - "Vastaanottaja: " - "Lähetetty: " - "Vastaanotettu: " - "Aihe: " - "Koko: " - "Tärkeys: " - "SIM-kortti: " - "Korkea" - "Normaali" - "Matala" - "SIM-kortti %s" - "Piilotettu lähettäjän osoite" - "Viestiä ei voi lähettää liitetiedostojen latauksen aikana." - "Liitettä ei voi ladata. Yritä uudelleen." - "Verkko ei ole valmis. Yritä uudelleen." - "Poista teksti" - "Vaihda tekstin ja numeroiden syötön välillä" - "Lisää osallistujia" - "Vahvista osallistujat" - "Aloita uusi keskustelu" - "Valitse tämä" - "Katso video" - "Henkilöt ja asetukset" - "Vianetsintä" - "Henkilöt ja asetukset" - "Yleiset" - "Keskustelun osallistujat" - "Soita puhelu" - "Lähetä viesti" - "Lähetä viesti<br/><small>kortilta %s</small>" - - Lähetä kuvia - Lähetä kuva - - - Lähetä äänitiedostoja - Lähetä äänitiedosto - - - Lähetä videoita - Lähetä video - - - Lähetä yhteystietokortteja - Lähetä yhteystietokortti - - - Lähetä liitteitä - Lähetä liite - - - %d liitettä valmiina lähetettäväksi - Yksi liite valmiina lähetettäväksi - - "Lähetä palautetta" - "Näytä Google Play Kaupassa" - "Versiotiedot" - "Versio %1$s" - "Avoimen lähdekoodin käyttöluvat" - "Ilmoitukset" - "Liitetiedostojen enimmäisraja on saavutettu." - "Liitetiedoston lataaminen epäonnistui." - "Lisätäänkö yhteystietoihin?" - "Lisää yhteystieto" - "Aihe" - "Aihe: " - "%s%s" - "Ladataan yhteystietokorttia" - "Yhteystietokorttia ei voitu ladata:" - "Näytä yhteystietokortti" - - %d yhteystietoa - %d yhteystieto - - "Yhteystietokortit" - "Syntymäpäivä" - "Muistiinpanot" - "Lähetä viesti edelleen" - "Vastaa" - "+1" - "+%d" - "Tekstiviestit on poistettu käytöstä" - "Voit lähettää valittuasi Viestit oletustekstiviestisovellukseksi." - "Aseta Viestit oletustekstiviestisovellukseksi." - "Vaihda" - "Voit vastaanottaa viestejä valittuasi Viestit oletustekstiviestisovellukseksi." - "Ensisijaista SIM-korttia ei ole valittu tekstiviestien lähettämiselle" - "Laitteen omistaja ei ole myöntänyt lupaa tälle sovellukselle." - "OK" - "Keskustelussa on liian monta osallistujaa" - - Virheelliset yhteystiedot - Virheellinen yhteystieto - - "Kamerakuvaa ei voitu ladata" - "Sinä: " - "%s: " - "Luonnos" - "Uudet keskustelut on lueteltu tässä" - "Arkistoidut keskustelut näkyvät tässä" - "Ladataan keskusteluja…" - "Kuva" - "Äänileike" - "Video" - "Yhteystietokortti" - "Multimediaviesti" - "Kumoa" - "Yritä uudelleen" - "Aloita uusi viesti antamalla yhteystiedon nimi tai puhelinnumero" - "Estä" - "Estä %s" - "Kumoa esto: %s" - "Estetäänkö %s?" - "Saat edelleen viestejä tästä numerosta, mutta et enää ilmoituksia. Tämä keskustelu arkistoidaan." - "Estetyt yhteystiedot" - "KUMOA ESTO" - "Estetyt yhteystiedot" - "Valitse kuva dokumenttikirjastosta" - "Lähetetään viestiä" - "Viesti lähetetty" - "Matkapuhelindata on kytketty pois. Tarkista asetukset." - "Viestejä ei voi lähettää lentokonetilassa" - "Viestiä ei voitu lähettää" - "Viesti ladattu" - "Matkapuhelindata on kytketty pois. Tarkista asetukset." - "Viestejä ei voi ladata lentokonetilassa." - "Viestin lataaminen epäonnistui." - "Nolla" - "Yksi" - "Kaksi" - "Kolme" - "Neljä" - "Viisi" - "Kuusi" - "Seitsemän" - "Kahdeksan" - "Yhdeksän" - "Viestin lähettäminen operaattorin %1$s kautta ei onnistunut. Virhe %2$d" - "Viestin lähettäminen tuntemattoman operaattorin kautta ei onnistunut. Virhe %1$d" - "Vl: %s" - "Viestiä ei lähetetty: palvelu ei ole käytössä verkossa" - "Viestiä ei lähetetty: virheellinen kohdeosoite" - "Viestiä ei lähetetty: virheellinen viesti" - "Viestiä ei lähetetty: sisältöä ei tueta" - "Viestiä ei lähetetty: viestiä ei tueta" - "Viestiä ei lähetetty: liian suuri" - "Uusi viesti" - "Näytä" - "Kuva" - "Sopivaa sovellusta ei löytynyt" - "Poista vastaanottaja" - "Uusi viesti" - "Peruuta" - "Muokkaa tukiasemaa" - "Ei asetettu" - "Nimi" - "APN" - "MMSC" - "Multimediaviestien välityspalvelin" - "Multimediaviestien portti" - "MCC" - "MNC" - "APN-tyyppi" - "Poista APN" - "Uusi APN" - "Tallenna" - "Hylkää" - "Nimi-kenttä ei voi olla tyhjä." - "APN ei voi olla tyhjä." - "MCC-kentän arvon oltava kolminumeroinen." - "MNC-kentän arvon on oltava kaksi- tai kolminumeroinen." - "Palautetaan APN-oletusasetuksia" - "Palauta oletukseksi" - "APN-oletusasetukset on palautettu." - "Nimetön" - "Tukiasemien nimet" - "APN:t" - "Uusi APN" - "Tämä käyttäjä ei voi käyttää APN-asetuksia" - "Kopioidaanko leikepöydälle?" - "Kopioi" - "vastaanottaja: %s" - "Yleiset" - "Lisäasetukset" - "Yleiset asetukset" - "Lisäasetukset" - "%s SIM" - "Lähetä oma tekstiviesti kullekin vastaanottajalle. Vain sinä saat vastaukset." - "Lähetä yksi MMS-viesti kaikille vastaanottajille" - "Tuntematon numero" - "Uusi viesti" - "Uusi viesti." - "SIM-valitsin" - "%1$s valittu, SIM-valitsin" - "Muokkaa aihetta" - "Valitse SIM tai muokkaa aihetta" - "Nauhoita ääntä koskettamalla pitkään" - "Aloita uusi keskustelu." - "Viestit" - "Viestit-luettelo" - "Viestit" - "Uusi viesti" - "Keskusteluluettelo" - "Ladataan keskusteluja." - "Ladataan viestejä." - "Näytä lisää keskusteluita." - "Näytä lisää viestejä." - "Keskustelu on poistettu." - "Keskustelu poistettu. Näytä toinen Viestit-keskustelu." - "Estetty" - "Esto kumottu" - "Tallennustila on vähissä. Tietoja voi kadota." - "Valitse liitteet" - "Vahvista valinta" - "%d valittu" - "Poista yksi tai useampi liitetiedosto ja yritä uudelleen." - "Voit yrittää lähettää viestin, mutta sitä ei ehkä toimiteta ennen kuin olet poistanut yhden tai useamman liitetiedoston." - "Voit lähettää vain yhden videon viestiä kohden. Poista ylimääräiset videot ja yritä uudelleen." - "Viestit ei pystynyt lataamaan liitetiedostoa." - "Lähetä silti" - "Keskustelun aloittaminen epäonnistui" - "%1$s (%2$s)" - "%s valittu" - diff --git a/res/values-fr-rCA/arrays.xml b/res/values-fr-rCA/arrays.xml deleted file mode 100644 index 05d76fb..0000000 --- a/res/values-fr-rCA/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sans objet" - "aucun objet" - - - "Oui" - "Non" - "OK" - "Hé hé" - "Merci" - "J\'accepte" - "Bien" - "En chemin" - "OK, je te réponds plus tard" - ":)" - ":(" - - diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml deleted file mode 100644 index 239f4af..0000000 --- a/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messagerie" - "Messagerie" - "Sélectionner la conversation" - "Paramètres" - "Envoyer un message" - "Ajouter une pièce jointe" - "Aide" - "Bienvenue" - "Ignorer" - "Suivant >" - "Suivant" - "Quitter" - "Paramètres >" - "Paramètres" - "Vous devez accorder à l\'application Messagerie des autorisations d\'accès aux messages, au téléphone et aux contacts." - "Vous pouvez modifier les autorisations dans Paramètres > Applications > Messagerie > Autorisations." - "Vous pouvez modifier les autorisations dans Paramètres > Applications > Messagerie > Autorisations." - "Fréquents" - "Tous les contacts" - "Envoyer à %s" - "Prendre des photos ou filmer une vidéo" - "Choisir des images de cet appareil" - "Enregistrer des fichiers audio" - "Choisir une photo" - "Support sélectionné." - "Support désélectionné." - "%d sélectionné(s)" - "Image : %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "image" - "Enregistrer des fichiers audio" - "Partager" - "À l\'instant" - "Maintenant" - - %d min - %d min - - - %d heure - %d heures - - - %d jour - %d jours - - - %d semaine - %d semaines - - - %d mois - %d mois - - - %d an - %d ans - - "Message de catégorie 0" - "Enregistrer" - "L\'appareil manque d\'espace. Pour en libérer, les messages de l\'application Messagerie les plus anciens seront automatiquement supprimés." - "L\'espace de stockage est presque plein" - "Il est possible que vous ne puissiez pas envoyer ni recevoir de messages dans l\'application Messagerie jusqu\'à ce que votre appareil ait plus d\'espace libre." - "Stockage limité pour les textos. Vous devrez peut-être supprimer des messages." - "Confirmez votre numéro de téléphone" - "Cette étape unique permet de garantir que vos messages de groupe sont correctement envoyés dans Messagerie." - "Numéro de téléphone" - "Supprimer tous les messages avec du contenu multimédia" - "Supprimer les messages datant de plus de %s" - "Suppression automatique des messages de plus de %s" - "Ignorer" - "Supprimer tous les messages avec contenu multimédia?" - "Supprimer les messages datant de plus de %s?" - "Supprimer les messages de plus de %s et activer la suppression automatique?" - "%s a dit" - "Vous avez dit" - "Message de %s" - "Vous avez envoyé un message" - "Envoi en cours..." - "Message non envoyé. Touchez ici pour réessayer." - "Message non envoyé. Nouvelle tentative en cours..." - "Renvoyer ou supprimer" - "Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre texto pour l\'instant." - "Échec" - "Nouveau message multimédia à télécharger" - "Nouveau message multimédia" - "Échec du téléchargement" - "Touchez ici pour réessayer" - "Touchez ici pour lancer le téléchargement." - "Télécharger ou supprimer" - "Téléchargement en cours..." - "Le message a expiré ou n\'est plus disponible." - "taille : %1$s, date d\'expiration : %2$s" - "Impossible d\'envoyer le message. Destinataire non valide." - "Ce service n\'est pas activé sur le réseau" - "Impossible d\'envoyer le message en raison d\'un problème de réseau" - "Le message est expiration ou n\'est plus disponible." - "(Aucun objet)" - "Expéditeur inconnu" - "Distribué" - "Impossible de télécharger le message « %1$s » de l\'utilisateur suivant : %2$s." - "Impossible d\'effectuer cette opération dans la base de données. Mémoire insuffisante." - "Le message n\'a pas été envoyé" - "Certains messages n\'ont pas été envoyés dans Messagerie" - - %d message dans %d conversations - %d messages dans %d conversations - - "Message non téléchargé" - "Certains messages n\'ont pas été téléchargés dans Messagerie" - - %d message dans %d conversations - %d messages dans %d conversations - - "Échec de l\'envoi du message à %1$s" - "Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre texto au numéro %1$s pour l\'instant." - - %d nouveau message - %d nouveaux messages - - "Démarrer" - "L\'appareil photo n\'est pas disponible" - "L\'appareil photo n\'est pas disponible" - "L\'enregistrement vidéo n\'est pas disponible" - "Impossible d\'enregistrer le média" - "Impossible de prendre la photo." - "Précédent" - "Archivée" - "Supprimer" - "Archiver" - "Annuler l\'archivage" - "Désactiver les notifications" - "Activer les notifications" - "Ajouter un contact" - "Télécharger" - "Envoyer" - "Supprimer" - "Supprimer ce message?" - "Cette action ne peut être annulée." - "Supprimer" - - Supprimer cette conversation? - Supprimer ces conversations? - - "Supprimer" - "Annuler" - "À" - "Sélectionner plusieurs images" - "Confirmer la sélection" - "+%d" - "Impossible d\'enregistrer l\'audio. Veuillez réessayer." - "Impossible de lire l\'audio. Veuillez réessayer." - "Impossible d\'enregistrer l\'audio. Veuillez réessayer." - "Maintenez le doigt ici" - ", " - " " - ": " - " " - "Image" - "Extrait audio" - "Vidéo" - "Fiche de contact" - "Télécharger" - "Répondre par texto" - "Rép. m. multim." - "Répondre" - - %d participant - %d participants - - "Moi" - "Contact bloqué et archivé" - "Contact débloqué et archivage annulé" - "%d archivée(s)" - "Archivage annulé de %d" - "Les notifications sont désactivées" - "Les notifications sont activées" - "Tout est prêt. Touchez « Envoyer » de nouveau." - "L\'application Messagerie a été définie comme application de messagerie par défaut." - - Supprimer la pièce jointe - Supprimer les pièces jointes - - "Contenu audio joint" - "Jouer le contenu audio joint" - "Pause" - "Message de %s : %s." - "Échec de la réception du message de %s : %s. Heure : %s." - "Message de %s : %s. Heure : %s." - "Message non envoyé à %s : %s. Heure : %s." - "Envoi du message à %s : %s. Heure : %s." - "Échec de l\'envoi du message à %s : %s. Heure : %s." - "Message à %s : %s. Heure : %s." - "Échec de la réception du message de %s : %s. Heure : %s. %s." - "Message de %s : %s. Heure : %s. %s." - "Message non envoyé à : %s: %s. Heure: %s." - "Envoi du message à %s : %s. Heure : %s." - "Échec de l\'envoi du message à %s : %s. Heure : %s." - "Message à %s : %s. Heure : %s." - "Échec du message. Touchez pour réessayer." - "Conversation avec %s" - "Supprimer l\'objet" - "Filmer une vidéo" - "Capturer une image fixe" - "Prendre une photo" - "Lancer l\'enregistrement vidéo" - "Activer le mode plein écran de l\'appareil photo" - "Basculer entre l\'appareil photo avant et arrière" - "Arrêter l\'enregistrement et joindre la vidéo" - "Arrêter l\'enregistrement vidéo" - "Photos de Messagerie" - - %d photo enregistrée dans l\'album « %s » - %d photos enregistrées dans l\'album « %s » - - - %d vidéo enregistrée dans l\'album « %s » - %d vidéos enregistrées dans l\'album « %s » - - - %d pièce jointe enregistrée dans l\'album « %s » - %d pièces jointes enregistrées dans l\'album « %s » - - - %d pièce jointe enregistrée dans le dossier « Téléchargements » - %d pièces jointes enregistrées dans le dossier « Téléchargements » - - - %d pièce jointe enregistrée - %d pièces jointes enregistrées - - - Impossible d\'enregistrer %d pièce jointe - Impossible d\'enregistrer %d pièces jointes - - "La pièce jointe du message multimédia a été enregistrée" - "Paramètres" - "Archivée" - "Fermer" - "Message multimédia" - "Avancés" - "Déboguer" - "Notifications" - "Son" - "Mode silencieux" - "Vibreur" - "Bloqué" - "Accusés de réception des messages texte" - "Demander un accusé de réception pour chaque texto envoyé" - "Récupération automatique" - "Récupérer automatiquement les messages multimédias" - "Récupération auto. en itinérance" - "Récupérer automatiquement les messages multimédias en itinérance" - "Messagerie de groupe" - "Utiliser la messagerie multimédia pour envoyer un message unique à plusieurs destinataires" - "Application de messagerie texte par défaut" - "Application de messagerie texte par défaut" - - - "Votre numéro de téléphone" - "Inconnu" - "Sons pour les messages sortants" - "Capturer les messages texte" - "Capturer les données brutes reçues des messages multimédias dans un fichier de stockage externe" - "Capturer les messages multimédias" - "Capturer les données brutes reçues des messages multimédias dans un fichier de stockage externe" - "Alertes par réseau sans fil" - "Options relatives aux messages" - "Copier le texte" - "Afficher les détails" - "Supprimer" - "Transférer" - "Détails du message" - "Type : " - "Message texte" - "Message multimédia" - "De : " - "À : " - "Envoyé : " - "Reçu : " - "Objet : " - "Taille : " - "Priorité : " - "Carte SIM : " - "Élevée" - "Normale" - "Faible" - "Fente SIM %s" - "Adresse d\'expéditeur masquée" - "Impossible d\'envoyer le message pendant le chargement des pièces jointes." - "Impossible de charger la pièce jointe. Veuillez réessayer." - "Le réseau n\'est pas prêt. Réssayez." - "Supprimer le texte" - "Alterner entre l\'entrée de texte et de chiffres" - "Ajouter des participants" - "Confirmer les participants" - "Commencer une nouvelle discussion" - "Sélectionner cet article" - "Lire la vidéo" - "Participants et options" - "Déboguer" - "Participants et options" - "Général" - "Participants à cette conversation" - "Faire un appel" - "Envoyer un message" - "Envoyer un message<br/><small>à partir de %s</small>" - - Envoyer la photo - Envoyer les photos - - - Envoyer le fichier audio - Envoyer les fichiers audio - - - Envoyer la vidéo - Envoyer les vidéos - - - Envoyer la fiche de contact - Envoyer les fiches de contact - - - Envoyer la pièce jointe - Envoyer les pièces jointes - - - %d pièce jointe est prête à être envoyée - %d pièces jointes sont prêtes à être envoyées - - "Envoyer des commentaires" - "Afficher dans la boutique Google Play Store" - "Détails de la version" - "Version %1$s" - "Licences de logiciels libres" - "Notifications" - "Taille maximale des pièces jointes atteinte" - "Échec du chargement de la pièce jointe." - "Ajouter aux contacts?" - "Ajouter un contact" - "Objet" - "Objet : " - "%s %s" - "Chargement de la fiche de contact" - "Impossible de charger la fiche de contact" - "Afficher la fiche de contact" - - %d contact - %d contacts - - "Fiches de contact" - "Anniversaire" - "Remarques" - "Transférer le message" - "Répondre" - "+1" - "+%d" - "Messagerie texte désactivée" - "Pour envoyer des messages, définissez Messagerie comme application de messagerie par défaut" - "Définissez Messagerie comme application de messagerie par défaut" - "Modifier" - "Pour recevoir des messages, vous devez définir l\'application Messagerie comme application de messagerie par défaut." - "Aucune carte SIM préférée n\'a été sélectionnée pour l\'envoi des messages texte" - "Cette application n\'est pas autorisée par le propriétaire de l\'appareil." - "OK" - "Trop de participants dans une conversation" - - Contact non valide - Contacts non valides - - "Impossible de charger l\'image de l\'appareil photo" - "Vous : " - "%s : " - "Brouillon" - "Lorsque vous commencez une conversation, elle s\'affiche ici" - "Les conversations archivées s\'affichent ici" - "Chargement des conversations en cours..." - "Image" - "Extrait audio" - "Vidéo" - "Fiche de contact" - "Message multimédia" - "Annuler" - "Réessayer" - "Entrez le nom d\'un contact ou un numéro de téléphone pour commencer un nouveau message" - "Bloquer" - "Bloquer %s" - "Débloquer %s" - "Bloquer %s?" - "Vous continuerez à recevoir les messages envoyés par ce numéro, mais vous ne recevrez plus de notifications. Cette conversation sera archivée." - "Contacts bloqués" - "DÉBLOQUER" - "Contacts bloqués" - "Choisissez une image dans la bibliothèque de documents" - "Envoi du message en cours..." - "Message envoyé" - "Les données cellulaires sont désactivées. Vérifiez vos paramètres." - "Impossible d\'envoyer des messages en mode Avion" - "Le message n\'a pas pu être envoyé" - "Message téléchargé" - "Les données cellulaires sont désactivées. Vérifiez vos paramètres." - "Impossible de télécharger des messages en mode Avion" - "Le message n\'a pas pu être téléchargé" - "Zéro" - "Un" - "Deux" - "Trois" - "Quatre" - "Cinq" - "Six" - "Sept" - "Huit" - "Neuf" - "Impossible d\'envoyer le message par l\'intermédiaire de %1$s. Erreur  : %2$d." - "Impossible d\'envoyer le message par l\'intermédiaire d\'un fournisseur de services inconnu. Erreur : %1$d." - "Tr : %s" - "Le message n\'a pas été envoyé, car le service n\'est pas activé sur le réseau" - "Le message n\'a pas été envoyé, car l\'adresse de destination est incorrecte" - "Le message n\'a pas été envoyé, car il n\'est pas valide" - "Le message n\'a pas été envoyé, car le contenu est incompatible" - "Le message n\'a pas été envoyé, car il n\'est pas pris en charge" - "Le message n\'a pas été envoyé, car il est trop gros" - "Nouveau message" - "Afficher" - "Image" - "Impossible de trouver une application appropriée." - "Supprimer le destinataire" - "Nouveau message" - "Annuler" - "Modifier le point d\'accès" - "Non défini" - "Nom" - "Nom du point d\'accès" - "MMSC" - "Serveur mandataire pour la messagerie multimédia" - "Port pour la messagerie multimédia" - "MCC" - "MNC" - "Type de point d\'accès" - "Supprimer le point d\'accès" - "Nouveau point d\'accès" - "Enregistrer" - "Supprimer" - "Le champ « Nom » est obligatoire." - "Le champ « Point d\'accès » est obligatoire." - "Le champ MCC doit contenir 3 chiffres." - "Le champ MNC doit contenir 2 ou 3 chiffres." - "Restauration des paramètres de points d\'accès par défaut en cours..." - "Rétablir les valeurs par défaut" - "La réinitialisation des paramètres de points d\'accès par défaut est terminée." - "Sans titre" - "Noms des points d\'accès" - "Noms des points d\'accès" - "Nouveau point d\'accès" - "Les paramètres de point d\'accès ne sont pas disponibles pour cet utilisateur" - "Copier dans le presse-papiers?" - "Copier" - %s" - "Général" - "Avancés" - "Paramètres généraux" - "Paramètres avancés" - "Carte SIM « %s »" - "Envoyer des textos individuels à tous les destinataires. Vous seul recevrez les réponses." - "Envoyer des messages multimédias individuels à tous les destinataires." - "Numéro inconnu" - "Nouveau message" - "Nouveau message." - "Sélecteur de carte SIM" - "%1$s sélectionnée, sélecteur de carte SIM" - "Modifier l\'objet" - "Sélectionner une carte SIM ou modifier l\'objet" - "Maintenez le doigt sur le bouton pour enregistrer des contenus audio" - "Commencer une nouvelle discussion" - "Messagerie" - "Liste des conversations" - "Messagerie" - "Nouveau message" - "Liste des conversations" - "Chargement des conversations en cours..." - "Messages en cours de chargement…" - "Afficher plus de conversations" - "Voir plus de messages" - "Conversation supprimée" - "La conversation a été supprimée. Touchez pour afficher une autre conversation dans Messagerie." - "Bloqué" - "Débloqué" - "L\'espace de stockage est faible. Vous risquez de perdre certaines données." - "Sélectionner les pièces jointes" - "Confirmer la sélection" - "%d sélectionné(s)" - "Veuillez supprimer une ou plusieurs pièces jointes, puis réessayer." - "Vous pouvez essayer d\'envoyer votre message, mais il risque de ne pas être remis sauf si vous supprimez une ou plusieurs pièces jointes." - "Vous ne pouvez envoyer qu\'une vidéo par message. Veuillez supprimer les vidéos supplémentaires, puis réessayer." - "Échec du chargement de la pièce jointe." - "Envoyer quand même" - "Impossible de démarrer la conversation" - "%1$s (%2$s)" - "%s sélectionnée" - diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml deleted file mode 100644 index 6bd25ad..0000000 --- a/res/values-fr/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "aucun objet" - "nosubject" - - - "Oui" - "Non" - "OK" - "Haha" - "Merci" - "J\'accepte." - "Bien" - "J\'arrive." - "OK, je te réponds plus tard." - ":)" - ":(" - - diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml deleted file mode 100644 index 688c1d5..0000000 --- a/res/values-fr/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "SMS/MMS" - "SMS/MMS" - "Sélectionner la conversation" - "Paramètres" - "Envoyer le message" - "Ajouter une pièce jointe" - "Aide" - "Bienvenue" - "Ignorer" - "Suivant >" - "Suivant" - "Quitter" - "Paramètres >" - "Paramètres" - "Vous devez accorder à l\'application de SMS/MMS des autorisations d\'accès aux SMS, au téléphone et aux contacts." - "Vous pouvez modifier les autorisations dans Paramètres > Applications > SMS/MMS > Autorisations." - "Vous pouvez modifier les autorisations dans Paramètres > Applications > SMS/MMS > Autorisations." - "Fréquents" - "Tous les contacts" - "Envoyer au %s" - "Prendre des photos ou filmer une vidéo" - "Sélectionner des images sur cet appareil" - "Enregistrer un fichier audio" - "Sélectionner une photo" - "Le support est sélectionné." - "Le support est désélectionné." - "%d élément(s) sélectionné(s)" - "image %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "image" - "Enregistrer un fichier audio" - "Partager" - "À l\'instant" - "À l\'instant" - - %d min - %d min - - - %d heure - %d heures - - - %d jour - %d jours - - - %d semaine - %d semaines - - - %d mois - %d mois - - - %d an - %d ans - - "Message de catégorie 0" - "Enregistrer" - "L\'appareil manque d\'espace. Pour en libérer, les messages de l\'application SMS/MMS les plus anciens seront supprimés automatiquement." - "Espace de stockage bientôt saturé" - "Il est possible que vous ne puissiez pas envoyer ni recevoir de messages via l\'application de SMS/MMS jusqu\'à ce que votre appareil dispose de plus d\'espace." - "Espace de stockage limité. Il est possible que vous deviez supprimer des SMS." - "Confirmez votre numéro de téléphone" - "Cette étape unique permet de garantir que vos messages de groupe sont correctement envoyés via les SMS/MMS." - "Numéro de téléphone" - "Supprimer tous les messages comportant du contenu multimédia" - "Supprimer les messages datant de plus de %s" - "Supprimer automatiquement les messages datant de plus de %s" - "Ignorer" - "Supprimer tous les messages comportant du contenu multimédia ?" - "Supprimer les messages datant de plus de %s ?" - "Supprimer les messages datant de plus de %s et activer la suppression automatique ?" - "%s a dit" - "Vous avez dit" - "Expéditeur du message : %s" - "Vous avez envoyé un message." - "Envoi en cours…" - "Message non envoyé. Veuillez appuyer ici pour réessayer." - "Message non envoyé. Nouvelle tentative en cours…" - "Renvoyer ou supprimer" - "Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre SMS pour l\'instant." - "Échec." - "Nouveau MMS à télécharger" - "Nouveau message MMS" - "Téléchargement impossible." - "Appuyer ici pour réessayer" - "Appuyer ici pour lancer le téléchargement" - "Télécharger ou supprimer" - "Téléchargement en cours…" - "Le message est arrivé à expiration ou n\'est plus disponible." - "taille : %1$s, expiration : %2$s" - "Impossible d\'envoyer le message. Destinataire non valide." - "Service non activé sur le réseau." - "Impossible d\'envoyer le message en raison de problèmes sur le réseau." - "Le message est arrivé à expiration ou n\'est plus disponible." - "(Aucun objet)" - "Expéditeur inconnu" - "Distribué" - "Impossible de télécharger le message \"%1$s\" de l\'utilisateur suivant : %2$s." - "Impossible d\'effectuer cette opération dans la base de données. Mémoire insuffisante." - "Le message n\'a pas été envoyé." - "Certains messages n\'ont pas été envoyés via les SMS/MMS." - - %d messages dans %d conversation - %d messages dans %d conversations - - "Message non téléchargé." - "Certains messages n\'ont pas été téléchargés via les SMS/MMS." - - %d messages dans %d conversation - %d messages dans %d conversations - - "Échec de l\'envoi du message à %1$s" - "Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre SMS au numéro %1$s pour l\'instant." - - %d nouveau message - %d nouveaux messages - - "Démarrer" - "Caméra indisponible." - "Caméra indisponible." - "Enregistrement vidéo indisponible." - "Impossible d\'enregistrer le fichier multimédia." - "Impossible de prendre la photo." - "Retour" - "Archivées" - "Supprimer" - "Archiver" - "Annuler l\'archivage" - "Désactiver les notifications" - "Activer les notifications" - "Ajouter un contact" - "Télécharger" - "Envoyer" - "Supprimer" - "Supprimer ce message ?" - "Cette action est irréversible." - "Supprimer" - - Supprimer cette conversation ? - Supprimer ces conversations ? - - "Supprimer" - "Annuler" - "À" - "Sélectionner plusieurs images" - "Confirmer la sélection" - "+ %d" - "Impossible d\'enregistrer l\'audio. Veuillez réessayer." - "Impossible de lire l\'audio. Veuillez réessayer." - "Impossible d\'enregistrer l\'audio. Veuillez réessayer." - "Appuyez ici de manière prolongée" - ", " - " " - " : " - " " - "Image" - "Extrait audio" - "Vidéo" - "Fiche de contact" - "Télécharger" - "Répondre par SMS" - "Répondre par MMS" - "Répondre" - - %d participant - %d participants - - "Moi" - "Contact bloqué et archivé" - "Contact débloqué et archivage annulé." - "%d archivée(s)" - "Archivage annulé de %d conversation(s)." - "Notifications désactivées." - "Notifications activées." - "Configuration réussie. Appuyez de nouveau sur \"Envoyer\"." - "L\'application de SMS/MMS a bien été définie comme application de SMS par défaut." - - Supprimer la pièce jointe - Supprimer les pièces jointes - - "Pièce jointe audio" - "Jouer le contenu audio joint" - "Pause" - "Message de %s : %s." - "Échec de la réception du message envoyé par %s : %s. Heure : %s" - "Message envoyé par %s : %s. Heure : %s" - "Message non envoyé à %s : %s. Heure : %s" - "Envoi du message à %s en cours : %s. Heure : %s" - "Échec de l\'envoi du message à %s : %s. Heure : %s" - "Message à %s : %s. Heure : %s" - "Échec de la réception du message envoyé par %s : %s. Heure : %s. %s" - "Message envoyé par %s : %s. Heure : %s. %s" - "Message non envoyé au groupe \"%s\" : %s. Heure : %s" - "Envoi du message au groupe \"%s\" en cours : %s. Heure : %s" - "Échec de l\'envoi du message au groupe \"%s\" : %s. Heure : %s" - "Message au groupe \"%s\" : %s. Heure : %s" - "Échec du message. Appuyez pour réessayer." - "Conversation avec %s" - "Supprimer l\'objet" - "Enregistrer une vidéo" - "Capturer une image fixe" - "Prendre une photo" - "Lancer l\'enregistrement vidéo" - "Passer en caméra plein écran" - "Passer de la caméra frontale à la caméra arrière, et inversement" - "Arrêter l\'enregistrement et joindre la vidéo" - "Arrêter l\'enregistrement vidéo" - "Photos envoyées ou reçues par SMS/MMS" - - %d photo a été enregistrée dans l\'album \"%s\" - %d photos ont été enregistrées dans l\'album \"%s\". - - - %d vidéo a été enregistrée dans l\'album \"%s\". - %d vidéos ont été enregistrées dans l\'album \"%s\". - - - %d pièce jointe a été enregistrée dans l\'album \"%s\". - %d pièces jointes ont été enregistrées dans l\'album \"%s\". - - - %d pièce jointe a été enregistrée dans le dossier \"Téléchargements\". - %d pièces jointes ont été enregistrées dans le dossier \"Téléchargements\". - - - %d pièce jointe a été enregistrée. - %d pièces jointes ont été enregistrées. - - - Impossible d\'enregistrer %d pièce jointe. - Impossible d\'enregistrer %d pièces jointes. - - "Pièce jointe du MMS enregistrée" - "Paramètres" - "Archivées" - "Fermer" - "MMS" - "Paramètres avancés" - "Déboguer" - "Notifications" - "Son" - "Mode silencieux" - "Vibreur" - "Bloqué" - "Accusés de réception de SMS" - "Demander un accusé de réception pour chaque SMS envoyé" - "Récupération automatique" - "Récupérer automatiquement les MMS" - "Récupération auto. en itinérance" - "Récupérer automatiquement les MMS en itinérance" - "Messagerie de groupe" - "Utiliser un MMS pour envoyer un message unique à plusieurs destinataires" - "Application SMS par défaut" - "Application SMS par défaut" - - - "Votre numéro de téléphone" - "Inconnu" - "Sons pour les messages sortants" - "Créer une copie des SMS" - "Créer une copie des données SMS brutes reçues dans un fichier de stockage externe" - "Créer une copie des MMS" - "Créer une copie des données MMS brutes reçues dans un fichier de stockage externe" - "Alertes via le réseau sans fil" - "Options relatives au message" - "Copier le texte" - "Afficher les détails" - "Supprimer" - "Transférer" - "Détails du message" - "Type : " - "SMS" - "MMS" - "Expéditeur : " - "Destinataire : " - "Envoyé le : " - "Reçu le : " - "Objet : " - "Taille : " - "Priorité : " - "SIM : " - "Élevée" - "Normale" - "Basse" - "SIM %s" - "Adresse de l\'expéditeur masquée" - "Impossible d\'envoyer le message pendant le chargement des pièces jointes." - "Impossible de charger la pièce jointe. Veuillez réessayer." - "Le réseau n\'est pas prêt. Veuillez réessayer." - "Supprimer le texte" - "Alterner entre la saisie de texte et de chiffres" - "Ajouter d\'autres participants" - "Confirmer les participants" - "Démarrer une nouvelle conversation" - "Sélectionner cet article" - "Lire la vidéo" - "Participants et options" - "Déboguer" - "Participants et options" - "Général" - "Participants à cette conversation" - "Passer un appel" - "Envoyer un message" - "Envoyer un message<br/><small>avec la carte SIM \"%s\"</small>" - - Envoyer la photo - Envoyer les photos - - - Envoyer le fichier audio - Envoyer les fichiers audio - - - Envoyer la vidéo - Envoyer les vidéos - - - Envoyer la carte de visite - Envoyer les cartes de visite - - - Envoyer la pièce jointe - Envoyer les pièces jointes - - - %d pièce jointe est prête à être envoyée. - %d pièces jointes sont prêtes à être envoyées. - - "Envoyer des commentaires" - "Afficher sur le Google Play Store" - "Informations sur la version" - "Version %1$s" - "Licences Open Source" - "Notifications" - "Taille maximale des pièces jointes atteinte" - "Échec du chargement de la pièce jointe." - "Ajouter aux contacts ?" - "Ajouter un contact" - "Objet" - "Objet : " - "%s%s" - "Chargement de la fiche de contact en cours…" - "Impossible de charger la fiche de contact." - "Afficher la fiche de contact" - - %d contact - %d contacts - - "Fiches de contacts" - "Anniversaire" - "Remarques" - "Transférer le message" - "Répondre" - "+ 1" - "+%d" - "SMS désactivés" - "Pour envoyer des messages, vous devez définir l\'application de SMS/MMS comme application de SMS par défaut." - "Définissez l\'application de SMS/MMS comme application de SMS par défaut." - "Modifier" - "Pour recevoir des messages, vous devez définir l\'application de SMS/MMS comme application de SMS par défaut." - "Aucune carte SIM préférée sélectionnée pour l\'envoi de SMS." - "Cette application n\'est pas autorisée par le propriétaire de l\'appareil." - "OK" - "Trop de participants dans une conversation." - - Contact non valide. - Contacts non valides. - - "Impossible de charger l\'image de la caméra." - "Vous : " - "%s : " - "Brouillon" - "Lorsque vous commencez une conversation, elle s\'affiche ici." - "Les conversations archivées s\'affichent ici." - "Chargement des conversations en cours…" - "Image" - "Extrait audio" - "Vidéo" - "Fiche de contact" - "MMS" - "Annuler" - "Réessayer" - "Saisissez le nom d\'un contact ou un numéro de téléphone pour commencer un nouveau message" - "Bloquer" - "Bloquer \"%s\"" - "Débloquer \"%s\"" - "Bloquer %s ?" - "Vous continuerez à recevoir les messages envoyés par ce numéro, mais vous ne recevrez plus de notifications. Cette conversation sera archivée." - "Contacts bloqués" - "DÉBLOQUER" - "Contacts bloqués" - "Sélectionner une image dans la bibliothèque de documents" - "Envoi du message en cours..." - "Message envoyé." - "Les données cellulaires sont désactivées. Veuillez vérifier vos paramètres." - "Impossible d\'envoyer des messages en mode Avion." - "Envoi du SMS impossible." - "Message téléchargé." - "Les données cellulaires sont désactivées. Veuillez vérifier vos paramètres." - "Impossible de télécharger les messages en mode Avion." - "Impossible de télécharger le message." - "Zéro" - "Un" - "Deux" - "Trois" - "Quatre" - "Cinq" - "Six" - "Sept" - "Huit" - "Neuf" - "Impossible d\'envoyer le message via %1$s. Erreur : %2$d" - "Impossible d\'envoyer le message via un opérateur inconnu. Erreur : %1$d" - "TR : %s" - "Le message n\'a pas été envoyé, car le service n\'est pas activé sur le réseau." - "Le message n\'a pas été envoyé, car l\'adresse de destination est incorrecte." - "Le message n\'a pas été envoyé, car il n\'est pas valide." - "Le message n\'a pas été envoyé, car le contenu est incompatible." - "Le message n\'a pas été envoyé, car il est incompatible." - "Le message n\'a pas été envoyé, car il est trop volumineux." - "Nouveau message" - "Afficher" - "Image" - "Impossible de trouver une application appropriée." - "Supprimer le destinataire" - "Nouveau message" - "Annuler" - "Modifier le point d\'accès" - "Non défini" - "Nom" - "APN" - "MMSC" - "Proxy pour les MMS" - "Port pour les MMS" - "MCC" - "MNC" - "Type d\'APN" - "Supprimer l\'APN" - "Nouvel APN" - "Enregistrer" - "Supprimer" - "Champ \"Nom\" obligatoire." - "APN obligatoire." - "Le champ \"MCC\" doit contenir trois chiffres." - "Le champ \"MNC\" doit contenir deux ou trois chiffres." - "Restauration des paramètres APN par défaut en cours…" - "Rétablir les paramètres par défaut" - "Les paramètres APN par défaut ont bien été réinitialisés." - "Sans titre" - "Points d\'accès" - "APN" - "Nouvel APN" - "Les paramètres de point d\'accès ne sont pas disponibles pour cet utilisateur." - "Copier dans le Presse-papiers ?" - "Copier" - "destinataire : \"%s\"" - "Général" - "Paramètres avancés" - "Paramètres généraux" - "Paramètres avancés" - "Carte SIM \"%s\"" - "Envoyer des SMS individuels à tous les destinataires. Vous seul recevrez les réponses." - "Envoyer un MMS unique à tous les destinataires" - "Depuis un numéro inconnu" - "Nouveau message" - "Nouveau message." - "Sélecteur de carte SIM" - "\"%1$s\" sélectionnée – Sélecteur de carte SIM" - "Modifier l\'objet" - "Sélectionner une carte SIM ou modifier l\'objet" - "Appuyer de manière prolongée pour enregistrer des contenus audio" - "Lancer une conversation" - "SMS/MMS" - "Liste des SMS/MMS" - "SMS/MMS" - "Nouveau message" - "Liste des conversations" - "Chargement des conversations en cours…" - "Chargement des messages en cours…" - "Afficher plus de conversations" - "Afficher d\'autres messages" - "Conversation supprimée." - "La conversation a bien été supprimée. Appuyez pour afficher une autre conversation de SMS/MMS." - "Bloqué" - "Débloqué" - "L\'espace de stockage est faible. Vous risquez de perdre certaines données." - "Sélectionner les pièces jointes" - "Confirmer la sélection" - "%d élément(s) sélectionné(s)" - "Veuillez supprimer une ou plusieurs pièces jointes, puis réessayer." - "Vous pouvez essayer d\'envoyer votre message, mais il risque de ne pas être remis sauf si vous supprimez une ou plusieurs pièces jointes." - "Vous ne pouvez envoyer qu\'une vidéo par message. Veuillez supprimer les vidéos supplémentaires, puis réessayer." - "Échec du chargement de la pièce jointe via les SMS/MMS." - "Envoyer quand même" - "Impossible de lancer la conversation." - "%1$s (%2$s)" - "%s sélectionnée" - diff --git a/res/values-gl-rES/arrays.xml b/res/values-gl-rES/arrays.xml deleted file mode 100644 index 1e43974..0000000 --- a/res/values-gl-rES/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sen asunto" - "senasunto" - - - "Si" - "Non" - "Aceptar" - "He he" - "Grazas" - "Acepto" - "Excelente" - "Á miña maneira" - "De acordo, póñome en contacto contigo máis tarde" - ":)" - ":(" - - diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml deleted file mode 100644 index 91a6311..0000000 --- a/res/values-gl-rES/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mensaxería" - "Mensaxería" - "Seleccionar conversa" - "Configuración" - "Enviar mensaxe" - "Engadir un anexo" - "Axuda" - "Dámosche a benvida" - "Omitir" - "Seguinte >" - "Seguinte" - "Saír" - "Configuración >" - "Configuración" - "Mensaxería necesita permiso para acceder ás SMS, ao teléfono e aos contactos." - "Podes cambiar os permisos en Configuración > Aplicacións > Mensaxería > Permisos." - "Podes cambiar os permisos en Configuración, Aplicacións, Mensaxería, Permisos." - "Frecuentes" - "Todos os contactos" - "Enviar ao %s" - "Capturar fotos ou vídeo" - "Escoller imaxes deste dispositivo" - "Gravar audio" - "Escoller foto" - "O ficheiro multimedia está seleccionado." - "O ficheiro multimedia non está seleccionado." - "%d seleccionado" - "imaxe do %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imaxe" - "Gravar audio" - "Compartir" - "Agora mesmo" - "Agora" - - %d minutos - %d minuto - - - %d horas - %d hora - - - %d días - %d día - - - %d semanas - unha semana - - - %d meses - un mes - - - %d anos - un ano - - "Mensaxe de clase 0" - "Gardar" - "Este dispositivo ten pouco espazo. Mensaxería eliminará automaticamente as mensaxes antigas para liberar espazo." - "Estase esgotando o espazo de almacenamento" - "É posible que Mensaxería non envíe nin reciba mensaxes ata que haxa máis espazo dispoñible no teu dispositivo." - "Queda pouco almacenamento de SMS restante. É posible que precises eliminar mensaxes." - "Confirma o teu número de teléfono" - "Este paso único garantirá que Mensaxería entregue as túas mensaxes de grupo de forma adecuada." - "Número de teléfono" - "Eliminar todas as mensaxes con ficheiros multimedia" - "Eliminar mensaxes anteriores a %s" - "Eliminar automaticamente mensaxes anteriores a %s" - "Ignorar" - "Queres eliminar todas as mensaxes con ficheiros multimedia?" - "Queres eliminar as mensaxes anteriores a %s?" - "Queres eliminar mensaxes anteriores a %s e activar a eliminación automática?" - "%s dixo" - "Dixeches" - "Mensaxe de %s" - "Enviaches unha mensaxe" - "Enviando…" - "Non se enviou. Toca para tentalo de novo." - "Non se enviou. Tentando de novo…" - "Reenviar ou eliminar" - "Realiza unha chamada de voz aos servizos de emerxencia. Non se puido entregar a túa mensaxe de texto neste momento." - "Erro" - "Nova mensaxe MMS para descargar" - "Nova mensaxe MMS" - "Non se puido descargar" - "Tocar para tentalo de novo" - "Tocar para descargar" - "Descargar ou eliminar" - "Descargando..." - "A mensaxe caducou ou non está dispoñible" - "tamaño: %1$s, caducidade: %2$s" - "Non se pode enviar. O destinatario non é válido." - "Servizo non activado na rede" - "Non se puido enviar debido a un problema de rede" - "A mensaxe caducou ou non está dispoñible" - "(Sen asunto)" - "Remitente descoñecido" - "Entregado" - "Non se puido descargar a mensaxe %1$s de %2$s." - "Non se puido completar a operación da base de datos debido a que queda pouca memoria" - "Mensaxe non enviada" - "Hai mensaxes non enviadas en Mensaxería" - - %d mensaxes en %d conversas - %d mensaxes nunha conversa - - "Non se descargou a mensaxe" - "Algunhas mensaxes non se descargaron en Mensaxería" - - %d mensaxes en %d conversas - %d mensaxes nunha conversa - - "Non se enviou a mensaxe ao %1$s" - "Realiza unha chamada de voz aos servizos de emerxencia. Non se puido entregar a túa mensaxe de texto ao %1$s neste momento." - - %d mensaxes novas - Mensaxe nova - - "Iniciar" - "A cámara non está dispoñible" - "A cámara non está dispoñible" - "A captura de vídeo non está dispoñible" - "Non se pode gardar o ficheiro multimedia" - "Non se pode sacar a foto" - "Atrás" - "Arquivadas" - "Eliminar" - "Arquivar" - "Cancelar o arquivado" - "Desactivar notificacións" - "Activar notificacións" - "Engadir contacto" - "Descargar" - "Enviar" - "Eliminar" - "Queres eliminar esta mensaxe?" - "Esta acción non se pode desfacer." - "Eliminar" - - Queres eliminar estas conversas? - Queres eliminar esta conversa? - - "Eliminar" - "Cancelar" - "Para" - "Seleccionar varias imaxes" - "Confirmar selección" - "+%d" - "Non se pode gravar o audio. Téntao de novo." - "Non se pode reproducir o audio. Téntao de novo." - "Non se puido gardar o audio. Téntao de novo." - "Mantén premido" - ", " - " " - ": " - " " - "Imaxe" - "Clip de audio" - "Vídeo" - "Tarxeta de contacto" - "Descargar" - "Responder mediante SMS" - "Resposta MMS" - "Responde" - - %d participantes - %d participante - - "Eu" - "Contacto bloqueado e arquivado" - "Contacto desbloqueado e sen arquivar" - "%d arquivada/s" - "%d non arquivada/s" - "Notificacións desactivadas" - "Notificacións activadas" - "Todo listo. Toca Enviar de novo." - "Mensaxería estableceuse correctamente como a aplicación de SMS predeterminada." - - Descartar anexos - Descartar anexo - - "Anexo de audio" - "Reproducir anexo de audio" - "Pausa" - "Mensaxe de %s: %s." - "Erro ao recibir a mensaxe de %s: %s. Hora: %s" - "Mensaxe de %s: %s. Hora: %s" - "Non se enviou a mensaxe a %s: %s. Hora: %s" - "Enviando mensaxe a %s: %sE. Hora: %s" - "Erro ao enviar a mensaxe a %s: %s. Hora: %s" - "Mensaxe para %s: %s. Hora: %s" - "Erro ao recibir a mensaxe de %s: %s. Hora: %s. %s" - "Mensaxe de %s: %s. Hora: %s. %s" - "Non se enviou a mensaxe a %s: %s. Hora: %s" - "Enviando mensaxe a %s: %sE. Hora: %s" - "Erro ao enviar a mensaxe a %s: %s. Hora: %s" - "Mensaxe para %s: %s. Hora: %s" - "Erro na mensaxe. Toca para tentalo de novo." - "Conversa con %s" - "Borrar asunto" - "Capturar vídeo" - "Capturar unha imaxe estática" - "Sacar foto" - "Comezar a gravar vídeo" - "Cambiar á cámara de pantalla completa" - "Cambiar entre a cámara dianteira e traseira" - "Deter a gravación e anexar vídeo" - "Deter a gravación de vídeo" - "Fotos de Mensaxería" - - Gardáronse %d fotos no álbum \"%s\" - Gardouse %d foto no álbum \"%s\" - - - Gardáronse %d vídeos no álbum \"%s\" - Gardouse %d vídeo no álbum \"%s\" - - - Gardáronse %d anexos no álbum \"%s\" - Gardouse %d anexo no álbum \"%s\" - - - Gardáronse %d anexos en \"Descargas\" - Gardouse %d anexo en \"Descargas\" - - - Gardáronse %d anexos - Gardouse %d anexo - - - Non se puideron gardar %d anexos - Non se puido gardar %d anexo - - "Anexo de MMS gardado" - "Configuración" - "Arquivadas" - "Pechar" - "MMS" - "Avanzado" - "Depuración" - "Notificacións" - "Son" - "Silencio" - "Vibrar" - "Bloqueado" - "Informes de entrega de SMS" - "Solicita un informe de entrega cando envías un SMS" - "Recuperación automática" - "Recupera mensaxes MMS automaticamente" - "Recuperación automática en itinerancia" - "Recuperación automática de MMS en itinerancia" - "Mensaxería en grupo" - "Utiliza MMS para enviar unha soa mensaxe cando hai varios destinatarios" - "Aplicación de SMS predeterminada" - "Aplicación de SMS predeterminada" - - - "O teu número de teléfono" - "Descoñecido" - "Sons de mensaxes saíntes" - "Pasar SMS" - "Pasa os datos recibidos de SMS sen procesar a un ficheiro de almacenamento externo" - "Pasar MMS" - "Pasa os datos recibidos de MMS sen procesar a un ficheiro de almacenamento externo" - "Alertas sen fíos" - "Opcións de mensaxes" - "Copiar texto" - "Ver detalles" - "Eliminar" - "Reenviar" - "Detalles da mensaxe" - "Tipo: " - "Mensaxe de texto" - "Mensaxe multimedia" - "De: " - "Para: " - "Enviada: " - "Recibida: " - "Asunto: " - "Tamaño: " - "Prioridade: " - "SIM: " - "Alta" - "Normal" - "Baixa" - "SIM %s" - "Enderezo do remitente oculto" - "Non é posible enviar a mensaxe ao cargar anexos." - "Non se pode cargar o anexo. Téntao de novo." - "A rede non está lista. Téntao de novo." - "Eliminar texto" - "Cambiar entre introducir texto e números" - "Engadir máis participantes" - "Confirmar participantes" - "Iniciar nova conversa" - "Selecciona este elemento" - "Reproducir vídeo" - "Persoas e opcións" - "Depurar" - "Persoas e opcións" - "Xeral" - "Persoas que participan nesta conversa" - "Facer unha chamada" - "Enviar mensaxe" - "Envía a mensaxe<br/><small>desde %s</small>" - - Enviar fotos - Enviar foto - - - Enviar gravacións de audio - Enviar gravación de audio - - - Enviar vídeos - Enviar vídeo - - - Enviar tarxetas de contacto - Enviar tarxeta de contacto - - - Enviar anexos - Enviar anexo - - - %d anexos listos para enviar - Un anexo listo para enviar - - "Dános a túa opinión" - "Ver en Google Play Store" - "Información da versión" - "Versión %1$s" - "Licenzas de código aberto" - "Notificacións" - "Acadouse o límite para anexos" - "Non se puido cargar o anexo." - "Queres engadir este número aos teus contactos?" - "Engadir contacto" - "Asunto" - "Asunto: " - "%s%s" - "Cargando tarxeta de contacto" - "Non se puido cargar a tarxeta de contacto" - "Ver tarxeta de contacto" - - %d contactos - %d contacto - - "Tarxetas de contacto" - "Aniversario" - "Notas" - "Reenviar mensaxe" - "Responder" - "+1" - "+%d" - "SMS desactivadas" - "Establece Mensaxería como a aplicación de SMS predeterminada para enviar mensaxes" - "Establece Mensaxería como a aplicación de SMS predeterminada" - "Cambiar" - "Establece Mensaxería como a aplicación de SMS predeterminada para recibir mensaxes" - "Non se seleccionou unha SIM preferida para o envío de mensaxes SMS" - "O propietario do dispositivo non permite esta aplicación." - "Aceptar" - "Hai demasiados participantes nunha conversa" - - Contactos non válidos - Contacto non válido - - "Non se puido cargar a imaxe da cámara" - "Ti: " - "%s: " - "Borrador" - "Cando inicies unha nova conversa, aparecerá aquí" - "As conversas arquivadas aparecen aquí" - "Cargando conversas..." - "Imaxe" - "Clip de audio" - "Vídeo" - "Tarxeta de contacto" - "MMS" - "Desfacer" - "Tentar de novo" - "Introduce o nome dun contacto ou o número de teléfono para iniciar unha mensaxe nova" - "Bloquear" - "Bloquear %s" - "Desbloquear %s" - "Queres bloquear %s?" - "Seguirás recibindo mensaxes deste número, pero xa non se notificarán. Arquivarase esta conversa." - "Contactos bloqueados" - "DESBLOQUEAR" - "Contactos bloqueados" - "Escolle unha imaxe da biblioteca de documentos" - "Enviando a mensaxe" - "Mensaxe enviada" - "Os datos móbiles están desactivados. Comproba a túa configuración." - "Non se poden enviar mensaxes en modo avión" - "Erro ao enviar a mensaxe" - "Mensaxe descargada" - "Os datos móbiles están desactivados. Comproba a túa configuración." - "Non se poden descargar mensaxes no modo avión" - "Non se puido descargar a mensaxe" - "Cero" - "Un" - "Dous" - "Tres" - "Catro" - "Cinco" - "Seis" - "Sete" - "Oito" - "Nove" - "Non se pode enviar a mensaxe con %1$s. Erro: %2$d" - "Non se pode enviar a mensaxe cun operador descoñecido. Erro: %1$d" - "Rv: %s" - "Mensaxe non enviada: servizo non activado na rede" - "Mensaxe non enviada: enderezo de destino non válido" - "Mensaxe non enviada: mensaxe non válida" - "Mensaxe non enviada: contido non compatible" - "Mensaxe non enviada: mensaxe non compatible" - "Mensaxe non enviada: demasiado longa" - "Mensaxe nova" - "Ver" - "Imaxe" - "Non se puido encontrar unha aplicación adecuada" - "Elimina o destinatario" - "Mensaxe nova" - "Cancelar" - "Editar punto de acceso" - "Sen configurar" - "Nome" - "APN" - "MMSC" - "Proxy MMS" - "Porto de MMS" - "MCC" - "MNC" - "Tipo de APN" - "Eliminar APN" - "Novo APN" - "Gardar" - "Descartar" - "O nome do campo non pode estar baleiro." - "O APN non pode estar baleiro." - "O campo MCC debe conter 3 díxitos." - "O campo MNC debe conter 2 ou 3 díxitos." - "Restaurando a configuración de APN predeterminada." - "Restablecer aos valores predeterminados" - "Completouse o restablecemento da configuración de APN predeterminada." - "Sen título" - "Nomes do punto de acceso" - "APN" - "Novo APN" - "A configuración do nome do punto de acceso non está dispoñible para este usuario" - "Copiar no portapapeis?" - "Copiar" - "en %s" - "Xeral" - "Avanzada" - "Configuración xeral" - "Configuración avanzada" - "SIM \"%s\"" - "Enviar mensaxes SMS individuais a todos os destinatarios. Só ti recibirás as respostas" - "Enviar unha mensaxe MMS simple a todos os destinatarios" - "Número descoñecido" - "Mensaxe nova" - "Mensaxe nova." - "Selector de SIM" - "Seleccionouse %1$s, selector da SIM" - "Editar asunto" - "Seleccionar a SIM ou editar o asunto" - "Tocar e manter premido para gravar audio" - "Iniciar nova conversa" - "Mensaxería" - "Lista de Mensaxería" - "Mensaxería" - "Mensaxe nova" - "Lista de conversas" - "Cargando conversas" - "Cargando mensaxes" - "Ver máis conversas" - "Ver máis mensaxes" - "Conversa eliminada" - "Eliminouse a conversa. Toca para mostrar unha conversa de Mensaxería diferente" - "Bloqueado" - "Desbloqueado" - "Queda pouco espazo de almacenamento. É posible que se perdan algúns datos." - "Seleccionar anexos" - "Confirmar selección" - "%d seleccionado/s" - "Elimina un ou varios anexos e téntao de novo." - "Podes probar a enviar a túa mensaxe, pero é posible que non se entregue a menos que elimines un ou varios anexos." - "Só podes enviar un vídeo por mensaxe. Elimina os vídeos adicionais e téntao de novo." - "Mensaxería non puido cargar o anexo." - "Enviar igualmente" - "Non se puido iniciar a conversa" - "%1$s (%2$s)" - "Seleccionouse %s" - diff --git a/res/values-gu-rIN/arrays.xml b/res/values-gu-rIN/arrays.xml deleted file mode 100644 index 89975e0..0000000 --- a/res/values-gu-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "કોઇ વિષય નહીં" - "વિષય નથી" - - - "હા" - "નહીં" - "ઑકે" - "હીહી" - "આભાર" - "હું સહમત છું" - "સરસ" - "મારી અનુકૂળતાએ" - "ઑકે, હું તમને પછી મળું છું" - ":)" - ":(" - - diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml deleted file mode 100644 index 866823c..0000000 --- a/res/values-gu-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "મેસેજિંગ" - "મેસેજિંગ" - "વાર્તાલાપ પસંદ કરો" - "સેટિંગ્સ" - "સંદેશ મોકલો" - "એક જોડાણ ઉમેરો" - "સહાય" - "સ્વાગત છે" - "છોડી દો" - "આગલું >" - "આગલું" - "બહાર નીકળો" - "સેટિંગ્સ >" - "સેટિંગ્સ" - "મેસેજિંગને SMS, ફોન અને સંપર્કોની પરવાનગીની જરૂર છે." - "તમે સેટિંગ્સ > એપ્લિકેશન્સ > મેસેજિંગ > પરવાનગીઓમાં પરવાનગીઓ બદલી શકો છો." - "તમે સેટિંગ્સ, એપ્લિકેશન્સ, મેસેજિંગ, પરવાનગીઓમાં પરવાનગીઓ બદલી શકો છો." - "વારંવાર" - "તમામ સંપર્કો" - "%s પર મોકલો" - "ચિત્રો અથવા વિડિઓ કેપ્ચર કરો" - "આ ઉપકરણથી છબીઓ પસંદ કરો" - "ઑડિઓ રેકોર્ડ કરો" - "ફોટો પસંદ કરો" - "મીડિયા પસંદ થયેલ છે." - "મીડિયા પસંદ કરેલ નથી." - "%d પસંદ કર્યાં" - "છબી %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "છબી" - "ઑડિઓ રેકોર્ડ કરો" - "શેર કરો" - "હમણાં જ" - "હમણાં" - - %d મિનિટ - %d મિનિટ - - - %d કલાક - %d કલાક - - - %d દિવસ - %d દિવસ - - - %d અઠવાડિયા - %d અઠવાડિયા - - - %d મહિના - %d મહિના - - - %d વર્ષ - %d વર્ષ - - "વર્ગ 0 સંદેશ" - "સાચવો" - "ઉપકરણ પર જગ્યા ઓછી છે. મેસેજિંગ જગ્યા ખાલી કરવા માટે જૂના સંદેશા આપમેળે કાઢી નાખશે." - "સ્ટોરેજ સ્થાન સમાપ્ત થયું" - "તમારા ઉપકરણ પર વધુ સ્થાન ઉપલબ્ધ ન થાય ત્યાં સુધી મેસેજિંગ સંદેશા મોકલી અથવા પ્રાપ્ત કરી શકશે નહીં." - "ઓછો SMS સંગ્રહ. તમારે કેટલાક સંદેશા કાઢી નાખવાની જરૂર હોઈ શકે છે." - "તમારા ફોન નંબરની પુષ્ટિ કરો" - "મેસેજિંગ તમારા જૂથ સંદેશાનું યોગ્ય રીતે વિતરણ કરે તે બાબતની આ એક-વારનું પગલું ખાતરી કરશે." - "ફોન નંબર" - "મીડિયા સાથેના તમામ સંદેશા કાઢી નાખો" - "%s કરતાં જૂનાં સંદેશા કાઢી નાખો" - "%s કરતા જૂના સંદેશા આપમેળે કાઢી નાખો" - "અવગણો" - "મીડિયા સાથેના તમામ સંદેશા કાઢી નાખીએ?" - "%s કરતા જૂના સંદેશા કાઢી નાખીએ?" - "%s કરતા જૂનાં સંદેશા કાઢી નાખીએ અને આપમેળે કાઢી નાખવું ચાલુ કરીએ?" - "%s એ કહ્યું" - "તમે કહ્યું" - "%s તરફથી સંદેશ" - "તમે એક સંદેશ મોકલ્યો" - "મોકલી રહ્યાં છે…" - "મોકલાયો નથી. ફરીથી પ્રયાસ કરવા માટે ટચ કરો." - "મોકલાયું નથી. ફરીથી પ્રયાસ કરી રહ્યું છે…" - "ફરીથી મોકલો અથવા કાઢી નાખો" - "કૃપા કરીને કટોકટીની સેવાઓ માટે એક વૉઇસ કૉલ કરો. તમારો ટેક્સ્ટ સંદેશ આ સમયે વિતરિત કરી શકાયો નથી." - "નિષ્ફળ થયું" - "ડાઉનલોડ કરવા માટેનો નવો MMS સંદેશ" - "નવો MMS સંદેશ" - "ડાઉનલોડ કરી શક્યાં નથી" - "ફરીથી પ્રયત્ન કરવા ટચ કરો" - "ડાઉનલોડ કરવા ટચ કરો" - "ડાઉનલોડ કરો અથવા કાઢી નાખો" - "ડાઉનલોડ કરી રહ્યાં છે..." - "સંદેશની સમયસીમા સમાપ્ત થઈ અથવા ઉપલબ્ધ નથી" - "કદ: %1$s, સમય સમાપ્તિ: %2$s" - "મોકલી શકતા નથી. પ્રાપ્તકર્તા માન્ય નથી." - "નેટવર્ક પર સેવા સક્રિય કરી નથી" - "નેટવર્ક સમસ્યાને કારણે મોકલી શકાયો નથી" - "સંદેશની સમયસીમા સમાપ્ત થઈ અથવા ઉપલબ્ધ નથી" - "(કોઇ વિષય નથી)" - "અજાણ્યો પ્રેષક" - "વિતરિત" - "%2$s તરફનાં %1$s સંદેશા ડાઉનલોડ કરી શકાયા નથી." - "ઓછી મેમરીને કારણે ડેટાબેસ ક્રિયા પૂર્ણ કરી શકાઈ નથી" - "સંદેશ મોકલ્યો નથી" - "મેસેજિંગમાં કેટલાક સંદેશા મોકલ્યાં નથી" - - %d વાર્તાલાપમાં %d સંદેશા - %d વાર્તાલાપમાં %d સંદેશા - - "સંદેશ ડાઉનલોડ થયેલ નથી" - "મેસેજિંગમાં કેટલાક સંદેશા ડાઉનલોડ થયાં નથી" - - %d વાર્તાલાપમાં %d સંદેશા - %d વાર્તાલાપમાં %d સંદેશા - - "%1$s ને સંદેશ મોકલાયો નથી" - "કટોકટી સેવાઓને કૃપા કરીને એક વૉઇસ કૉલ કરો. %1$s ને તમારો ટેક્સ્ટ સંદેશ આ સમયે વિતરિત કરી શકાયો નથી." - - %d નવા સંદેશા - %d નવા સંદેશા - - "પ્રારંભ કરો" - "કૅમેરો ઉપલબ્ધ નથી" - "કૅમેરો ઉપલબ્ધ નથી" - "વિડિઓ કેપ્ચર ઉપલબ્ધ નથી" - "મીડિયા સાચવી શકતા નથી" - "ચિત્ર લઈ શકતા નથી" - "પાછળ" - "આર્કાઇવ કરેલા" - "કાઢી નાખો" - "આર્કાઇવ" - "આર્કાઇવ.કરેલ નથી" - "સૂચનાઓ બંધ કરો" - "સૂચનાઓ ચાલુ કરો" - "સંપર્ક ઉમેરો" - "ડાઉનલોડ કરો" - "મોકલો" - "કાઢી નાખો" - "આ સંદેશ કાઢી નાખીએ?" - "આ ક્રિયા પૂર્વવત્ કરી શકાતી નથી." - "કાઢી નાખો" - - આ વાર્તાલાપ કાઢી નાખીએ? - આ વાર્તાલાપ કાઢી નાખીએ? - - "કાઢી નાખો" - "રદ કરો" - "પ્રતિ" - "બહુવિધ છબીઓ પસંદ કરો" - "પસંદગીની પુષ્ટિ કરો" - "+%d" - "ઑડિઓ રેકોર્ડ કરી શકાતો નથી. ફરીથી પ્રયાસ કરો." - "ઑડિઓ ચલાવી શકાતો નથી. ફરીથી પ્રયાસ કરો." - "ઑડિઓ સાચવી શકાયો નથી. ફરીથી પ્રયાસ કરો." - "ટચ કરો અને પકડો" - ", " - " " - ": " - " " - "ચિત્ર" - "ઑડિઓ ક્લિપ" - "વિડિઓ" - "સંપર્ક કાર્ડ" - "ડાઉનલોડ કરો" - "SMS દ્વારા જવાબ આપો" - "MMS દ્વારા જવાબ આપો" - "જવાબ આપો" - - %d સહભાગીઓ - %d સહભાગીઓ - - "મારા" - "સંપર્ક અવરોધિત કર્યો અને આર્કાઇવ કર્યો" - "સંપર્ક અનાવરોધિત અને અનઆર્કાઇવ કર્યો" - "%d આર્કાઇવ કર્યાં" - "%d અનઆર્કાઇવ કર્યા" - "સૂચનાઓ બંધ કરી" - "સૂચનાઓ ચાલુ કરી" - "બધું સેટ થયું. મોકલો ને ફરીથી ટચ કરો." - "ડિફોલ્ટ SMS એપ્લિકેશન તરીકે મેસેજિંગ સફળતાપૂર્વક સેટ થયું." - - જોડાણો નિકાળો - જોડાણો નિકાળો - - "ઑડિઓ જોડાણ" - "ઑડિઓ જોડાણ ચલાવો" - "થોભો" - "%s તરફથી સંદેશ: %s." - "%s તરફથી સંદેશ નિષ્ફળ: %s. સમય: %s." - "%s તરફથી સંદેશ: %s. સમય: %s." - "%s ને વણમોકલાયેલ સંદેશ: %s. સમય: %s." - "%s ને સંદેશ મોકલી રહ્યું છે: %s. સમય: %s." - "%s ને સંદેશ મોકલવું નિષ્ફળ: %s. સમય: %s." - "%s ને સંદેશ: %s. સમય: %s." - "%s તરફથી સંદેશ નિષ્ફળ: %s. સમય: %s. %s." - "%s તરફથી સંદેશ: %s. સમય: %s. %s." - "%s ને વણમોકલાયેલ સંદેશ: %s. સમય: %s." - "%s ને સંદેશ મોકલી રહ્યું છે: %s. સમય: %s." - "%s ને સંદેશ મોકલવું નિષ્ફળ: %s. સમય: %s." - "%s ને સંદેશ: %s. સમય: %s." - "નિષ્ફળ સંદેશ. ફરી પ્રયાસ કરવા ટચ કરો." - "%s સાથે વાર્તાલાપ" - "વિષય કાઢી નાખો" - "વિડિઓ કેપ્ચર કરો" - "એક સ્થિર છબી કેપ્ચર કરો" - "ફોટો લો" - "વિડિઓ રેકોર્ડિંગ પ્રારંભ કરો" - "પૂર્ણ સ્ક્રીન કૅમેરા પર સ્વિચ કરો" - "આગળનાં અને પાછળનાં કૅમેરા વચ્ચે સ્વિચ કરો" - "રેકોર્ડિંગ બંધ કરો અને વિડિઓ જોડો" - "વિડિઓ રેકોર્ડિંગ બંધ કરો" - "મેસેજિંગ ફોટા" - - \"%s\" આલ્બમમાં %d ફોટા સાચવ્યાં - \"%s\" આલ્બમમાં %d ફોટા સાચવ્યાં - - - \"%s\" આલ્બમમાં %d વિડિઓઝ સાચવી - \"%s\" આલ્બમમાં %d વિડિઓઝ સાચવી - - - \"%s\" આલ્બમમાં %d જોડાણો સાચવ્યાં - \"%s\" આલ્બમમાં %d જોડાણો સાચવ્યાં - - - \"ડાઉનલોડ્સ\"માં %d જોડાણો સાચવ્યાં - \"ડાઉનલોડ્સ\"માં %d જોડાણો સાચવ્યાં - - - %d જોડાણ સાચવ્યાં - %d જોડાણ સાચવ્યાં - - - %d જોડાણ સાચવી શક્યાં નથી - %d જોડાણ સાચવી શક્યાં નથી - - "સાચવેલ MMS જોડાણ" - "સેટિંગ્સ" - "આર્કાઇવ કરેલા" - "બંધ કરો" - "MMS" - "વિગતવાર" - "ડીબગ" - "સૂચનાઓ" - "ધ્વનિ" - "શાંત" - "વાઇબ્રેટ" - "અવરોધિત" - "SMS વિતરણ રિપોર્ટ્સ" - "તમે મોકલેલા દરેક SMS માટે વિતરણ રિપોર્ટની વિનંતી કરો" - "સ્વતઃ-પુનર્પ્રાપ્ત કરો" - "MMS સંદેશા સ્વતઃ-પુનર્પ્રાપ્ત કરો" - "રોમિંગ સ્વતઃ-પુનર્પ્રાપ્ત કરો" - "રોમિંગ પર હોય ત્યારે MMS આપમેળે પુનર્પ્રાપ્ત કરો" - "જૂથ મેસેજિંગ" - "બહુવિધ પ્રાપ્તકર્તાઓ હોય ત્યારે એક જ સંદેશ મોકલવા માટે MMS નો ઉપયોગ કરો" - "ડિફોલ્ટ SMS એપ્લિકેશન" - "ડિફોલ્ટ SMS એપ્લિકેશન" - - - "તમારો ફોન નંબર" - "અજાણ્યો" - "આઉટગોઇંગ સંદેશ ધ્વનિઓ" - "SMS ડમ્પ કરો" - "પ્રાપ્ત થયેલ SMS અપૂર્ણ ડેટાને બાહ્ય સંગ્રહ ફાઇલમાં ડમ્પ કરો" - "MMS ડમ્પ કરો" - "પ્રાપ્ત થયેલ MMS અપૂર્ણ ડેટાને બાહ્ય સંગ્રહ ફાઇલમાં ડમ્પ કરો" - "વાયરલેસ ચેતવણીઓ" - "સંદેશ વિકલ્પો" - "ટેક્સ્ટ કૉપિ કરો" - "વિગતો જુઓ" - "કાઢી નાખો" - "ફોરવર્ડ કરો" - "સંદેશ વિગતો" - "પ્રકાર: " - "ટેક્સ્ટ સંદેશ" - "મલ્ટિમીડિયા સંદેશ" - "અહીંથી: " - "પ્રતિ: " - "મોકલ્યું: " - "પ્રાપ્ત કર્યુ: " - "વિષય: " - "કદ: " - "પ્રાધાન્યતા: " - "SIM: " - "ઉચ્ચ" - "સામાન્ય" - "નિમ્ન" - "SIM %s" - "પ્રેષક સરનામું છુપાયેલ છે" - "જોડાણો લોડ કરતી વખતે સંદેશ મોકલી શકાતો નથી." - "જોડાણ લોડ કરી શકાતું નથી. ફરીથી પ્રયાસ કરો." - "નેટવર્ક તૈયાર નથી. ફરીથી પ્રયાસ કરો." - "ટેક્સ્ટ કાઢી નાખો" - "ટેક્સ્ટ અને સંખ્યાઓ દાખલ કરવા વચ્ચે સ્વિચ કરો" - "વધુ સહભાગીઓ ઉમેરો" - "સહભાગીઓની પુષ્ટિ કરો" - "નવો વાર્તાલાપ પ્રારંભ કરો" - "આ આઇટમ પસંદ કરો" - "વિડિઓ ચલાવો" - "લોકો અને વિકલ્પો" - "ડીબગ" - "લોકો અને વિકલ્પો" - "સામાન્ય" - "આ વાર્તાલાપમાંના લોકો" - "એક કૉલ કરો" - "સંદેશ મોકલો" - "%s</small> દ્વારા <br/><small>સંદેશ મોકલો" - - ફોટા મોકલો - ફોટા મોકલો - - - ઑડિઓ મોકલો - ઑડિઓ મોકલો - - - વિડિઓઝ મોકલો - વિડિઓઝ મોકલો - - - સંપર્ક કાર્ડ્સ મોકલો - સંપર્ક કાર્ડ્સ મોકલો - - - જોડાણ મોકલો - જોડાણ મોકલો - - - મોકલવા માટે %d જોડાણ તૈયાર છે - મોકલવા માટે %d જોડાણ તૈયાર છે - - "પ્રતિસાદ મોકલો" - "Google Play Store માં જુઓ" - "સંસ્કરણ માહિતી" - "સંસ્કરણ %1$s" - "ઓપન સોર્સ લાઇસેંસેસ" - "સૂચનાઓ" - "જોડાણની મર્યાદા સુધી પહોંચ્યાં" - "જોડાણ લોડ કરવામાં નિષ્ફળ." - "સંપર્કોમાં ઉમેરીએ?" - "સંપર્ક ઉમેરો" - "વિષય" - "વિષય: " - "%s%s" - "સંપર્ક કાર્ડ લોડ કરી રહ્યું છે" - "સંપર્ક કાર્ડ લોડ કરી શકાયું નથી" - "સંપર્ક કાર્ડ જુઓ" - - %d સંપર્કો - %d સંપર્કો - - "સંપર્ક કાર્ડ્સ" - "જન્મદિવસ" - "નોંધો" - "સંદેશ ફોરવર્ડ કરો" - "જવાબ આપો" - "પસંદ" - "+%d" - "SMS અક્ષમ કરેલ છે" - "મોકલવા માટે, મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો" - "મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો" - "બદલો" - "સંદેશા પ્રાપ્ત કરવા માટે, મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો" - "SMS સંદેશા મોકલવા માટે પસંદીદા SIM પસંદ કરેલ નથી" - "આ એપ્લિકેશનને ઉપકરણ માલિક દ્વારા મંજૂરી નથી." - "ઑકે" - "વાર્તાલાપમાં ઘણાં બધા સહભાગીઓ છે" - - અમાન્ય સંપર્કો - અમાન્ય સંપર્કો - - "કૅમેરા છબી લોડ કરી શકાઈ નથી" - "તમે: " - "%s: " - "ડ્રાફ્ટ" - "એકવાર તમે એક નવો વાર્તાલાપ પ્રારંભ કરી લો તે પછી, તમે તેને અહીં સૂચિબદ્ધ જોશો" - "આર્કાઇવ કરેલ વાર્તાલાપ અહીં દેખાશે" - "વાર્તાલાપ લોડ કરી રહ્યાં છે…" - "ચિત્ર" - "ઑડિઓ ક્લિપ" - "વિડિઓ" - "સંપર્ક કાર્ડ" - "MMS" - "પૂર્વવત્ કરો" - "ફરી પ્રયાસ કરો" - "એક નવો સંદેશ પ્રારંભ કરવા માટે સંપર્ક નામ અથવા ફોન નંબર દાખલ કરો" - "અવરોધિત કરો" - "%s ને અવરોધિત કરો" - "%s ને અનાવરોધિત કરો" - "%s ને અવરોધિત કરીએ?" - "તમે આ નંબરથી સંદેશા પ્રાપ્ત કરવાનું ચાલુ રાખશો પરંતુ હવેથી સૂચિત કરવામાં આવશે નહીં. આ વાર્તાલાપ આર્કાઇવ કરવામાં આવશે." - "અવરોધિત કરેલા સંપર્કો" - "અનાવરોધિત કરો" - "અવરોધિત કરેલા સંપર્કો" - "દસ્તાવેજ લાઇબ્રેરીમાંથી છબી પસંદ કરો" - "સંદેશ મોકલી રહ્યાં છીએ" - "સંદેશ મોકલ્યો" - "સેલ્યુલર ડેટા બંધ છે. તમારી સેટિંગ્સ તપાસો." - "એરપ્લેન મોડમાં સંદેશા મોકલી શકાતા નથી" - "સંદેશ મોકલી શકાયો નથી" - "સંદેશ ડાઉનલોડ થયો" - "સેલ્યુલર ડેટા બંધ છે. તમારી સેટિંગ્સ તપાસો." - "એરપ્લેન મોડમાં સંદેશા ડાઉનલોડ કરી શકાતા નથી" - "સંદેશ ડાઉનલોડ કરી શકાયો નથી" - "શૂન્ય" - "એક" - "બે" - "ત્રણ" - "ચાર" - "પાંચ" - "છ" - "સાત" - "આઠ" - "નવ" - "%1$s સાથે સંદેશ મોકલી શકાતો નથી, ભૂલ %2$d" - "અજાણ્યાં કેરીઅર સાથે સંદેશ મોકલી શકાતો નથી, ભૂલ %1$d" - "ફોરવર્ડ: %s" - "સંદેશ મોકલાયો નથી: નેટવર્ક પર સેવા સક્રિય કરેલ નથી" - "સંદેશ મોકલાયો નથી: અમાન્ય લક્ષ્યસ્થાન સરનામું" - "સંદેશ મોકલાયો નથી: અમાન્ય સંદેશ" - "સંદેશ મોકલાયો નથી: અસમર્થિત સામગ્રી" - "સંદેશ મોકલાયો નથી: અસમર્થિત સંદેશ" - "સંદેશ મોકલ્યો નથી: ખૂબ મોટો" - "નવો સંદેશ" - "જુઓ" - "છબી" - "કોઈ યોગ્ય એપ્લિકેશન મળી શકી નથી" - "પ્રાપ્તકર્તા દૂર કરો" - "નવો સંદેશ" - "રદ કરો" - "અ‍ૅક્સેસ પોઇન્ટ સંપાદિત કરો" - "સેટ નથી" - "નામ" - "APN" - "MMSC" - "MMS પ્રોક્સી" - "MMS પોર્ટ" - "MCC" - "MNC" - "APN પ્રકાર" - "APN કાઢી નાખો" - "નવું APN" - "સાચવો" - "છોડી દો" - "નામ ફીલ્ડ ખાલી હોઈ શકતી નથી." - "APN ખાલી હોઈ શકતું નથી." - "MCC ફીલ્ડ 3 અંકોની હોવી આવશ્યક છે." - "MNC ફીલ્ડ 2 અથવા 3 અંકોની હોવી આવશ્યક છે." - "ડિફોલ્ટ APN સેટિંગ્સ પુનર્સ્થાપિત કરવી." - "ડિફોલ્ટ પર ફરીથી સેટ કરો" - "ડિફોલ્ટ APN સેટિંગ્સ ફરીથી સેટ કરો પૂર્ણ થયું." - "મથાળા વગર" - "ઍક્સેસ પોઇન્ટનું નામ" - "APNs" - "નવું APN" - "અ‍ૅક્સેસ પોઇન્ટનું નામ સેટિંગ્સ આ વપરાશકર્તા માટે ઉપલબ્ધ નથી" - "ક્લિપબોર્ડ પર કૉપિ કરીએ?" - "કૉપિ કરો" - "%s પર" - "સામાન્ય" - "વિગતવાર" - "સામાન્ય સેટિંગ્સ" - "વિગતવાર સેટિંગ્સ" - "\"%s\" SIM" - "તમામ પ્રાપ્તકર્તાઓને વ્યક્તિગત SMS સંદેશાઓ મોકલો. ફક્ત તમને જ કોઇ જવાબો મળશે" - "તમામ પ્રાપ્તકર્તાઓને એક જ MMS મોકલો" - "અજાણ્યો નંબર" - "નવો સંદેશ" - "નવો સંદેશ." - "SIM પસંદગીકર્તા" - "%1$s પસંદ કર્યા, SIM પસંદગીકર્તા" - "વિષય સંપાદિત કરો" - "SIM પસંદ કરો અથવા વિષય સંપાદિત કરો" - "ઑડિઓ રેકોર્ડ કરવા માટે ટચ કરો અને પકડો" - "નવો વાર્તાલાપ પ્રારંભ કરો" - "મેસેજિંગ" - "મેસેજિંગ સૂચિ" - "મેસેજિંગ" - "નવો સંદેશ" - "વાર્તાલાપની સૂચિ" - "વાર્તાલાપ લોડ કરી રહ્યાં છે" - "સંદેશા લોડ કરી રહ્યું છે" - "વધુ વાર્તાલાપ જુઓ" - "વધુ સંદેશા જુઓ" - "વાર્તાલાપ કાઢી નખાયો" - "વાર્તાલાપ કાઢી નાખ્યો. એક ભિન્ન મેસેજિંગ વાર્તાલાપ દર્શાવવા માટે ટચ કરો" - "અવરોધિત" - "અનાવરોધિત" - "સંગ્રહ સ્થાન ઓછું છે. કેટલોક ડેટા ખોવાઈ શકે છે." - "જોડાણો પસંદ કરો" - "પસંદગીની પુષ્ટિ કરો" - "%d પસંદ કર્યાં" - "કૃપા કરીને એક અથવા વધુ જોડાણો દૂર કરો અને ફરીથી પ્રયાસ કરો." - "તમે તમારો સંદેશ મોકલવાનો પ્રયાસ કરી શકો છો, પરંતુ તમે એક અથવા વધુ જોડાણો દૂર નહીં કરો ત્યાં સુધી તે વિતરિત કરવામાં આવી શકશે નહીં." - "તમે પ્રતિ સંદેશ ફક્ત એક વિડિઓ મોકલી શકો છો. કૃપા કરીને અતિરિક્ત વિડિઓઝ દૂર કરો અને ફરી પ્રયાસ કરો." - "જોડાણ લોડ કરવામાં મેસેજિંગ નિષ્ફળ." - "કોઈપણ રીતે મોકલો" - "વાર્તાલાપ પ્રારંભ કરી શકાયો નથી" - "%1$s (%2$s)" - "%s પસંદ કરી" - diff --git a/res/values-hi/arrays.xml b/res/values-hi/arrays.xml deleted file mode 100644 index abdcedf..0000000 --- a/res/values-hi/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "कोई विषय नहीं" - "कोई विषय नहीं" - - - "हां" - "नहीं" - "ठीक" - "हेहे" - "धन्यवाद" - "मैं सहमत हूं" - "बढ़िया" - "मेरे रास्ते में रहने के दौरान" - "ठीक है, मैं आपसे बाद में संपर्क करूंगा/करूंगी" - ":)" - ":(" - - diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml deleted file mode 100644 index c281680..0000000 --- a/res/values-hi/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "संदेश सेवा" - "संदेश सेवा" - "वार्तालाप चुनें" - "सेटिंग" - "संदेश भेजें" - "कोई अटैचमेंट जोड़ें" - "सहायता" - "स्वागत है" - "अभी नहीं" - "अगला >" - "अगला" - "बाहर निकलें" - "सेटिंग >" - "सेटिंग" - "संदेश सेवा को SMS, फ़ोन और संपर्कों के लिए अनुमति की आवश्यकता होती है." - "आप अनुमतियों को सेटिंग > ऐप्स > संदेश सेवा > अनुमतियां में बदल सकते हैं." - "आप अनुमतियों को सेटिंग, ऐप्‍स, संदेश सेवा, अनुमतियां में बदल सकते हैं." - "अक्सर" - "सभी संपर्क" - "%s पर भेजें" - "चित्र या वीडियो कैप्चर करें" - "इस डिवाइस के चित्र चुनें" - "ऑडियो रिकॉर्ड करें" - "फ़ोटो चुनें" - "मीडिया को चुना है." - "मीडिया को नहीं चुना है." - "%d चुने गए" - "चित्र %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "चित्र" - "ऑडियो रिकॉर्ड करें" - "साझा करें" - "अभी-अभी" - "अभी" - - %d मिनट - %d मिनट - - - %d घंटे - %d घंटे - - - %d दिन - %d दिन - - - %d सप्‍ताह - %d सप्‍ताह - - - %d महीने - %d महीने - - - %d वर्ष - %d वर्ष - - "वर्ग 0 संदेश" - "जोड़ें" - "डिवाइस में स्थान कम है. स्थान खाली करने के लिए संदेश सेवा पुराने संदेशों को अपने आप हटा देगी." - "मेमोरी स्‍थान समाप्‍त हो रहा है" - "हो सकता है कि आपके डिवाइस पर अधिक स्‍थान उपलब्‍ध होने तक संदेश सेवा संदेशों को ना भेजे या प्राप्‍त ना करे." - "कम SMS मेमोरी. आपको संदेशों को हटाने की आवश्‍यकता हो सकती है." - "अपने फ़ोन नंबर की पुष्टि करें" - "यह एक-बार का चरण यह सुनिश्चित करेगा कि संदेश सेवा आपके समूह संदेशों को ठीक से वितरित करेगी." - "फ़ोन नंबर" - "मीडिया वाले सभी संदेशों को हटाएं" - "%s से पुराने संदेशों को हटाएं" - "%s से पुराने संदेशों को स्‍वत: हटाएं" - "अनदेखा करें" - "मीडिया वाले सभी संदेशों को हटाएं?" - "%s से पुराने संदेशों को हटाएं?" - "%s से पुराने संदेश हटाएं और स्‍वत: हटाना चालू करें?" - "प्रेषक: %s" - "आपने कहा" - "%s की ओर से संदेश" - "आपने एक संदेश भेजा" - "भेजा जा रहा है..." - "नहीं भेजा गया. पुन: प्रयास करने के लिए स्‍पर्श करें." - "नहीं भेजा गया. पुन: प्रयास किया जा रहा है…" - "फिर से भेजें या हटाएं" - "कृपया आपातकालीन सेवाओं को वॉइस कॉल करें. आपका लेख संदेश इस समय वितरित नहीं किया जा सकता." - "विफल रहा" - "डाउनलोड करने के लिए नया MMS संदेश" - "नया MMS संदेश" - "डाउनलोड नहीं किया जा सका" - "पुन: प्रयास करने के लिए स्पर्श करें" - "डाउनलोड करने के लिए स्‍पर्श करें" - "डाउनलोड करें या हटाएं" - "डाउनलोड किया जा रहा है..." - "संदेश की समय सीमा समाप्त हो गई या वह उपलब्ध नहीं है" - "आकार: %1$s, समाप्‍ति: %2$s" - "भेजा नहीं जा सकता. प्राप्‍तकर्ता मान्‍य नहीं है." - "नेटवर्क पर सेवा सक्रिय नहीं है" - "नेटवर्क समस्‍या के कारण भेजा नहीं जा सका" - "संदेश की समय सीमा समाप्त हो गई या वह उपलब्ध नहीं है" - "(कोई विषय नहीं)" - "अज्ञात प्रेषक" - "डिलीवर कर दिया गया" - "%2$s से %1$s संदेश डाउनलोड नहीं किया जा सका." - "कम मेमोरी के कारण डेटाबेस कार्रवाई पूर्ण नहीं किया जा सका" - "संदेश नहीं भेजा गया" - "कुछ संदेशों को संदेश सेवा में नहीं भेजा गया है" - - %d वार्तालापों में %d संदेश - %d वार्तालापों में %d संदेश - - "संदेश डाउनलोड नहीं किया गया" - "कुछ संदेशों को संदेश सेवा में डाउनलोड नहीं किया गया है" - - %d वार्तालापों में %d संदेश - %d वार्तालापों में %d संदेश - - "%1$s को संदेश नहीं भेजा गया" - "कृपया आपातकालीन सेवाओं को वॉइस कॉल करें. %1$s को भेजा गया आपका लेख संदेश इस समय वितरित नहीं किया जा सकता." - - %d नए संदेश - %d नए संदेश - - "प्रारंभ करें" - "कैमरा उपलब्ध नहीं है" - "कैमरा उपलब्ध नहीं है" - "वीडियो कैप्‍चर उपलब्‍ध नहीं है" - "मीडिया सहेजा नहीं जा सकता" - "चित्र नहीं लिया जा सकता" - "पीछे" - "संग्रहीत" - "हटाएं" - "संग्रहीत करें" - "संग्रह से निकालें" - "नोटिफिकेशन बंद करें" - "नोटिफिकेशन चालू करें" - "संपर्क जोड़ें" - "डाउनलोड करें" - "भेजें" - "हटाएं" - "इस संदेश को हटाएं?" - "इस क्रिया को पूर्ववत नहीं किया जा सकता." - "हटाएं" - - इन वार्तालापों को हटाएं? - इन वार्तालापों को हटाएं? - - "हटाएं" - "अभी नहीं" - "प्रति" - "एकाधिक चित्र चुनें" - "चयन की दुबारा पूछें" - "+%d" - "ऑडियो रिकॉर्ड नहीं किया जा सकता. पुनः प्रयास करें." - "ऑडियो नहीं चलाया जा सकता. पुन: प्रयास करें." - "ऑडियो सहेजा नहीं जा सका. पुनः प्रयास करें." - "स्पर्श करके रखें" - ", " - " " - ": " - " " - "चित्र" - "ऑडियो क्लिप" - "वीडियो" - "संपर्क कार्ड" - "डाउनलोड करें" - "SMS के द्वारा उत्तर दें" - "MMS से उत्तर दें" - "उत्तर दें" - - %d प्रतिभागी - %d प्रतिभागी - - "मुझे" - "संपर्क अवरोधित तथा संग्रहीत किया गया" - "संपर्क को अनवरोधित किया गया और संग्रह से निकाला गया" - "%d को संग्रहीत किया गया" - "%d को संग्रह से निकाला गया" - "नोटिफ़िकेशन बंद हैं" - "नोटिफ़िकेशन चालू हैं" - "बिल्‍कुल तैयार. भेजें को पुन: स्‍पर्श करें." - "संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सफलतापूर्वक सेट कर दिया गया है." - - अटैचमेंट छोड़ें - अटैचमेंट छोड़ें - - "ऑडियो अटैचमेंट" - "ऑडियो अटैचमेंट चलाएं" - "रोकें" - "%s की ओर से संदेश: %s." - "%s का भेजने में विफल रहा संदेश: %s. समय: %s." - "%s का संदेश: %s. समय: %s." - "%s को नहीं भेजा गया संदेश: %s. समय: %s." - "%s को संदेश भेजा जा रहा है: %s. समय: %s." - "%s को भेजने में विफल रहा संदेश: %s. समय: %s." - "%s के लिए संदेश: %s. समय: %s." - "%s का विफल रहा संदेश: %s. समय: %s. %s." - "%s का संदेश: %s. समय: %s. %s." - "%s को नहीं भेजा गया संदेश: %s. समय: %s." - "%s को संदेश भेजा जा रहा है: %s. समय: %s." - "%s को भेजने में विफल रहा संदेश: %s. समय: %s." - "%s के लिए संदेश: %s. समय: %s." - "विफल रहा संदेश. पुन: प्रयास करने के लिए स्पर्श करें." - "%s के साथ बातचीत" - "विषय हटाएं" - "वीडियो कैप्चर करें" - "स्थिर चित्र कैप्चर करें" - "चित्र लें" - "वीडियो रिकॉर्ड करना प्रारंभ करें" - "पूर्ण स्क्रीन कैमरा पर स्विच करें" - "सामने और पीछे वाले कैमरे के बीच स्‍विच करें" - "रिकॉर्डिंग रोकें और वीडियो अटैच करें" - "वीडियो रिकॉर्ड करना बंद करें" - "संदेश सेवा फ़ोटो" - - %d फ़ोटो \"%s\" एल्बम में सहेजी गईं - %d फ़ोटो \"%s\" एल्बम में सहेजी गईं - - - %d वीडियो \"%s\" एल्बम में सहेजे गए - %d वीडियो \"%s\" एल्बम में सहेजे गए - - - %d अटैचमेंट \"%s\" एल्बम में सहेजे गए - %d अटैचमेंट \"%s\" एल्बम में सहेजे गए - - - %d अटैचमेंट \"डाउनलोड\" में सहेजे गए - %d अटैचमेंट \"डाउनलोड\" में सहेजे गए - - - %d अटैचमेंट सहेजे गए - %d अटैचमेंट सहेजे गए - - - %d अटैचमेंट सहेजे नहीं जा सके - %d अटैचमेंट सहेजे नहीं जा सके - - "MMS अटैचमेंट सहेजा गया" - "सेटिंग" - "संग्रहीत" - "बंद करें" - "MMS" - "अतिरिक्त सेटिंग" - "डीबग" - "नोटिफिकेशन" - "ध्वनि" - "मौन" - "वाइब्रेट" - "अवरोधित" - "SMS वितरण रिपोर्ट" - "आपके द्वारा भेजे गए प्रत्येक SMS की वितरण रिपोर्ट का अनुरोध करें" - "स्वतः पुनर्प्राप्ति" - "MMS संदेशों को अपने आप प्राप्त करें" - "रोमिंग स्वतः पुनर्प्राप्ति" - "रोमिंग के दौरान MMS अपने आप प्राप्त करें" - "समूह संदेश सेवा" - "एक से अधिक प्राप्तकर्ता होने पर एक ही संदेश भेजने के लिए MMS का उपयोग करें" - "डिफ़ॉल्ट SMS ऐप" - "डिफ़ॉल्ट SMS ऐप" - - - "आपका फ़ोन नंबर" - "अज्ञात" - "आउटगोइंग संदेश ध्वनि" - "SMS डंप करें" - "बाहरी मेमोरी फ़ाइल में प्राप्त किया गया SMS अपरिष्कृत डेटा डंप करें" - "MMS डंप करें" - "बाहरी मेमोरी फ़ाइल में प्राप्त किया गया MMS अपरिष्कृत डेटा डंप करें" - "वायरलेस अलर्ट" - "संदेश के विकल्प" - "लेख की प्रतिलिपि बनाएं" - "विवरण देखें" - "हटाएं" - "अग्रेषित करें" - "संदेश के विवरण" - "प्रकार: " - "लेख संदेश" - "मल्टीमीडिया संदेश" - "प्रेषक: " - "प्रति: " - "भेजा गया: " - "प्राप्त हुआ: " - "विषय: " - "आकार: " - "प्राथमिकता: " - "सिम: " - "उच्च" - "सामान्य" - "निम्न" - "सिम %s" - "प्रेषक का पता छिपाएं" - "अटैचमेंट लोड करते समय संदेश नहीं भेजा जा सकता." - "अटैचमेंट लोड नहीं किया जा सकता. पुन: प्रयास करें." - "नेटवर्क तैयार नहीं है. पुनः प्रयास करें." - "लेख हटाएं" - "लेख और संख्याएं डालने के बीच स्विच करें" - "और अधिक प्रतिभागी जोड़ें" - "प्रतिभागियों की पुष्‍टि करें" - "नई बातचीत प्रारंभ करें" - "यह आइटम चुनें" - "वीडियो चलाएं" - "लोग और विकल्प" - "डीबग" - "लोग और विकल्प" - "सामान्य" - "इस वार्तालाप में लोग" - "कॉल करें" - "संदेश भेजें" - "<br/><small>from %s</small> से संदेश भेजें" - - फ़ोटो भेजें - फ़ोटो भेजें - - - ऑडियो भेजें - ऑडियो भेजें - - - वीडियो भेजें - वीडियो भेजें - - - संपर्क कार्ड भेजें - संपर्क कार्ड भेजें - - - अटैचमेंट भेजें - अटैचमेंट भेजें - - - %d अटैचमेंट भेजे जाने के लिए तैयार हैं - %d अटैचमेंट भेजे जाने के लिए तैयार हैं - - "फ़ीडबैक भेजें" - "Google Play स्टोर में देखें" - "वर्शन जानकारी" - "वर्शन %1$s" - "ओपन सोर्स लाइसेंस" - "नोटिफिकेशन" - "अटैचमेंट सीमा पर पहुंच गए" - "अटैचमेंट लोड करने में विफल रहा." - "संपर्कों में जोड़ें?" - "संपर्क जोड़ें" - "विषय" - "विषय: " - "%s%s" - "संपर्क कार्ड लोड हो रहा है" - "संपर्क कार्ड लोड नहीं किया जा सका" - "संपर्क कार्ड देखें" - - %d संपर्क - %d संपर्क - - "संपर्क कार्ड" - "जन्मदिन" - "नोट" - "संदेश अग्रेषित करें" - "जवाब दें" - "+1" - "+%d" - "SMS अक्षम है" - "भेजने के लिए, संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सेट करें" - "संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सेट करें" - "बदलें" - "संदेश प्राप्‍त करने के लिए, संदेश सेवा को डिफ़ॉल्‍ट SMS ऐप के रूप में सेट करें" - "SMS संदेश भेजने के लिए कोई भी पसंदीदा सिम नहीं चुनी गई है" - "डिवाइस स्‍वामी द्वारा इस ऐप्स की अनुमति नहीं दी गई है." - "ठीक" - "बातचीत में बहुत अधिक प्रतिभागी हैं" - - अमान्‍य संपर्क - अमान्‍य संपर्क - - "कैमरे का चित्र लोड नहीं किया जा सका" - "आप: " - "%s: " - "ड्राफ़्ट" - "आपके द्वारा नया वार्तालाप प्रारंभ करने के बाद, वह आपको यहां सूचीबद्ध दिखाई देगा" - "संग्रहीत वार्तालाप यहां दिखाई देते हैं" - "वार्तालाप लोड हो रहे हैं…" - "चित्र" - "ऑडियो क्लिप" - "वीडियो" - "संपर्क कार्ड" - "MMS" - "वापस लाएं" - "पुन: प्रयास करें" - "नया संदेश प्रारंभ करने के लिए कोई संपर्क नाम या फ़ोन नंबर डालें" - "अवरुद्ध करें" - "%s को अवरुद्ध करें" - "%s को अनवरोधित करें" - "%s को अवरोधित करें?" - "आपको इस संदेश से नंबर प्राप्‍त होते रहेंगे लेकिन अब उनकी सूचना नहीं दी जाएगी. इस वार्तालाप को संग्रहीत किया जाएगा." - "अवरोधित संपर्क" - "अनवरोधित करें" - "अवरोधित संपर्क" - "दस्‍तावेज़ लाइब्रेरी से चित्र चुनें" - "संदेश भेजा जा रहा है" - "संदेश भेजा गया" - "सेलुलर डेटा बंद हो गया है. अपनी सेटिंग जांचें." - "हवाई जहाज़ मोड में संदेश नहीं भेजे जा सकते" - "संदेश भेजा नहीं जा सका" - "संदेश डाउनलोड किया गया" - "सेल्‍युलर डेटा बंद हो गया है. अपनी सेटिंग जांचें." - "संदेश को हवाई जहाज़ मोड में डाउनलोड नहीं किया जा सकता" - "संदेश डाउनलोड नहीं किया जा सका" - "शून्य" - "एक" - "दो" - "तीन" - "चार" - "पांच" - "छह" - "सात" - "आठ" - "नौ" - "%1$s के साथ संदेश नहीं भेजा जा सकता, त्रुटि %2$d" - "अज्ञात वाहक के साथ संदेश नहीं भेजा जा सकता, त्रुटि %1$d" - "अग्रेषित: %s" - "संदेश नहीं भेजा गया: नेटवर्क पर सेवा सक्रिय नहीं है" - "संदेश नहीं भेजा गया: अमान्य गंतव्य पता" - "संदेश नहीं भेजा गया: अमान्य संदेश" - "संदेश नहीं भेजा गया: असमर्थित सामग्री" - "संदेश नहीं भेजा गया: असमर्थित संदेश" - "संदेश नहीं भेजा गया: बहुत बड़ा है" - "नया संदेश" - "देखें" - "चित्र" - "उपयुक्त ऐप्लिकेशन नहीं मिला" - "प्राप्तकर्ता को निकालें" - "नया संदेश" - "अभी नहीं" - "पहुंच बिंदु संपादित करें" - "सेट नहीं है" - "नाम" - "APN" - "MMSC" - "MMS प्रॉक्‍सी" - "MMS पोर्ट" - "MCC" - "MNC" - "APN प्रकार" - "APN हटाएं" - "नया APN" - "सहेजें" - "छोड़ें" - "नाम फ़ील्‍ड खाली नहीं हो सकता." - "APN खाली नहीं हो सकता." - "MCC फ़ील्‍ड में 3 अंक होने चाहिए." - "MCC फ़ील्‍ड में 2 या 3 अंक होने चाहिए." - "डिफ़ॉल्‍ट APN सेटिंग पुनर्स्थापित हो रही हैं." - "डिफ़ॉल्ट पर रीसेट करें" - "डिफ़ॉल्‍ट APN सेटिंग रीसेट करना पूर्ण हुआ." - "शीर्षक रहित" - "पहुंच बिंदु के नाम" - "APN" - "नया APN" - "एैक्सेस बिंदु नाम सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं" - "क्लिपबोर्ड पर कॉपी बनाएं?" - "कॉपी बनाएं" - "%s पर" - "सामान्य" - "अतिरिक्त सेटिंस" - "सामान्य सेटिंग" - "अतिरिक्त सेटिंग" - "\"%s\" सिम" - "सभी प्राप्‍तकर्ताओं को व्‍यक्‍तिगत SMS संदेश भेजें. कोई भी जवाब केवल आपको प्राप्‍त होंगे" - "सभी प्राप्‍तकर्ताआें को एकल MMS भेजें" - "अज्ञात नंबर" - "नया संदेश" - "नया संदेश." - "सिम चयनकर्ता" - "%1$s चयनित, SIM चयनकर्ता" - "विषय संपादित करें" - "सिम चुनें या विषय संपादित करें" - "ऑडियो रिकॉर्ड करने के लिए स्‍पर्श करके रखें" - "नई बातचीत प्रारंभ करें" - "संदेश सेवा" - "संदेश सेवा सूची" - "संदेश सेवा" - "नया संदेश" - "वार्तालाप सूची" - "बातचीत लोड हो रही हैं…" - "संदेश लोड हो रहे हैं" - "और बातचीत देखें" - "अधिक संदेश देखें" - "वार्तालाप हटा दिया गया है" - "वार्तालाप हटा दिया गया. कोई भिन्न संदेश सेवा वार्तालाप दिखाने के लिए स्पर्श करें" - "अवरोधित" - "अनवरोधित" - "मेमोरी स्‍थान कम है. कुछ डेटा खो सकता है." - "अटैचमेंट चुनें" - "चयन की पुष्टि करें" - "%d चुने गए" - "कृपया एक या अधिक अटैचमेंट निकालें और पुन: प्रयास करें." - "आप अपना संदेश भेजकर देख सकते हैं, लेकिन यह तब तक वितरित नहीं होगा जब तक कि आप एक या कुछ और अटैचमेंट निकाल नहीं देते." - "आप प्रति संदेश केवल एक वीडियो भेज सकते हैं. कृपया अतिरिक्‍त वीडियो निकालें और पुन: प्रयास करें." - "संदेश सेवा अटैचमेंट लोड करने में विफल रही." - "फिर भी भेजें" - "बातचीत प्रारंभ नहीं की जा सकी" - "%1$s (%2$s)" - "%s चुनी गई" - diff --git a/res/values-hr/arrays.xml b/res/values-hr/arrays.xml deleted file mode 100644 index 5ebf706..0000000 --- a/res/values-hr/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "nema predmeta" - "nosubject" - - - "Da" - "Ne" - "U redu" - "He-he" - "Hvala" - "Slažem se" - "Lijepo" - "Krećem" - "U redu, javit ću se kasnije" - ":)" - ":(" - - diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml deleted file mode 100644 index e12b64f..0000000 --- a/res/values-hr/strings.xml +++ /dev/null @@ -1,545 +0,0 @@ - - - - - "Slanje poruka" - "Slanje poruka" - "Odabir razgovora" - "Postavke" - "Pošalji poruku" - "Dodajte privitak" - "Pomoć" - "Dobro došli" - "Preskoči" - "Dalje >" - "Dalje" - "Izlaz" - "Postavke >" - "Postavke" - "Slanje poruka treba dopuštenje za SMS, Telefon i Kontakte." - "Dopuštenja možete promijeniti u Postavkama > Aplikacije > Slanje poruka > Dopuštenja." - "Dopuštenja možete promijeniti tako da u Postavkama otvorite Aplikacije, Slanje poruka, Dopuštenja." - "Često kontaktirani" - "Svi kontakti" - "Pošalji na %s" - "Snimite fotografije ili videozapis" - "Odaberite slike s ovog uređaja" - "Snimanje zvuka" - "Odaberite fotografiju" - "Medij je odabran." - "Odabir medija poništen je." - "Odabrano: %d" - "slika %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "slika" - "Snimanje zvuka" - "Dijeljenje" - "Upravo sada" - "Sad" - - %d min - %d min - %d min - - - %d sat - %d sata - %d sati - - - %d dan - %d dana - %d dana - - - %d tjedan - %d tjedna - %d tjedana - - - %d mjesec - %d mjeseca - %d mjeseci - - - %d godina - %d godine - %d godina - - "Poruka klase 0" - "Spremi" - "Na uređaju ponestaje prostora. Slanje poruka automatski će izbrisati starije poruke da bi se oslobodio prostor." - "Ponestaje prostora za pohranu" - "Slanje poruka možda neće slati ili primati poruke dok se na uređaju ne oslobodi više prostora." - "Nema dovoljno prostora za pohranu SMS-a. Možda ćete morati izbrisati poruke." - "Potvrđivanje telefonskog broja" - "Taj jednokratni korak omogućit će Slanju poruka pravilnu isporuku vaših grupnih poruka." - "Telefonski broj" - "Izbriši sve poruke s medijskim sadržajima" - "Izbriši poruke starije od %s" - "Automatski izbriši poruke starije od %s" - "Zanemari" - "Izbrisati sve poruke s medijskim sadržajima?" - "Izbrisati poruke starije od %s?" - "Izbrisati poruke starije od %s i uključiti automatsko brisanje?" - "%s kaže" - "Rekli ste" - "Poruka pošiljatelja %s" - "Poslali ste poruku" - "Slanje…" - "Nije poslano. Dodirnite za ponovni pokušaj." - "Nije poslano. Pokušavamo ponovo…" - "Ponovno slanje ili brisanje" - "Nazovite hitnu službu. Vaša tekstna poruka nije isporučena." - "Nije poslano" - "Nova MMS poruka za preuzimanje" - "Nova MMS poruka" - "Preuzimanje nije moguće" - "Dodirnite za ponovni pokušaj" - "Dodirnite za preuzimanje" - "Preuzimanje ili brisanje" - "Preuzimanje..." - "Poruka je istekla ili nije dostupna" - "veličina: %1$s, istek: %2$s" - "Nije poslano. Primatelj nije važeći." - "Usluga nije aktivirana na mreži" - "Slanje nije uspjelo zbog problema s mrežom" - "Poruka je istekla ili nije dostupna" - "(Bez predmeta)" - "Nepoznati pošiljatelj" - "Isporučeno" - "Preuzimanje poruke %1$s od pošiljatelja %2$s nije uspjelo." - "Radnja baze podataka nije dovršena zbog nedostatka memorije" - "Poruka nije poslana" - "Neke poruke nisu poslane u Slanju poruka" - - %d poruka u %d razgovoru - %d poruke u %d razgovora - %d poruka u %d razgovora - - "Poruka nije preuzeta" - "Neke poruke nisu preuzete u Slanje poruka" - - %d poruka u %d razgovoru - %d poruke u %d razgovora - %d poruka u %d razgovora - - "Poruka na broj %1$s nije poslana" - "Nazovite hitnu službu. Vaša tekstna poruka na broj %1$s nije isporučena." - - %d nova poruka - %d nove poruke - %d novih poruka - - "Započni" - "Fotoaparat nije dostupan" - "Fotoaparat nije dostupan" - "Snimanje videozapisa nije dostupno" - "Medij nije spremljen" - "Nije moguće snimiti sliku" - "Natrag" - "Arhivirano" - "Izbriši" - "Arhiviraj" - "Poništi arhiviranje" - "Isključi obavijesti" - "Uključi obavijesti" - "Dodaj kontakt" - "Preuzmi" - "Pošalji" - "Izbriši" - "Želite li izbrisati tu poruku?" - "Tu radnju nije moguće poništiti." - "Izbriši" - - Izbrisati razgovore? - Izbrisati razgovore? - Izbrisati razgovore? - - "Izbriši" - "Odustani" - "Odredište" - "Odaberi više slika" - "Potvrdite odabir" - "+%d" - "Audioporuka nije snimljena. Pokušajte ponovo." - "Audioporuku nije moguće reproducirati. Pokušajte ponovo." - "Audioporuka nije spremljena. Pokušajte ponovo." - "Dodirnite i zadržite" - ", " - " " - ": " - " " - "Slika" - "Audioisječak" - "Videozapis" - "Kartica kontakta" - "Preuzmi" - "Odgovorite putem SMS-a" - "Odgovor MMS-om" - "Odgovorite" - - %d sudionik - %d sudionika - %d sudionika - - "Ja" - "Kontakt je blokiran i arhiviran" - "Kontakt deblokiran i dearhiviran" - "Arhivirano: %d" - "Poništena arhiviranja: %d" - "Obavijesti isključene" - "Obavijesti uključene" - "Sve je postavljeno. Ponovo dodirnite Pošalji." - "Slanje poruka uspješno je postavljeno kao zadana aplikacija za SMS." - - Odbacivanje privitaka - Odbacivanje privitaka - Odbacivanje privitaka - - "Audioprivitak" - "Reproduciraj audioprivitak" - "Pauziraj" - "Poruka pošiljatelja %s: %s." - "Poruka pošiljatelja %s nije uspjela: %s. Vrijeme: %s" - "Poruka pošiljatelja %s: %s. Vrijeme: %s" - "Poruka za kontakt %s nije poslana: %s. Vrijeme: %s" - "Slanje poruke kontaktu %s: %s. Vrijeme: %s" - "Poruka za kontakt %s nije uspjela: %s. Vrijeme: %s" - "Poruka kontaktu %s: %s. Vrijeme: %s" - "Neuspjela poruka pošiljatelja %s: %s. Vrijeme: %s. %s" - "Poruka pošiljatelja %s: %s. Vrijeme: %s. %s" - "Poruka za grupu %s nije poslana: %s. Vrijeme: %s" - "Slanje poruke grupi %s: %s. Vrijeme: %s" - "Poruka za grupu %s nije uspjela: %s. Vrijeme: %s" - "Poruka grupi %s: %s. Vrijeme: %s" - "Poruka nije uspjela. Dodirnite za ponovni pokušaj." - "Razgovor sa sljedećima: %s" - "Brisanje predmeta" - "Snimi videozapis" - "Snimi fotografiju" - "Snimi sliku" - "Početak snimanja videozapisa" - "Prebacivanje na fotoaparat na punom zaslonu" - "Prebacivanje između prednjeg i stražnjeg fotoaparata" - "Zaustavi snimanje i priloži videozapis" - "Zaustavi snimanje videozapisa" - "Fotografije Slanja poruka" - - %d fotografija spremljena je u album \"%s\" - %d fotografije spremljene su u album \"%s\" - %d fotografija spremljeno je u album \"%s\" - - - %d videozapis spremljen je u album \"%s\" - %d videozapisa spremljena su u album \"%s\" - %d videozapisa spremljeno je u album \"%s\" - - - %d privitak spremljen je u album \"%s\" - %d privitka spremljena su u album \"%s\" - %d privitaka spremljeno je u album \"%s\" - - - %d privitak spremljen je u \"Preuzimanja\" - %d privitka spremljena su u \"Preuzimanja\" - %d privitaka spremljeno je u \"Preuzimanja\" - - - %d privitak spremljen - %d privitka spremljena - %d privitaka spremljeno - - - Spremanje %d privitka nije uspjelo - Spremanje %d privitaka nije uspjelo - Spremanje %d privitaka nije uspjelo - - "MMS privitak spremljen" - "Postavke" - "Arhivirano" - "Zatvori" - "MMS" - "Napredno" - "Otklanjanje pogrešaka" - "Obavijesti" - "Zvuk" - "Bešumno" - "Vibriranje" - "Blokirano" - "Izvješća o isporuci SMS-a" - "Zahtijevanje izvješća o isporuci za svaki poslani SMS" - "Automatski dohvat" - "Automatsko dohvaćanje MMS-ova" - "Automatski dohvati u roamingu" - "Automatsko dohvaćanje MMS-ova u roamingu" - "Grupne poruke" - "Slanje jedne poruke za više primatelja pomoću MMS-a" - "Zadana aplikacija za SMS" - "Zadana aplikacija za SMS" - - - "Vaš telefonski broj" - "Nepoznato" - "Zvukovi odlazne poruke" - "Sigurnosno kopiraj SMS-ove" - "Sigurnosno kopiranje neobrađenih podataka primljenih SMS-ova u datoteku vanjske pohrane" - "Sigurnosno kopiraj MMS-ove" - "Sigurnosno kopiranje neobrađenih podataka primljenih MMS-ova u datoteku vanjske pohrane" - "Upozorenja putem mobilne mreže" - "Opcije poruka" - "Kopiraj tekst" - "Prikaz pojedinosti" - "Izbriši" - "Proslijedi" - "Pojedinosti poruke" - "Vrsta: " - "SMS poruka" - "Multimedijska poruka" - "Šalje: " - "Prima: " - "Poslano: " - "Primljeno: " - "Predmet: " - "Veličina: " - "Prioritet: " - "SIM: " - "Visok" - "Uobičajeni" - "Niski" - "SIM %s" - "Skrivena adresa pošiljatelja" - "Slanje poruke nije moguće tijekom učitavanja privitaka." - "Privitak nije učitan. Pokušajte ponovo." - "Mreža nije spremna. Pokušajte ponovo." - "Brisanje teksta" - "Prebacivanje između unosa teksta i brojeva" - "Dodaj još sudionika" - "Potvrdi sudionike" - "Započnite novi razgovor" - "Odaberite ovu stavku" - "Reproduciraj videozapis" - "Osobe i opcije" - "Otkloni pogreške" - "Osobe i opcije" - "Općenito" - "Osobe u ovom razgovoru" - "Uputite poziv" - "Pošaljite poruku" - "Pošalji poruku<br/><small>sa SIM kartice %s</small>" - - Pošaljite fotografije - Pošaljite fotografije - Pošaljite fotografije - - - Pošaljite audiozapise - Pošaljite audiozapise - Pošaljite audiozapise - - - Pošaljite videozapise - Pošaljite videozapise - Pošaljite videozapise - - - Pošaljite kartice kontakata - Pošaljite kartice kontakata - Pošaljite kartice kontakata - - - Pošaljite privitke - Pošaljite privitke - Pošaljite privitke - - - %d privitak spreman je za slanje - %d privitka spremna su za slanje - %d privitaka spremno je za slanje - - "Slanje povratnih informacija" - "Prikaži u Trgovini Google Play" - "Informacije o verziji" - "Verzija %1$s" - "Licence otvorenog koda" - "Obavijesti" - "Dosegnuto je ograničenje za privitke" - "Učitavanje privitka nije uspjelo." - "Želite li dodati u Kontakte?" - "Dodavanje kontakta" - "Predmet" - "Predmet: " - "%s%s" - "Učitavanje kartice kontakta" - "Kartica kontakta nije učitana" - "Prikaz kartice kontakta" - - %d kontakt - %d kontakta - %d kontakata - - "Kartice kontakata" - "Rođendan" - "Bilješke" - "Proslijedi poruku" - "Odgovori" - "+1" - "+%d" - "SMS je onemogućen" - "Postavite Slanje poruka kao zadanu aplikaciju za SMS da biste slali poruke" - "Postavite Slanje poruka kao zadanu aplikaciju za SMS" - "Promijeni" - "Postavite Slanje poruka kao zadanu aplikaciju za SMS da biste primali poruke" - "Nije odabran preferirani SIM za slanje SMS poruka" - "Vlasnik uređaja nije odobrio upotrebu te aplikacije." - "U redu" - "Previše sudionika u razgovoru" - - Nevažeći kontakti - Nevažeći kontakti - Nevažeći kontakti - - "Slika nije učitana" - "Vi: " - "%s: " - "Skica" - "Ovdje će biti naveden novi razgovor kada ga pokrenete" - "Arhivirani razgovori prikazuju se ovdje" - "Učitavanje razgovora..." - "Slika" - "Audioisječak" - "Videozapis" - "Kartica kontakta" - "MMS" - "Poništi" - "Pokušaj ponovo" - "Unesite ime kontakta ili telefonski broj da biste započeli novu poruku" - "Blokiraj" - "Blokiranje %s" - "Deblokiraj %s" - "Blokirati %s?" - "I dalje ćete primati poruke s tog broja, ali više nećete primati obavijesti o tome. Razgovor će se arhivirati." - "Blokirani kontakti" - "DEBLOKIRAJ" - "Blokirani kontakti" - "Odaberite sliku iz knjižnice dokumenata" - "Slanje poruke" - "Poruka je poslana" - "Mobilni su podaci isključeni. Provjerite postavke." - "Ne možete slati poruke u načinu rada u zrakoplovu" - "Slanje poruke nije uspjelo" - "Poruka preuzeta" - "Mobilni su podaci isključeni. Provjerite postavke." - "Nije moguće preuzimanje poruka u načinu rada u zrakoplovu" - "Preuzimanje poruke nije uspjelo" - "Nula" - "Jedan" - "Dva" - "Tri" - "Četiri" - "Pet" - "Šest" - "Sedam" - "Osam" - "Devet" - "Poruka nije poslana putem operatera %1$s, pogreška %2$d" - "Poruka nije poslana putem nepoznatog mobilnog operatera, pogreška %1$d" - "Fwd: %s" - "Poruka nije poslana jer usluga nije aktivirana na mreži" - "Poruka nije poslana jer odredišna adresa nije važeća" - "Poruka nije poslana jer nije važeća" - "Poruka nije poslana jer sadržaj nije podržan" - "Poruka nije poslana jer nije podržana" - "Poruka nije poslana jer je prevelika" - "Nova poruka" - "Prikaz" - "Slika" - "Nije pronađena odgovarajuća aplikacija" - "Uklanjanje primatelja" - "Nova poruka" - "Odustani" - "Uređivanje pristupne točke" - "Nije postavljeno" - "Naziv" - "APN" - "MMSC" - "MMS proxy" - "MMS priključak" - "MCC" - "MNC" - "Vrsta APN-a" - "Izbriši APN" - "Novi APN" - "Spremi" - "Odbaci" - "Polje naziva ne može biti prazno." - "APN ne može biti prazan." - "MCC polje mora imati 3 znamenke." - "Polje MNC mora imati 2 ili 3 znamenke." - "Vraćaju se zadane postavke APN-a." - "Vrati na zadano" - "Dovršeno je vraćanje zadanih postavki APN-a." - "Bez naziva" - "Nazivi pristupnih točaka" - "APN-ovi" - "Novi APN" - "Postavke naziva pristupne točke nisu dostupne ovom korisniku" - "Kopirati u međuspremnik?" - "Kopiraj" - "na %s" - "Općenito" - "Napredno" - "Opće postavke" - "Napredne postavke" - "\"%s\" SIM" - "Slanje pojedinačnih SMS poruka svim primateljima. Eventualne odgovore dobit ćete samo vi" - "Slanje pojedinačnog MMS-a svim primateljima" - "Nepoznati broj" - "Nova poruka" - "Nova poruka." - "Gumb za odabir SIM-a" - "%1$s odabran, gumb za odabir SIM-a" - "Uredi predmet" - "Odabir SIM-a ili uređivanje predmeta" - "Dodirnite i zadržite da biste snimili zvuk" - "Započnite novi razgovor" - "Slanje poruka" - "Popis Slanja poruka" - "Slanje poruka" - "Nova poruka" - "Popis razgovora" - "Učitavanje razgovora" - "Učitavanje poruka" - "Prikaži više razgovora" - "Prikaži više poruka" - "Razgovor je izbrisan" - "Razgovor izbrisan. Dodirnite za prikaz drugog razgovora iz Slanja poruka" - "Blokirano" - "Deblokirano" - "Nedostaje prostora za pohranu. Neki se podaci mogu izgubiti." - "Odaberite privitke" - "Potvrdite odabir" - "Odabrano: %d" - "Uklonite jedan ili više privitaka i pokušajte ponovo." - "Možete pokušati poslati poruku, ali možda neće biti dostavljena ako ne uklonite jedan ili više privitaka." - "Možete poslati samo jedan videozapis u poruci. Uklonite dodatne videozapise i pokušajte ponovo." - "Slanje poruka nije uspjelo učitati privitak." - "Svejedno pošalji" - "Pokretanje razgovora nije uspjelo" - "%1$s (%2$s)" - "Odabrani SIM: %s" - diff --git a/res/values-hu/arrays.xml b/res/values-hu/arrays.xml deleted file mode 100644 index 36c0cd0..0000000 --- a/res/values-hu/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "nincs tárgy" - "nincs tárgy" - - - "Igen" - "Nem" - "OK" - "Haha!" - "Köszönöm!" - "Egyetértek" - "Ügyes" - "Úton vagyok" - "Rendben, később jelentkezem" - ":)" - ":(" - - diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml deleted file mode 100644 index cd254f2..0000000 --- a/res/values-hu/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Üzenetváltás" - "Üzenetváltás" - "Beszélgetés kijelölése" - "Beállítások" - "Üzenet küldése" - "Melléklet hozzáadása" - "Súgó" - "Üdvözöljük!" - "Kihagyás" - "Következő >" - "Tovább" - "Kilépés" - "Beállítások >" - "Beállítások" - "A Messaging engedélyt kér a következőkhöz: SMS, telefon és névjegyek." - "Az engedélyeket a Beállítások > Alkalmazások > Messaging > Engedélyek lehetőségnél módosíthatja." - "Az engedélyeket a Beállítások, Alkalmazások, Messaging, Engedélyek lehetőségnél módosíthatja." - "Gyakran használt névjegyek" - "Az összes névjegy" - "Küldés: %s" - "Kép vagy videó rögzítése" - "Képek kiválasztása erről az eszközről" - "Hanganyag rögzítése" - "Fotó kiválasztása" - "Kijelölte a médiaelemet." - "Visszavonta a médiaelem kijelölését." - "%d kiválasztva" - "kép %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "kép" - "Hanganyag rögzítése" - "Megosztás" - "Éppen most" - "Most" - - %d perce - %d perce - - - %d órája - %d órája - - - %d napja - %d napja - - - %d hét - egy hét - - - %d hónap - egy hónap - - - %d év - egy év - - "0. osztályú üzenet" - "Mentés" - "Az eszközön kevés a tárhely. A Messaging automatikusan törli a régebbi üzeneteket tárhely felszabadítása érdekében." - "Kevés a szabad terület" - "A Messaging nem küldhet vagy fogadhat üzenetet, amíg nem áll rendelkezésre több hely az eszközön." - "Kevés a tárhely az SMS-eknek. Lehet, hogy üzeneteket kell törölnie." - "Telefonszám megerősítése" - "Ez az egyszeri lépés biztosítja, hogy a Messaging megfelelően továbbítsa a csoportos üzeneteket." - "Telefonszám" - "Az összes médiatartalommal bíró üzenet törlése" - "A(z) %s időtartamnál régebbi üzenetek törlése" - "A(z) %s időtartamnál régebbi üzenetek automatikus törlése" - "Mellőzés" - "Törli az összes, médiát tartalmazó üzenetet?" - "Törli a(z) %s időtartamnál régebbi üzeneteket?" - "Törli a(z) %s időtartamnál régebbi üzeneteket, és bekapcsolja az automatikus törlést?" - "%s a következőt mondta:" - "Ön a következőt mondta" - "%s üzenete:" - "Ön elküldött egy üzenetet" - "Küldés…" - "Nem lehetett elküldeni. Érintse meg az újbóli próbálkozáshoz." - "Nem lehetett elküldeni. Újabb próbálkozás…" - "Újraküldés vagy törlés" - "Kérjük, tárcsázza a segélyhívót. A szöveges üzenetet jelenleg nem tudjuk elküldeni." - "Sikertelen" - "Új letölthető MMS üzenet" - "Új MMS üzenet" - "Nem sikerült letölteni" - "Érintse meg az újabb próbálkozáshoz" - "Érintse meg a letöltéshez" - "Letöltés vagy törlés" - "Letöltés…" - "Az üzenet vagy lejárt, vagy nem érhető el." - "méret: %1$s, lejárat: %2$s" - "Nem lehetett elküldeni. A címzett érvénytelen." - "A szolgáltatás nincs aktiválva a hálózaton" - "Hálózati probléma miatt nem lehetett elküldeni" - "Az üzenet vagy lejárt, vagy nem érhető el." - "(Nincs tárgy)" - "Ismeretlen feladó" - "Kézbesítve" - "Nem sikerült letölteni %2$s%1$s” tárgyú levelét." - "Kevés a memória, ezért nem sikerült végrehajtani a műveletet az adatbázison" - "A rendszer nem küldte el az üzenetet" - "A Messaging egyes üzeneteit nem lehetett elküldeni" - - %d üzenet %d beszélgetésben - %d üzenet egy beszélgetésben - - "Az üzenet nincs letöltve" - "A Messaging egyes üzeneteit nem lehetett letölteni" - - %d üzenet %d beszélgetésben - %d üzenet egy beszélgetésben - - "Nem sikerült elküldeni az üzenetet a(z) %1$s számra" - "Kérjük, tárcsázza a segélyhívót. A szöveges üzenetet jelenleg nem tudjuk elküldeni erre a számra: %1$s." - - %d új üzenet - Új üzenet - - "Indítás" - "A kamera nem érhető el" - "A kamera nem érhető el" - "A videorögzítés nem érhető el" - "Nem lehetett menteni a médiát" - "Nem lehet képet készíteni" - "Vissza" - "Archiválva" - "Törlés" - "Archiválás" - "Archiválás visszavonása" - "Értesítések kikapcsolása" - "Értesítések bekapcsolása" - "Névjegy hozzáadása" - "Letöltés" - "Küldés" - "Törlés" - "Törli ezt az üzenetet?" - "Ez a művelet nem vonható vissza." - "Törlés" - - Törli ezeket a beszélgetéseket? - Törli ezt a beszélgetést? - - "Törlés" - "Mégse" - "Címzett" - "Több kép kiválasztása" - "Kiválasztás megerősítése" - "+%d" - "Nem sikerült rögzíteni a hangot. Próbálja újra." - "A hanganyag nem játszható le. Próbálja újra." - "Nem sikerült elmenteni a hangot. Próbálja újra." - "Érintés és tartás" - ", " - " " - ": " - " " - "Kép" - "Hangfelvétel" - "Videó" - "Névjegykártya" - "Letöltés" - "Válasz SMS-ben" - "Válasz MMS-ben" - "Válasz" - - %d résztvevő - %d résztvevő - - "Én" - "Ismerős letiltva és archiválva" - "Névjegy tiltása és archiválása visszavonva" - "%d archiválva" - "%d archiválása megszüntetve" - "Értesítések kikapcsolva" - "Értesítések bekapcsolva" - "Minden készen áll. Érintse meg újra a Küldés lehetőséget." - "A Messaging sikeresen beállítva alapértelmezett SMS-alkalmazásként." - - Mellékletek elvetése - Melléklet elvetése - - "Hangmelléklet" - "Hangmelléklet lejátszása" - "Szünet" - "%s üzenete: %s." - "Sikertelen üzenet %s feladótól: %s. Idő: %s." - "Üzenet %s feladótól: %s. Idő: %s." - "Elküldetlen üzenet a(z) %s csoportnak: %s. Idő: %s." - "Üzenet küldése a(z) %s csoportnak: %s. Idő: %s." - "Sikertelen üzenet a(z) %s csoportnak: %s. Idő: %s." - "Üzenet a(z) %s csoportnak: %s. Idő: %s." - "Sikertelen üzenet %s feladótól: %s. Idő: %s. %s" - "Üzenet %s feladótól: %s. Idő: %s. %s" - "Elküldetlen üzenet a(z) %s csoportnak: %s. Idő: %s." - "Üzenet küldése a(z) %s csoportnak: %s. Idő: %s." - "Sikertelen üzenet a(z) %s csoportnak: %s. Idő: %s." - "Üzenet a(z) %s csoportnak: %s. Idő: %s." - "Sikertelen üzenet. Érintse meg az újrapróbáláshoz." - "Beszélgetés a következőkkel: %s" - "Tárgy törlése" - "Videó rögzítése" - "Állókép rögzítése" - "Kép készítése" - "Videofelvétel megkezdése" - "Váltás teljes képernyős kameranézetre" - "Váltás az elülső és a hátulsó kamera között" - "Rögzítés leállítása és a videó csatolása" - "Videofelvétel leállítása" - "Messaging-fotók" - - %d fotó mentve a következő albumba: %s - %d fotó mentve a következő albumba: %s - - - %d videó mentve a következő albumba: %s - %d videó mentve a következő albumba: %s - - - %d melléklet mentve a következő albumba: %s - %d melléklet mentve a következő albumba: %s - - - %d melléklet mentve a „Letöltések” közé - %d melléklet mentve a „Letöltések” közé - - - %d melléklet sikeresen mentve - %d melléklet sikeresen mentve - - - %d melléklet mentése nem sikerült - %d melléklet mentése nem sikerült - - "Mentett MMS-melléklet" - "Beállítások" - "Archiválva" - "Bezárás" - "MMS" - "Speciális" - "Hibakeresés" - "Értesítések" - "Hang" - "Néma" - "Rezgés" - "Letiltva" - "SMS-kézbesítési jelentések" - "Kézbesítési jelentés kérése minden elküldött SMS-ről" - "Automatikus letöltés" - "MMS-üzenetek automatikus letöltése" - "Auto. letöltés barangoláskor" - "MMS-ek automatikus lekérése adatbarangoláskor" - "Csoportos üzenetküldés" - "Küldjön MMS-t, amikor több címzettnek szeretne üzenetet küldeni." - "Alapértelmezett SMS-alkalmazás" - "Alapértelmezett SMS-alkalmazás" - - - "Az Ön telefonszáma" - "Ismeretlen" - "Kimenő üzenetek hangja" - "SMS kiírása" - "Beérkező nyers SMS-adatok kiírása külső tárolófájlba" - "MMS kiírása" - "Beérkező nyers MMS-adatok kiírása külső tárolófájlba" - "Vezeték nélküli értesítések" - "Üzenetbeállítások" - "Szöveg másolása" - "Részletek megjelenítése" - "Törlés" - "Továbbítás" - "Üzenet részletei" - "Típus: " - "Szöveges üzenet" - "Multimédiás üzenet" - "Küldő: " - "Címzett: " - "Elküldve: " - "Érkezett: " - "Tárgy: " - "Méret: " - "Prioritás: " - "SIM: " - "Magas" - "Normál" - "Alacsony" - "%s. SIM" - "A küldő címe elrejtve" - "A mellékletek betöltése közben nem lehet elküldeni az üzenetet." - "Nem lehetett betölteni a mellékletet. Próbálja újra." - "A hálózat nem áll készen. Próbálja újra." - "Szöveg törlése" - "Váltás szöveg és számok bevitele között" - "További résztvevők hozzáadása" - "Résztvevők megerősítése" - "Új beszélgetés indítása" - "Elem kiválasztása" - "Videó lejátszása" - "Személyek és beállítások" - "Hibakeresés" - "Személyek és beállítások" - "Általános" - "A jelen beszélgetésben részt vevő személyek" - "Hívásindítás" - "Üzenet küldése" - "Üzenet küldése<br/><small>a következő SIM kártyáról: %s</small>" - - Fotók küldése - Fotó küldése - - - Hangok küldése - Hang küldése - - - Videók küldése - Videó küldése - - - Névjegykártyák küldése - Névjegykártya küldése - - - Mellékletek küldése - Melléklet küldése - - - %d melléklet készen áll a küldésre - Egy melléklet készen áll a küldésre - - "Visszajelzés küldése" - "Megtekintés a Google Play Áruházban" - "Verzióadatok" - "Verzió: %1$s" - "Nyílt forráskódú licencek" - "Értesítések" - "Elérte a mellékletekre vonatkozó korlátot" - "Nem sikerült betölteni a mellékletet." - "Hozzáadja a Névjegyekhez?" - "Névjegy hozzáadása" - "Tárgy" - "Tárgy: " - "%s%s" - "Névjegykártya betöltése" - "Nem lehetett betölteni a névjegykártyát" - "Névjegykártya megtekintése" - - %d névjegy - %d névjegy - - "Névjegykártyák" - "Születésnap" - "Jegyzetek" - "Üzenet továbbítása" - "Válasz" - "+1" - "+%d" - "SMS-ek letiltva" - "A küldéshez állítsa be, hogy a Messaging legyen az alapértelmezett SMS-alkalmazás" - "A Messaging beállítása alapértelmezett SMS-alkalmazásként" - "Módosítás" - "Üzenetek fogadásához állítsa be, hogy a Messaging legyen az alapértelmezett SMS-alkalmazás" - "Nincs SIM kiválasztva az SMS-küldéshez" - "Az eszköz tulajdonosa nem engedélyezi ezt az alkalmazást." - "OK" - "Túl sok a résztvevő a beszélgetésben" - - Érvénytelen névjegy - Érvénytelen névjegy - - "Nem lehetett betölteni a kamera képét" - "Ön: " - "%s: " - "Piszkozat" - "Amikor új beszélgetésbe kezd, az itt jelenik meg" - "Itt jelennek meg az archivált beszélgetések" - "Beszélgetések betöltése…" - "Kép" - "Hangfelvétel" - "Videó" - "Névjegykártya" - "MMS" - "Visszavonás" - "Újra" - "Új üzenet létrehozásához adjon meg egy névjegyet vagy telefonszámot" - "Tiltás" - "%s letiltása" - "%s feloldása" - "Letiltja ezt: %s?" - "Továbbra is kap üzeneteket erről a számról, ám a rendszer nem küld róluk értesítést. A beszélgetést a rendszer archiválja." - "Letiltott ismerősök" - "FELOLD" - "Letiltott ismerősök" - "Kép választása a dokumentumtárból" - "Üzenet küldése" - "Üzenet elküldve" - "A mobiladat-kapcsolat ki van kapcsolva. Ellenőrizze beállításait." - "Repülős módban nem lehet üzeneteket küldeni" - "Az üzenetet nem sikerült elküldeni" - "Üzenet letöltve" - "A mobiladat-kapcsolat ki van kapcsolva. Ellenőrizze beállításait." - "Repülős üzemmódban nem lehet üzenetet letölteni" - "Az üzenetet nem sikerült letölteni" - "Nulla" - "Egy" - "Kettő" - "Három" - "Négy" - "Öt" - "Hat" - "Hét" - "Nyolc" - "Kilenc" - "Nem lehet elküldeni az üzenetet a következő szolgáltatónál: %1$s (hibakód: %2$d)" - "Nem lehet elküldeni az üzenetet egy ismeretlen szolgáltatónál (hibakód: %1$d)" - "Továbbítás: %s" - "Az üzenet nincs elküldve: a szolgáltatás nincs aktiválva a hálózaton" - "Az üzenet küldése sikertelen: a címzett címe érvénytelen" - "Az üzenet küldése sikertelen: érvénytelen üzenet" - "Az üzenet küldése sikertelen: nem támogatott tartalom" - "Az üzenet küldése sikertelen: nem támogatott üzenet" - "Az üzenet nincs elküldve: túl nagy" - "Új üzenet" - "Megtekintés" - "Kép" - "Nem található megfelelő alkalmazás" - "Címzett eltávolítása" - "Új üzenet" - "Mégse" - "Hozzáférési pont szerkesztése" - "Nincs megadva" - "Név" - "APN" - "MMSC" - "MMS-proxy" - "MMS-port" - "MCC" - "MNC" - "APN típusa" - "APN törlése" - "Új APN" - "Mentés" - "Elvetés" - "A Név mező nem lehet üres." - "Az APN nem lehet üres." - "Az MCC mezőnek három számjegyet kell tartalmaznia." - "Az MNC mezőbe 2 vagy 3 számjegyet kell írni." - "Alapértelmezett APN-beállítások visszaállítása." - "Visszaállítás alaphelyzetbe" - "Az alapértelmezett APN-beállítások visszaállítása befejeződött." - "Névtelen" - "Hozzáférési pontok nevei" - "APN-ek" - "Új APN" - "A hozzáférési pont nevének beállításai nem érhetők el ennél a felhasználónál" - "Átmásolja a vágólapra?" - "Másolás" - "fogadás a következőn: %s" - "Általános" - "Speciális" - "Általános beállítások" - "Speciális beállítások" - "„%s” SIM" - "Külön SMS küldése valamennyi címzettnek. Csak Ön kap választ" - "Egyetlen MMS küldése valamennyi címzettnek" - "Ismeretlen szám" - "Új üzenet" - "Új üzenet" - "SIM kártya kiválasztása" - "A(z) %1$s SIM kártya kiválasztva (SIM-választó)" - "Tárgy szerkesztése" - "SIM kártya kiválasztása vagy a tárgy szerkesztése" - "Hangfelvétel készítéséhez tartsa nyomva" - "Új beszélgetés indítása" - "Messaging" - "Messaging-lista" - "Messaging" - "Új üzenet" - "Beszélgetéslista" - "Beszélgetések betöltése" - "Üzenetek betöltése" - "További beszélgetések megtekintése" - "További üzenetek megtekintése" - "Beszélgetés törölve" - "Beszélgetés törölve. Másik Messaging-beszélgetés megjelenítéséhez érintse meg." - "Letiltva" - "Letiltás visszavonva" - "Kevés a tárhely. Lehet, hogy bizonyos adatok elvesztek." - "Mellékletek kiválasztása" - "Kiválasztás megerősítése" - "%d kiválasztva" - "Távolítson el legalább egy mellékletet, majd próbálja újra." - "Megpróbálhatja elküldeni az üzenetet, de előfordulhat, hogy az csak akkor lesz kézbesíthető, ha legalább egy mellékletet eltávolít." - "Üzenetenként csak egy videót lehet küldeni. Távolítsa el a többi videót, és próbálja újra." - "A Messaging nem tudta betölteni a mellékletet." - "Küldés mindenképpen" - "Nem sikerült elindítani a beszélgetést" - "%1$s (%2$s)" - "%s kiválasztva" - diff --git a/res/values-hy-rAM/arrays.xml b/res/values-hy-rAM/arrays.xml deleted file mode 100644 index d520d07..0000000 --- a/res/values-hy-rAM/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "անվերնագիր" - "անվերնագիր" - - - "Այո" - "Ոչ" - "Լավ" - "Հե-հե" - "Շնորհակալություն" - "Համաձայն եմ" - "Գերազանց է" - "Ճանապարհին եմ" - "Լավ, կմիանամ ձեզ ավելի ուշ" - ":)" - ":(" - - diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml deleted file mode 100644 index 4d01fef..0000000 --- a/res/values-hy-rAM/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Հաղորդակցում" - "Հաղորդակցում" - "Ընտրեք խոսակցությունը" - "Կարգավորումներ" - "Ուղարկել հաղորդագրություն" - "Ավելացնել կցորդ" - "Օգնություն" - "Բարի գալուստ" - "Բաց թողնել" - "Հաջորդը >" - "Հաջորդը" - "Ելք" - "Կարգավորումներ >" - "Կարգավորումներ" - "Հաղորդակցում հավելվածին հարկավոր է թույլտվություն SMS-ներին, հեռախոսին ու կոնտակտներին:" - "Կարող եք փոխել թույլտվությունները՝ անցնելով Կարգավորումներ > Հավելվածներ > Հաղորդակցում > Թույլտվություններ:" - "Կարող եք փոխել թույլտվությունները՝ անցնելով Կարգավորումներ, Հավելվածներ, Հաղորդակցում, Թույլտվություններ:" - "Հաճախակի օգտագործվող կոնտակտներ" - "Բոլոր կոնտակտները" - "Ուղարկել %s համարին" - "Լուսանկարել կամ տեսագրել" - "Ընտրեք պատկերներ այս սարքից" - "Ձայնագրել ձայնանյութ" - "Ընտրել լուսանկար" - "Մեդիա ֆայլն ընտրված է:" - "Մեդիա ֆայլն ապընտրված է:" - "%d ընտրված" - "պատկեր %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "պատկեր" - "Ձայնագրել ձայնանյութ" - "Տարածել" - "Հենց հիմա" - "Այժմ" - - %d րոպե - %d րոպե - - - %d ժամ - %d ժամ - - - %d օր - %d օր - - - %d շաբաթ - %d շաբաթ - - - %d ամիս - %d ամիս - - - %d տարի - %d տարի - - "Class 0 հաղորդագրություն" - "Պահել" - "Սարքի վրա բավարար ազատ տարածք չկա: Հաղորդակցում հավելվածը ինքնաշխատորեն կջնջի հին հաղորդագրությունները, որպեսզի տեղ ազատի:" - "Պահոցային տարածքը սպառվում է" - "Հաղորդակցումը չի կարող ուղարկել կամ ստանալ հաղորդագրություններ, քանի դեռ սարքում բավարար տարածք չկա:" - "SMS պահոցի ծավալը փոքր է: Հնարավոր է՝ հաղորդագրություններ ջնջելու կարիք լինի:" - "Հաստատեք ձեր հեռախոսահամարը" - "Սա մեկանգամյա քայլ է, որի շնորհիվ Հաղորդակցումը անսխալ կուղարկի ձեր խմբային հաղորդագրությունները:" - "Հեռախոսահամար" - "Ջնջել մեդիաֆայլեր պարունակող բոլոր հաղորդագրությունները" - "Ջնջել %s-ից հին հաղորդագրությունները" - "Ավտոմատ ջնջել %s-ից հին հաղորդագրությունները" - "Անտեսել" - "Ջնջե՞լ մեդիա պարունակող բոլոր հաղորդագրությունները:" - "Ջնջե՞լ %s-ից հին հաղորդագրությունները:" - "Ջնջե՞լ %s-ից հին հաղորդագրությունները և միացնել ավտոմատ հեռացումը:" - "%s-ը ասել է" - "Դուք ասել եք" - "Հաղորդագրություն %s-ից" - "Դուք հաղորդագրություն եք ուղարկել" - "Ուղարկվում է…" - "Չի ուղարկվել: Հպեք՝ կրկին փորձելու համար:" - "Չի ուղարկվել: Փորձում է կրկին…" - "Կրկին ուղարկել թե ջնջել" - "Խնդրում ենք հեռախոսազանգ կատարել արտակարգ իրավիճակների ծառայություններին: Ձեր տեքստային հաղորդագրությունն այս պահին չի կարող առաքվել:" - "Ձախողվեց" - "Նոր MMS հաղորդագրություն՝ ներբեռնելու հաար" - "Նոր MMS հաղորդագրություն" - "Չհաջողվեց ներբեռնել" - "Հպեք՝ նորից փորձելու համար" - "Հպեք՝ ներբեռնելու համար" - "Ներբեռնել թե ջնջել" - "Ներբեռնում…" - "Հաղորդագրության ժամկետը սպառվել է կամ հասանելի չէ" - "չափը՝ %1$s , պիտանիությունը՝ %2$s" - "Հնարավոր չէ ուղարկել: Ստացողը վավեր չէ:" - "Ծառայությունն ակտիվացված չէ ցանցում" - "Հնարավոր չէ ուղարկել ցանցային խնդրի պատճառով" - "Հաղորդագրության ժամկետը սպառվել է կամ հասանելի չէ" - "(Անվերնագիր)" - "Անհայտ ուղարկող" - "Առաքված է" - "Հնարավոր չէ ներբեռնել %1$s հաղորդագրությունը %2$s-ից:" - "Չստացվեց ավարտել տվյալների շտեմարանի աշխատանքը՝ փոքր հիշողության պատճառով" - "Հաղորդագրությունը չի ուղարկվել" - "Հաղորդակցման որոշ հաղորդագրություններ չեն ուղարկվել" - - %d հաղորդագրություն %d խոսակցությունում - %d հաղորդագրություն %d խոսակցությունում - - "Հաղորդագրությունը չի ներբեռնվել" - "Որոշ հաղորդագրություններ չեն ներբեռնվել Հաղորդակցման մեջ" - - %d հաղորդագրություն %d խոսակցությունում - %d հաղորդագրություն %d խոսակցությունում - - "Հաղորդագրությունը %1$s համարին չի ուղարկվել" - "Խնդրում ենք հեռախոսազանգ կատարել արտակարգ իրավիճակների ծառայություններին: Ձեր տեքստային հաղորդագրությունն այս պահին չի կարող առաքվել %1$s համարին:" - - %d նոր հաղորդագրություն - %d նոր հաղորդագրություն - - "Սկսել" - "Խցիկն անհասանելի է" - "Խցիկն անհասանելի է" - "Տեսանյութը հասանելի չէ" - "Հնարավոր չէ պահել մեդիա ֆայլը" - "Չհաջողվեց լուսանկարել" - "Հետ" - "Արխիվացված" - "Ջնջել" - "Արխիվ" - "Ապարխիվացնել" - "Անջատել ծանուցումները" - "Միացնել ծանուցումները" - "Ավելացնել կոնտակտ" - "Ներբեռնել" - "Ուղարկել" - "Ջնջել" - "Ջնջե՞լ այս հաղորդագրությունը" - "Այս գործողությունը չի կարող հետարկվել:" - "Ջնջել" - - Ջնջե՞լ այս խոսակցությունները: - Ջնջե՞լ այս խոսակցությունները: - - "Ջնջել" - "Չեղարկել" - "Ստացող`" - "Ընտրել մի քանի պատկեր" - "Հաստատել ընտրությունը" - "+%d" - "Հնարավոր չէ ձայնագրել: Կրկին փորձեք:" - "Հնարավոր չէ նվագարկել աուդիո ֆայլը: Կրկին փորձեք:" - "Հնարավոր չէր պահել աուդիո ֆայլը: Կրկին փորձեք:" - "Հպել և պահել" - ", " - " " - ": " - " " - "Նկար" - "Ձայնահոլովակ" - "Տեսանյութ" - "Կոնտակտային քարտ" - "Ներբեռնել" - "Պատասխանել SMS-ով" - "Պատասխանել MMS-ով" - "Պատասխանել" - - %d մասնակից - %d մասնակից - - "Ես" - "Կոնտակտն արգելափակված և արխիվացված է" - "Կոնտակտն արգելաբացվեց և ապաարխիվացվեց" - "Արխիվացվեց %d խոսակցություն" - "Ապաարխիվացվեց %d խոսակցություն" - "Ծանուցումներն անջատվեցին" - "Ծանուցումները միացվեցին" - "Սահմանված է: Հպեք Ուղարկել կրկին:" - "Հաղորդակցումը դարձավ SMS-ների կանխադրված հավելված:" - - Հեռացնել կցորդները - Հեռացնել կցորդները - - "Կցված ձայնային ֆայլ" - "Նվագարկել աուդիո կցորդը" - "Դադարեցնել" - "Հաղորդագրություն %s-ից՝ %s:" - "Չառաքված հաղորդագրություն %s-ից՝ %s: Ժամանակ՝ %s:" - "Հաղորդագրություն %s-ից՝ %s: Ժամանակ՝ %s:" - "%s կոնտակտին չուղարկված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "Հաղորդագրության ուղարկում %s կոնտակտին՝ %s: Ժամանակ՝ %s:" - "%s կոնտակտին չառաքված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "%s կոնտակտին հասցեագրված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "Չառաքված հաղորդագրություն %s-ից՝ %s: Ժամանակ՝ %s: %s:" - "Հաղորդագրություն %s-ից՝ %s: Ժամանակ՝ %s: %s:" - "%s խմբին չուղարկված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "Հաղորդագրության ուղարկում %s խմբին՝ %s: Ժամանակ՝ %s:" - "%s խմբին չառաքված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "%s խմբին հասցեագրված հաղորդագրություն՝ %s: Ժամանակ՝ %s:" - "Հաղորդագրությունը չի առաքվել: Հպեք՝ նորից փորձելու համար:" - "Զրույց %s-ի հետ" - "Ջնջել վերնագիրը" - "Տեսագրել" - "Նկարահանել ստատիկ պատկեր" - "Լուսանկարել" - "Սկսել տեսաձայնագրումը" - "Անցնել լիաէկրան ֆոտոխցիկի" - "Փոխարկել դիմացի և հետևի տեսախցիկները" - "Կանգնեցնել ձայնագրումը և կցել տեսանյութը" - "Դադարեցնել տեսանկարահանումը" - "Հաղորդակցման լուսանկարներ" - - «%s» ալբոմում պահվել է %d լուսանկար - «%s» ալբոմում պահվել է %d լուսանկար - - - «%s» ալբոմում պահվել է %d տեսանյութ - «%s» ալբոմում պահվել է %d տեսանյութ - - - «%s» ալբոմում պահվել է %d կցորդ - «%s» ալբոմում պահվել է %d կցորդ - - - «Ներբեռնումներ» պանակում պահվել է %d կցորդ - «Ներբեռնումներ» պանակում պահվել է %d կցորդ - - - %d կցորդ պահվեց - %d կցորդ պահվեց - - - Չհաջողվեց պահել %d կցորդ - Չհաջողվեց պահել %d կցորդ - - "MMS կցորդը պահվեց" - "Կարգավորումներ" - "Արխիվացված" - "Փակել" - "MMS" - "Հավելյալ" - "Կարգաբերում" - "Ծանուցումներ" - "Ձայն" - "Լուռ" - "Թրթռոց" - "Արգելափակված է" - "SMS-ի առաքման հաշվետվություններ" - "Առաքման ծանուցում հայցել յուրաքանչյուր ուղարկվող հաղորդագրության համար" - "Ինքնաառբերում" - "Ավտոմատ առբերել MMS հաղորդագրությունները" - "Ավտոմատ առբերում ռոումինգում" - "Ինքնաշխատորեն առբերել MMS-ը ռոումինգում" - "Խմբային հաղորդագրություններ" - "Օգտագործել MMS՝ մեկ հաղորդագրությունը մի քանի ստացողների ուղարկելու համար" - "SMS-ների կանխադրված հավելված" - "SMS-ների կանխադրված հավելված" - - - "Ձեր հեռախոսահամարը" - "Անհայտ է" - "Ելից հաղորդագրությունների ձայներ" - "Տեղափոխել SMS հաղորդագրությունները" - "Տեղափոխել ստացված SMS հաղորդագրությունների չմշակված տվյալները արտաքին հիշողության մեջ" - "Տեղափոխել MMS հաղորդագրությունները" - "Տեղափոխել ստացված MMS հաղորդագրությունների չմշակված տվյալները արտաքին հիշողության մեջ" - "Ծանուցումներ անլար կապի միջոցով" - "Հաղորդագրության ընտրանքներ" - "Պատճենել տեքստը" - "Տեսնել մանրամասները" - "Ջնջել" - "Առաջ" - "Հաղորդագրության մանրամասները" - "Տեսակ՝ " - "Տեքստային հաղորդագրություն" - "Մուլտիմեդիա հաղորդագրություն" - "Ումից` " - "Ում` " - "Ուղարկվել է՝ " - "Ստացվել է՝ " - "Վերնագիր՝ " - "Չափ՝ " - "Առաջնահերթություն՝ " - "SIM՝ " - "Բարձր" - "Սովորական" - "Ցածր" - "SIM %s" - "Թաքնված ուղարկողի հասցեն" - "Կցորդները բեռնելիս հնարավոր չէ ուղարկել հաղորդագրությունը:" - "Հնարավոր չէ բեռնել կցորդը: Փորձեք կրկին:" - "Ցանցը պատրաստ չէ: Նորից փորձեք:" - "Ջնջել տեքստը" - "Փոխարկել թվեր և տեքստ մուտքագրելու ռեժիմների միջև" - "Ավելացնել այլ մասնակիցների" - "Հաստատեք մասնակիցներին" - "Սկսել նոր խոսակցություն" - "Ընտրել այս նյութը" - "Նվագարկել տեսանյութը" - "Մարդիկ և ընտրանքներ" - "Կարգաբերել" - "Մարդիկ և ընտրանքներ" - "Ընդհանուր" - "Այս խոսակցության մասնակիցները" - "Զանգահարել" - "Ուղարկել հաղորդագրություն" - "Ուղարկել հաղորդագրություն<br/><small>%s-ից</small>" - - Ուղարկել լուսանկարները - Ուղարկել լուսանկարները - - - Ուղարկել ձայնագրությունները - Ուղարկել ձայնագրությունները - - - Ուղարկել տեսանյութերը - Ուղարկել տեսանյութերը - - - Ուղարկել կոնտակտի քարտերը - Ուղարկել կոնտակտի քարտերը - - - Ուղարկել կցորդները - Ուղարկել կցորդները - - - %d կցորդ պատրաստ է ուղարկելու համար - %d կցորդ պատրաստ է ուղարկելու համար - - "Ուղարկել կարծիք" - "Դիտել Google Play Store-ում" - "Տեղեկություններ տարբերակի մասին" - "Տարբերակ %1$s" - "Բաց աղբյուրով ծրագրերի լիցենզիաներ" - "Ծանուցումներ" - "Կցորդի սահմանաչափը լրացել է" - "Չհաջողվեց բեռնել կցորդը:" - "Ավելացնե՞լ կոնտակտներում" - "Ավելացնել կոնտակտ" - "Թեմա" - "Թեմա՝ " - "%s%s" - "Կոնտակտի քարտը բեռնվում է" - "Հնարավոր չէ բեռնել կոնտակտի քարտը" - "Դիտել կոնտակտի քարտը" - - %d կոնտակտ - %d կոնտակտ - - "Կոնտակտների քարտեր" - "Ծննդյան օր" - "Գրառումներ" - "Փոխանցել հաղորդագրությունը" - "Պատասխանել" - "+1" - "+%d" - "SMS-ն անջատված է" - "Հաղորդագրություններ ուղարկելու համար դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված" - "Դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված" - "Փոխել" - "Հաղորդագրություններ ստանալու համար դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված" - "Դուք չեք նշել նախընտրելի SIM քարտը՝ SMS հաղորդագրություններն ուղարկելու համար" - "Այս հավելվածը սարքի սեփականատիրոջ կողմից թույլատրված չէ:" - "Լավ" - "Չափից շատ մասնակիցներ կան զրույցին" - - Անվավեր կոնտակտներ - Անվավեր կոնտակտներ - - "Չհաջողվեց բեռնել տեսախցիկի պատկերը" - "Դուք՝ " - "%s՝ " - "Սևագիր" - "Հենց նոր զրույց սկսեք, այն կտեսնեք այս ցուցակում" - "Արխիվացված խոսակցությունները հայտնվում են այստեղ" - "Խոսակցությունները բեռնվում են…" - "Նկար" - "Ձայնահոլովակ" - "Տեսանյութ" - "Կոնտակտային քարտ" - "MMS" - "Հետարկել" - "Կրկնել" - "Մուտքագրեք կոնտակտի անունը կամ հեռախոսահամարը՝ նոր հաղորդագրություն սկսելու համար" - "Արգելափակել" - "Արգելափակել %s-ին" - "Արգելաբացել %s-ին" - "Արգելափակե՞լ %s-ը:" - "Դուք կշարունակեք ստանալ հաղորդագրություններ այս հեռախոսահամարից, բայց չեք ծանուցվի դրանց մասին: Այս խոսակցությունը կարխիվացվի:" - "Արգելափակված կոնտակտներ" - "ԱՐԳԵԼԱԲԱՑԵԼ" - "Արգելափակված կոնտակտներ" - "Ընտրեք պատկեր՝ փաստաթղթերի գրադարանից" - "Հաղորդագրությունն ուղարկվում է" - "Հաղորդագրությունն ուղարկվել է" - "Բջջային տվյալների կապն անջատված է: Ստուգեք ձեր կարգավորումները:" - "Հնարավոր չէ ուղարկել հաղորդագրություններ Թռիչքի ռեժիմում" - "Հաղորդագրությունը չհաջողվեց ուղարկել" - "Հաղորդագրությունը բեռնվել է" - "Բջջային տվյալների կապն անջատված է: Ստուգեք ձեր կարգավորումները:" - "Հնարավոր չէ ներբեռնել հաղորդագրությունները Ինքնաթիռի ռեժիմում" - "Հաղորդագրությունը չներբեռնվեց" - "Զրո" - "Մեկ" - "Երկու" - "Երեք" - "Չորս" - "Հինգ" - "Վեց" - "Յոթ" - "Ութ" - "Ինը" - "Հնարավոր չէ ուղարկել հաղորդագրությունը %1$s-ի միջոցով: Սխալի կոդ՝ %2$d" - "Հնարավոր չէ ուղարկել հաղորդագրությունը անհայտ օպերատորի միջոցով: Սխալի կոդ՝ %1$d" - "Փխնց՝ %s" - "Հաղորդագրությունը չի ուղարկվել. Ծառայությունն ակտիվացված չէ ցանցում" - "Հաղորդագրությունը չի ուղարկվել. ստացողի հասցեն սխալ է" - "Հաղորդագրությունը չի ուղարկվել. անվավեր հաղորդագրություն" - "Հաղորդագրությունը չի ուղարկվել. չաջակցվող բովանդակություն" - "Հաղորդագրությունը չի ուղարկվել. չաջակցվող հաղորդագրություն" - "Հաղորդագրությունը չուղարկվեց: Այն չափազանց մեծ է" - "Նոր հաղորդագրություն" - "Դիտել" - "Պատկեր" - "Հնարավոր չէր գտնել համապատասխան հավելված" - "Հեռացնել ստացողին" - "Նոր հաղորդագրություն" - "Չեղարկել" - "Խմբագրել մուտքի կետը" - "Կարգավորված չէ" - "Անուն" - "APN" - "MMSC" - "MMS պրոքսի" - "MMS միացք" - "MCC" - "MNC" - "APN-ի տեսակը" - "Ջնջել APN-ը" - "Նոր APN" - "Պահել" - "Հրաժարվել" - "Անվանման դաշտը պետք է լրացված լինի:" - "APN-ը չի կարող դատարկ լինել:" - "MCC դաշտը պետք է 3 նիշ ունենա:" - "MNC դաշտը պետք է լինի առնվազն 2 կամ 3 թվանշան:" - "Լռելյայն APN կարգավորումների վերականգնում:" - "Վերականգնել կանխադրվածը" - "Սկզբնական APN կարգավորումների վերակարգավորումն ավարտված է:" - "Անվերնագիր" - "Մատչման կետերի անունները" - "APN-ներ" - "Նոր APN" - "Մատչման կետի անվան կարգավորումները հասանելի չեն այս օգտվողին" - "Պատճենել սեղմատախտակին" - "Պատճենել" - "%s-ին" - "Ընդհանուր" - "Հավելյալ" - "Ընդհանուր կարգավորումներ" - "Ընդլայնված կարգավորումներ" - %s» SIM" - "Ուղարկել անհատական ​​SMS հաղորդագրություններ բոլոր ստացողներին: Միայն դուք կստանաք պատասխաններ" - "Բոլոր ստացողներին ուղարկել առանձին MMS" - "Անհայտ համար" - "Նոր հաղորդագրություն" - "Նոր հաղորդագրություն:" - "SIM քարտի ընտրում" - "Ընտրված է %1$s-ը, SIM քարտի ընտրում" - "Խմբագրել վերնագիրը" - "Ընտրել SIM քարտ կամ խմբագրել վերնագիրը" - "Հպեք և պահեք՝ ձայնագրելու համար" - "Սկսել նոր խոսակցություն" - "Հաղորդակցում" - "Հաղորդակցման ցանկ" - "Հաղորդակցում" - "Նոր հաղորդագրություն" - "Խոսակցությունների ցանկ" - "Խոսակցությունները բեռնվում են" - "Հաղորդագրությունների բեռնում" - "Տեսնել այլ զրույցներ" - "Դիտել ավելի շատ հաղորդագրություններ" - "Զրույցը ջնջված է" - "Խոսակցությունը ջնջվել է: Հպեք՝ Հաղորդակցման մեջ այլ խոսակցություն դիտելու համար" - "Արգելափակված է" - "Ապաարգելափակված է" - "Ազատ տարածքը բավարար չէ: Որոշ տվյալներ չեն պահվի:" - "Ընտրել կցորդներ" - "Հաստատել ընտրությունը" - "%d ընտրված" - "Հեռացրեք մեկ կամ ավելի կցորդ, ապա նորից փորձեք:" - "Կարող եք փորձել ուղարկել հաղորդագրությունը, սակայն այն չի կարող առաքվել, եթե չհեռացնեք մեկ կամ ավելի կցորդ:" - "Ամեն մի հաղորդագրության համար կարող եք միայն մեկ տեսանյութ ուղարկել: Հեռացրեք ավելորդ տեսանյութերը և նորից փորձեք:" - "Կցորդը չբեռնվեց:" - "Ուղարկել ամեն դեպքում" - "Չհաջողվեց սկսել խոսակցությունը" - "%1$s (%2$s)" - "Ընտրված է %s քարտը" - diff --git a/res/values-in/arrays.xml b/res/values-in/arrays.xml deleted file mode 100644 index ce2892e..0000000 --- a/res/values-in/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "tanpa subjek" - "nosubject" - - - "Ya" - "Tidak" - "Oke" - "Hehe" - "Terima kasih" - "Saya setuju" - "Bagus" - "Dalam perjalanan" - "Oke, saya akan menghubungi Anda nanti" - ":)" - ":(" - - diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml deleted file mode 100644 index 3529301..0000000 --- a/res/values-in/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Perpesanan" - "Perpesanan" - "Pilih percakapan" - "Setelan" - "Kirim Pesan" - "Tambahkan lampiran" - "Bantuan" - "Selamat Datang" - "Lewati" - "Berikutnya >" - "Berikutnya" - "Keluar" - "Setelan >" - "Setelan" - "Perpesanan perlu izin ke SMS, Telepon, dan Kontak" - "Anda dapat mengubah izin di Setelan > Aplikasi > Perpesanan > Izin." - "Anda dapat mengubah izin di Setelan, Aplikasi, Messaging, Permissions." - "Sering" - "Semua kontak" - "Kirim ke %s" - "Jepret gambar atau rekam video" - "Pilih gambar dari perangkat ini" - "Rekam audio" - "Pilih foto" - "Media ini dipilih." - "Media ini batal dipilih." - "%d dipilih" - "gambar %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "gambar" - "Rekam audio" - "Bagikan" - "Baru saja" - "Sekarang" - - %d mnt - %d mnt - - - %d jam - %d jam - - - %d hari - %d hari - - - %d minggu - seminggu - - - %d bulan - sebulan - - - %d tahun - setahun - - "Pesan kelas 0" - "Simpan" - "Perangkat hampir kehabisan ruang. Perpesanan akan secara otomatis menghapus pesan lama untuk mengosongkan ruang." - "Ruang penyimpanan hampir habis" - "Perpesanan tidak dapat mengirim atau menerima pesan hingga lebih banyak ruang tersedia di perangkat Anda." - "Penyimpanan SMS tinggal sedikit. Anda perlu menghapus pesan." - "Konfirmasi nomor telepon Anda" - "Langkah satu kali ini akan memastikan bahwa Perpesanan mengirim pesan grup dengan tepat." - "Nomor telepon" - "Hapus semua pesan dengan media" - "Hapus pesan yang sudah lebih dari %s" - "Hapus otomatis pesan yang sudah lebih dari %s" - "Abaikan" - "Hapus semua pesan dengan media?" - "Hapus pesan yang sudah lebih dari %s?" - "Hapus pesan yang sudah lebih dari %s dan aktifkan fitur hapus otomatis?" - "%s mengatakan" - "Anda mengatakan" - "Pesan dari %s" - "Anda telah mengirim pesan" - "Mengirim…" - "Tidak dikirim. Sentuh untuk mencoba lagi." - "Tidak dikirim. Mencoba lagi..." - "Kirim ulang atau hapus" - "Lakukan panggilan suara ke layanan darurat. SMS Anda tidak dapat dikirim saat ini." - "Gagal" - "Pesan MMS baru untuk diunduh" - "Pesan MMS baru" - "Tidak dapat mengunduh" - "Sentuh untuk mencoba lagi" - "Sentuh untuk mengunduh" - "Unduh atau hapus" - "Mengunduh..." - "Pesan sudah tidak berlaku atau tidak tersedia" - "ukuran: %1$s, waktu berakhir: %2$s" - "Tidak dapat mengirim. Penerima tidak valid." - "Layanan tidak diaktifkan pada jaringan" - "Tidak dapat mengirim karena masalah jaringan" - "Pesan sudah tidak berlaku atau tidak tersedia" - "(Tanpa subjek)" - "Pengirim tidak diketahui" - "Telah Diantar" - "Tidak dapat mengunduh pesan %1$s dari %2$s." - "Tidak dapat menyelesaikan operasi basis data karena memori terlalu kecil" - "Pesan tidak dikirim" - "Beberapa pesan tidak terkirim di Perpesanan" - - %d pesan dalam %d percakapan - %d pesan dalam satu percakapan - - "Pesan tidak diunduh" - "Beberapa pesan tidak diunduh di Perpesanan" - - %d pesan dalam %d percakapan - %d pesan dalam satu percakapan - - "Pesan ke %1$s tidak terkirim" - "Lakukan panggilan suara ke layanan darurat. SMS Anda ke %1$s tidak dapat dikirim saat ini." - - %d pesan baru - Pesan baru - - "Mulai" - "Tidak ada kamera" - "Tidak ada kamera" - "Tidak ada rekaman video" - "Tidak dapat menyimpan media" - "Tidak dapat memfoto" - "Kembali" - "Diarsipkan" - "Hapus" - "Arsipkan" - "Batalkan pengarsipan" - "Nonaktifkan pemberitahuan" - "Aktifkan pemberitahuan" - "Tambahkan kontak" - "Unduh" - "Kirim" - "Hapus" - "Hapus pesan ini?" - "Tindakan ini tidak dapat diurungkan." - "Hapus" - - Hapus percakapan ini? - Hapus percakapan ini? - - "Hapus" - "Batal" - "Kepada" - "Pilih beberapa gambar" - "Konfirmasi pilihan" - "+%d" - "Tidak dapat merekam audio. Coba lagi." - "Tidak dapat memutar audio. Coba lagi." - "Tidak dapat menyimpan audio. Coba lagi." - "Sentuh & tahan" - ", " - " " - ": " - " " - "Gambar" - "Klip audio" - "Video" - "Kartu kontak" - "Unduh" - "Balas via SMS" - "Balas via MMS" - "Balas" - - %d peserta - %d peserta - - "Saya" - "Kontak diblokir & diarsipkan" - "Kontak tidak diblokir lagi & batal diarsipkan" - "%d diarsipkan" - "%d batal diarsipkan" - "Notifikasi dinonaktifkan" - "Notifikasi diaktifkan" - "Semua siap. Sentuh Kirim lagi." - "Perpesanan berhasil disetel sebagai aplikasi SMS default." - - Buang lampiran - Buang lampiran - - "Lampiran audio" - "Putar lampiran audio" - "Jeda" - "Pesan dari %s: %s." - "Pesan gagal dari %s: %s. Waktu: %s." - "Pesan dari %s: %s. Waktu: %s." - "Pesan yang tidak terkirim ke %s: %s. Waktu: %s." - "Mengirim pesan ke %s: %s. Waktu: %s." - "Pesan gagal ke %s: %s. Waktu: %s." - "Pesan ke %s: %s. Waktu: %s." - "Pesan gagal dari %s: %s. Waktu: %s. %s." - "Pesan dari %s: %s. Waktu: %s. %s." - "Pesan yang tidak terkirim ke %s: %s. Waktu: %s." - "Mengirim pesan ke %s: %s. Waktu: %s." - "Pesan gagal ke %s: %s. Waktu: %s." - "Pesan ke %s: %s. Waktu: %s." - "Pesan gagal. Sentuh untuk mencoba lagi." - "Percakapan dengan %s" - "Hapus subjek" - "Ambil video" - "Ambil gambar diam" - "Ambil gambar" - "Mulai merekam video" - "Beralih ke kamera layar penuh" - "Beralih antara kamera depan dan belakang" - "Hentikan perekaman dan lampirkan video" - "Berhenti merekam video" - "Foto Perpesanan" - - %d foto disimpan ke album \"%s\" - %d foto disimpan ke album \"%s\" - - - %d video disimpan ke album \"%s\" - %d video disimpan ke album \"%s\" - - - %d lampiran disimpan ke album \"%s\" - %d lampiran disimpan ke album \"%s\" - - - %d lampiran disimpan ke \"Unduhan\" - %d lampiran disimpan ke \"Unduhan\" - - - %d lampiran disimpan - %d lampiran disimpan - - - Tidak dapat menyimpan %d lampiran - Tidak dapat menyimpan %d lampiran - - "Lampiran MMS yang disimpan" - "Setelan" - "Diarsipkan" - "Tutup" - "MMS" - "Lanjutan" - "Debug" - "Notifikasi" - "Suara" - "Senyap" - "Getar" - "Diblokir" - "Laporan pengiriman SMS" - "Minta laporan pengiriman dari setiap SMS yang Anda kirimkan" - "Ambil otomatis" - "Otomatis ambil pesan MMS" - "Ambil otomatis saat roaming" - "Otomatis ambil MMS saat roaming" - "Perpesanan grup" - "Gunakan MMS untuk mengirimkan pesan tunggal saat ada beberapa penerima" - "Aplikasi SMS default" - "Aplikasi SMS default" - - - "Nomor telepon Anda" - "Tidak dikenal" - "Pesan suara keluar" - "Buang SMS" - "Buang data mentah SMS yang diterima ke dalam file penyimpanan eksternal" - "Buang MMS" - "Buang data mentah MMS yang diterima ke file penyimpanan eksternal" - "Lansiran nirkabel" - "Opsi pesan" - "Salin teks" - "Lihat detail" - "Hapus" - "Teruskan" - "Detail pesan" - "Jenis: " - "Pesan teks" - "Pesan multimedia" - "Dari: " - "Kepada: " - "Dikirim: " - "Diterima: " - "Subjek: " - "Ukuran: " - "Prioritas: " - "SIM: " - "Tinggi" - "Normal" - "Rendah" - "SIM %s" - "Alamat pengirim tersembunyi" - "Tidak dapat mengirim pesan saat memuat lampiran." - "Tidak dapat memuat lampiran. Coba lagi." - "Jaringan tidak siap. Coba lagi." - "Hapus teks" - "Beralih antara memasukkan teks dan angka" - "Tambahkan peserta lainnya" - "Konfirmasi peserta" - "Mulai percakapan baru" - "Pilih item ini" - "Putar video" - "Orang & opsi" - "Debug" - "Orang & opsi" - "Umum" - "Orang dalam percakapan ini" - "Lakukan panggilan telepon" - "Kirim pesan" - "Kirim pesan<br/><small>dari %s</small>" - - Kirim foto - Kirim foto - - - Kirim audio - Kirim audio - - - Kirim video - Kirim video - - - Kirim kartu kontak - Kirim kartu kontak - - - Kirim lampiran - Kirim lampiran - - - %d lampiran siap dikirim - Satu lampiran siap dikirim - - "Kirim masukan" - "Lihat di Google Play Store" - "Info versi" - "Versi %1$s" - "Lisensi sumber terbuka" - "Notifikasi" - "Batas lampiran tercapai" - "Gagal memuat lampiran." - "Tambahkan ke Kontak?" - "Tambahkan Kontak" - "Subjek" - "Subjek: " - "%s%s" - "Memuat kartu kontak" - "Tidak dapat memuat kartu kontak" - "Lihat kartu kontak" - - %d kontak - %d kontak - - "Kartu kontak" - "Ulang Tahun" - "Catatan" - "Teruskan pesan" - "Balas" - "+1" - "+%d" - "SMS dinonaktifkan" - "Untuk mengirim, setel Perpesanan sebagai aplikasi SMS default" - "Setel Perpesanan sebagai aplikasi SMS default" - "Ganti" - "Untuk menerima pesan, setel Perpesanan sebagai aplikasi SMS default." - "Tidak ada SIM yang dipilih untuk mengirim pesan SMS" - "Aplikasi ini tidak diizinkan oleh pemilik perangkat." - "Oke" - "Terlalu banyak peserta dalam percakapan" - - Kontak tidak valid - Kontak tidak valid - - "Tidak dapat memuat gambar kamera" - "Anda: " - "%s: " - "Draf" - "Setelah memulai percakapan baru, Anda akan melihatnya tercantum di sini" - "Arsip percakapan muncul di sini" - "Memuat percakapan..." - "Gambar" - "Klip audio" - "Video" - "Kartu kontak" - "MMS" - "Urungkan" - "Coba lagi" - "Masukkan nama atau nomor telepon yang dapat dihubungi untuk memulai pesan baru" - "Blokir" - "Blokir %s" - "Bebaskan %s" - "Blokir %s?" - "Anda akan terus menerima pesan dari nomor ini, tetapi tidak akan diberi tahu lagi. Percakapan ini akan diarsipkan." - "Kontak yang diblokir" - "BEBASKAN" - "Kontak yang diblokir" - "Pilih gambar dari perpustakaan dokumen" - "Mengirim pesan" - "Pesan terkirim" - "Data seluler dinonaktifkan. Periksa setelan." - "Tidak dapat mengirim pesan dalam mode Pesawat" - "Pesan tidak dapat dikirim" - "Pesan diunduh" - "Data seluler dinonaktifkan. Periksa setelan." - "Tidak dapat mengunduh pesan dalam mode Pesawat" - "Pesan tidak dapat diunduh" - "Nol" - "Satu" - "Dua" - "Tiga" - "Empat" - "Lima" - "Enam" - "Tujuh" - "Delapan" - "Sembilan" - "Tidak dapat mengirim pesan dengan %1$s, kesalahan %2$d" - "Tidak dapat mengirim pesan dengan operator tak dikenal, kesalahan %1$d" - "Trs: %s" - "Pesan tidak terkirim: layanan tidak diaktifkan di jaringan" - "Pesan tidak terkirim: alamat tujuan tidak valid" - "Pesan tidak terkirim: pesan tidak valid" - "Pesan tidak terkirim: konten tidak didukung" - "Pesan tidak terkirim: pesan tidak didukung" - "Pesan tidak terkirim: terlalu besar" - "Pesan baru" - "Lihat" - "Gambar" - "Aplikasi yang sesuai tidak dapat ditemukan" - "Hapus penerima" - "Pesan baru" - "Batal" - "Edit titik akses" - "Belum disetel" - "Nama" - "APN" - "MMSC" - "Proxy MMS" - "Port MMS" - "MCC" - "MNC" - "Jenis APN" - "Hapus APN" - "APN baru" - "Simpan" - "Buang" - "Bidang Nama wajib diisi." - "APN wajib diisi." - "Bidang MCC harus terdiri dari 3 digit." - "Bidang MNC harus 2 atau 3 digit." - "Memulihkan setelan APN default." - "Setel ulang ke default" - "Penyetelan ulang setelan APN default selesai." - "Tanpa judul" - "Nama Titik Akses" - "APN" - "APN baru" - "Setelan Nama Titik Akses tidak tersedia untuk pengguna ini" - "Salin ke papan klip?" - "Salin" - "kepada %s" - "Umum" - "Lanjutan" - "Setelan umum" - "Setelan lanjutan" - "SIM \"%s\"" - "Kirimkan pesan SMS individual ke semua penerima. Hanya Anda yang akan mendapatkan balasan" - "Kirimkan satu MMS ke semua penerima" - "Nomor tidak dikenal" - "Pesan baru" - "Pesan baru." - "Pemilih SIM" - "%1$s dipilih, pemilih SIM" - "Edit subjek" - "Pilih SIM atau edit subjek" - "Sentuh dan tahan untuk merekam audio" - "Mulai percakapan baru" - "Perpesanan" - "Daftar Perpesanan" - "Perpesanan" - "Pesan baru" - "Daftar percakapan" - "Memuat percakapan" - "Memuat pesan" - "Lihat percakapan lain" - "Lihat pesan lainnya" - "Percakapan telah dihapus" - "Percakapan dihapus. Sentuh untuk menampilkan percakapan Perpesanan yang berbeda" - "Diblokir" - "Dibebaskan" - "Sedikit ruang penyimpanan. Beberapa data mungkin hilang." - "Pilih lampiran" - "Konfirmasi pilihan" - "%d dipilih" - "Hapus satu atau beberapa lampiran dan coba lagi." - "Anda dapat mencoba mengirim pesan, tetapi pesan tersebut mungkin tidak terkirim, kecuali Anda menghapus satu atau beberapa lampiran." - "Anda hanya dapat mengirim satu video per pesan. Hapus video tambahan dan coba lagi." - "Perpesanan gagal memuat lampiran." - "Tetap kirim" - "Tidak dapat memulai percakapan" - "%1$s (%2$s)" - "%s dipilih" - diff --git a/res/values-is-rIS/arrays.xml b/res/values-is-rIS/arrays.xml deleted file mode 100644 index f8d4803..0000000 --- a/res/values-is-rIS/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ekkert efni" - "ekkertefni" - - - "Já" - "Nei" - "Í lagi" - "Hehe" - "Takk" - "Ég er sammála" - "Flott" - "Er á leiðinni" - "Ókei, ég fæ að hafa samband síðar" - ":)" - ":(" - - diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml deleted file mode 100644 index 4a54e14..0000000 --- a/res/values-is-rIS/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Skilaboð" - "Skilaboð" - "Veldu samtal" - "Stillingar" - "Senda skilaboð" - "Bæta viðhengi við" - "Hjálp" - "Komdu fagnandi" - "Sleppa" - "Áfram >" - "Áfram" - "Hætta" - "Stillingar >" - "Stillingar" - "Skilaboð þurfa heimild til að fá aðgang að SMS-skilaboðum, síma og tengiliðum." - "Þú getur breytt heimildum undir Stillingar > Forrit > Skilaboð > Heimildir." - "Þú getur breytt heimildum undir Stillingar, Forrit, Skilaboð, Heimildir." - "Algengir" - "Allir tengiliðir" - "Senda í %s" - "Taka myndir eða myndskeið" - "Velja myndir úr þessu tæki" - "Taka upp hljóð" - "Velja mynd" - "Efnið er valið." - "Efnið er ekki valið." - "%d valin" - "mynd %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "mynd" - "Taka upp hljóð" - "Deila" - "Rétt í þessu" - "Núna" - - %d mín. - %d mín. - - - %d klukkustund - %d klukkustundir - - - %d dagur - %d dagar - - - %d vika - %d vikur - - - %d mánuður - %d mánuðir - - - %d ár - %d ár - - "Einnota skilaboð" - "Vista" - "Lítið pláss er í tækinu. Skilaboðaforritið mun sjálfkrafa eyða gömlum skilaboðum til að losa um pláss." - "Geymslurýmið er senn á þrotum" - "Ekki er víst að Skilaboð geti sent eða tekið á móti skilaboðum fyrr en meira pláss er til staðar í tækinu." - "SMS-geymsla að fyllast. Þú gætir þurft að eyða skilaboðum." - "Staðfestu símanúmerið þitt" - "Þetta þarf að gera í eitt skipti til að tryggja að Skilaboð komi hópskilaboðunum þínum til skila á réttan hátt." - "Símanúmer" - "Eyða öllum skilaboðum með margmiðlunarefni" - "Eyða skilaboðum sem eru eldri en %s" - "Eyða sjálfkrafa skilaboðum sem eru eldri en %s" - "Hunsa" - "Eyða öllum skilaboðum með margmiðlunarefni?" - "Eyða skilaboðum sem eru eldri en %s?" - "Eyða skilaboðum sem eru eldri en %s og kveikja á sjálfvirkri eyðingu?" - "%s sagði" - "Þú sagðir" - "Skilaboð sem %s sendi" - "Þú sendir skilaboð" - "Sendir…" - "Ekki sent. Snertu til að reyna aftur." - "Ekki sent. Reynir aftur…" - "Endursenda eða eyða" - "Hringdu í neyðarþjónustuna. Ekki tókst að senda textaskilaboðin þín á þessari stundu." - "Mistókst" - "Ný MMS-skilaboð til að sækja" - "Ný MMS-skilaboð" - "Ekki hægt að sækja" - "Snertu til að reyna aftur" - "Snertu til að sækja" - "Sækja eða eyða" - "Sækir…" - "Skilaboð útrunnin eða ekki aðgengileg" - "stærð: %1$s, útrunnið: %2$s" - "Ekki er hægt að senda. Viðtakandinn er ekki gildur." - "Símkerfið hefur ekki gert þjónustuna virka" - "Sending mistókst vegna símkerfisvandamáls" - "Skilaboð útrunnin eða ekki aðgengileg" - "(Ekkert efni)" - "Óþekktur sendandi" - "Sent" - "Ekki var hægt að sækja skilaboðin %1$s sem %2$s sendi." - "Lítið minni varð til þess að ekki var hægt að ljúka gagnagrunnsaðgerðinni" - "Skilaboð ekki send" - "Einhver skilaboð voru ekki send í Skilaboðum" - - %d skilaboð í %d samtali - %d skilaboð í %d samtölum - - "Skilaboð ekki sótt" - "Einhver skilaboð voru ekki sótt í Skilaboð" - - %d skilaboð í %d samtali - %d skilaboð í %d samtölum - - "Skilaboð til %1$s voru ekki send" - "Hringdu í neyðarþjónustuna. Ekki tókst að senda textaskilaboðin þín í númerið %1$s á þessari stundu." - - %d ný skilaboð - %d ný skilaboð - - "Byrja" - "Myndavélin er ekki tiltæk" - "Myndavélin er ekki tiltæk" - "Upptaka myndskeiða er ekki tiltæk" - "Ekki er hægt að vista efni" - "Ekki er hægt að taka mynd" - "Til baka" - "Geymsla" - "Eyða" - "Setja í geymslu" - "Taka úr geymslu" - "Slökkva á tilkynningum" - "Kveikja á tilkynningum" - "Bæta tengilið við" - "Sækja" - "Senda" - "Eyða" - "Eyða þessum skilaboðum?" - "Ekki er hægt að afturkalla þessa aðgerð." - "Eyða" - - Eyða þessum samtölum? - Eyða þessum samtölum? - - "Eyða" - "Hætta við" - "Til" - "Velja margar myndir" - "Staðfesta val" - "+%d" - "Ekki er hægt að taka upp hljóð. Reyndu aftur." - "Ekki er hægt að spila hljóð. Reyndu aftur." - "Ekki var hægt að vista hljóð. Reyndu aftur." - "Haltu inni" - ", " - " " - ": " - " " - "Mynd" - "Hljóðbútur" - "Myndskeið" - "Tengiliðaspjald" - "Sækja" - "Svara með SMS" - "Svara með MMS-skilaboðum" - "Svara" - - %d þátttakandi - %d þátttakendur - - "Ég" - "Tengiliður settur á bannlista og í geymslu" - "Tengiliður tekinn af bannlista og úr geymslu" - "%d sett í geymslu" - "%d tekin úr geymslu" - "Slökkt á tilkynningum" - "Kveikt á tilkynningum" - "Allt til reiðu. Snertu Senda aftur." - "Skilaboð voru valin sem sjálfgefið SMS-forrit." - - Fleygja viðhengjum - Fleygja viðhengjum - - "Hljóðviðhengi" - "Spila hljóðviðhengi" - "Hlé" - "Skilaboð sem %s sendi: %s." - "Misheppnuð skilaboð sem %s sendi: %s. Tími: %s." - "Skilaboð sem %s sendi: %s. Tími: %s." - "Ósend skilaboð til %s: %s. Tími: %s." - "Sendir skilaboð til %s: %s. Tími: %s." - "Misheppnuð skilaboð til %s: %s. Tími: %s." - "Skilaboð til %s: %s. Tími: %s." - "Misheppnuð skilaboð sem %s sendi: %s. Tími: %s. %s." - "Skilaboð sem %s sendi: %s. Tími: %s. %s." - "Ósend skilaboð til %s: %s. Tími: %s." - "Sendir skilaboð til %s: %s. Tími: %s." - "Misheppnuð skilaboð til %s: %s. Tími: %s." - "Skilaboð til %s: %s. Tími: %s." - "Misheppnuð skilaboð. Snertu til að reyna aftur." - "Samtal við %s" - "Eyða efni" - "Taka upp myndskeið" - "Taka kyrrmynd" - "Taka mynd" - "Byrja að taka upp myndskeið" - "Skipta yfir í myndavél á öllum skjánum" - "Skipt á milli fremri og aftari myndavélar" - "Stöðva upptöku og hengja myndskeið við" - "Stöðva upptöku myndskeiðs" - "Skilaboðamyndir" - - %d mynd vistuð í „%s“ möppu - %d myndir vistaðar í „%s“ möppu - - - %d myndskeið vistað í „%s“ möppu - %d myndskeið vistuð í „%s“ möppu - - - %d viðhengi vistað í „%s“ möppu - %d viðhengi vistuð í „%s“ möppu - - - %d viðhengi vistað í „Niðurhal“ - %d viðhengi vistuð í „Niðurhal“ - - - %d viðhengi vistað - %d viðhengi vistuð - - - Ekki tókst að vista %d viðhengi - Ekki tókst að vista %d viðhengi - - "MMS-viðhengi vistað" - "Stillingar" - "Geymsla" - "Loka" - "MMS" - "Ítarlegt" - "Villuleita" - "Tilkynningar" - "Hljóð" - "Hljóðlaust" - "Titringur" - "Á bannlista" - "SMS-skilatilkynningar" - "Óska eftir skilatilkynningu fyrir hver send SMS-skilaboð" - "Sækja sjálfkrafa" - "Sækja MMS-skilaboð sjálfkrafa" - "Sækja sjálfkrafa í reiki" - "Sækja MMS sjálfkrafa í reiki" - "Hópskilaboð" - "Nota MMS til að senda ein skilaboð þegar viðtakendur eru margir" - "Sjálfgefið SMS-forrit" - "Sjálfgefið SMS-forrit" - - - "Símanúmerið þitt" - "Óþekkt" - "Hljóð fyrir send skilaboð" - "Vista SMS-gögn" - "Vista ósniðin SMS-gögn í ytri geymsluskrá" - "Vista MMS-gögn" - "Vista ósniðin MMS-gögn í ytri geymsluskrá" - "Þráðlausar viðvaranir" - "Skilaboðavalkostir" - "Afrita texta" - "Skoða upplýsingar" - "Eyða" - "Áframsenda" - "Skilaboðaupplýsingar" - "Gerð: " - "Textaskilaboð" - "Margmiðlunarskilaboð" - "Frá: " - "Til: " - "Sent: " - "Móttekið: " - "Efni: " - "Stærð: " - "Forgangur: " - "SIM-kort: " - "Hár" - "Venjulegur" - "Lágur" - "SIM-kort %s" - "Falið netfang sendanda" - "Ekki er hægt að senda skilaboð á meðan verið er að hlaða viðhengi." - "Ekki er hægt að hlaða viðhengi. Reyndu aftur." - "Netið er ekki tilbúið. Reyndu aftur." - "Eyða texta" - "Skipta á milli innsláttar texta og talna" - "Bæta við fleiri þátttakendum" - "Staðfesta þátttakendur" - "Byrja nýtt samtal" - "Velja þetta atriði" - "Spila myndskeið" - "Fólk og valkostir" - "Villuleita" - "Fólk og valkostir" - "Almennt" - "Fólk í þessu samtali" - "Hringja símtal" - "Senda skilaboð" - "Senda skilaboð<br/><small>úr %s</small>" - - Senda myndir - Senda myndir - - - Senda hljóð - Senda hljóð - - - Senda myndskeið - Senda myndskeið - - - Senda tengiliðaspjöld - Senda tengiliðaspjöld - - - Senda viðhengi - Senda viðhengi - - - %d viðhengi tilbúið til sendingar - %d viðhengi tilbúin til sendingar - - "Senda ábendingu" - "Skoða í Google Play Store" - "Útgáfuupplýsingar" - "Útgáfa %1$s" - "Leyfi opins kóða" - "Tilkynningar" - "Hámarki viðhengja náð" - "Mistókst að hlaða viðhengið." - "Bæta við tengiliði?" - "Bæta tengilið við" - "Efni" - "Efni: " - "%s%s" - "Hleður tengiliðaspjald" - "Ekki var hægt að hlaða tengiliðaspjald" - "Skoða tengiliðaspjald" - - %d tengiliður - %d tengiliðir - - "Tengiliðaspjöld" - "Fæðingardagur" - "Athugasemdir" - "Áframsenda skilaboð" - "Svara" - "+1" - "+%d" - "Slökkt á SMS" - "Til að senda skaltu velja Skilaboð sem sjálfgefið SMS-forrit" - "Veldu Skilaboð sem sjálfgefið SMS-forrit" - "Breyta" - "Til að fá skilaboð skaltu velja Skilaboð sem sjálfgefið SMS-forrit" - "Ekkert SIM-kort valið fyrir sendingu SMS-skilaboða" - "Eigandi tækisins leyfir ekki þetta forrit." - "Í lagi" - "Of margir þátttakendur í samtali" - - Ógildir tengiliðir - Ógildir tengiliðir - - "Ekki var hægt að hlaða mynd frá myndavél" - "Þú: " - "%s: " - "Drög" - "Þegar þú hefur nýtt samtal birtist það hér" - "Samtöl í geymslu birtast hér" - "Hleður samtöl…" - "Mynd" - "Hljóðbútur" - "Myndskeið" - "Tengiliðaspjald" - "MMS" - "Afturkalla" - "Reyna aftur" - "Sláðu inn nafn eða símanúmer tengiliðar til að búa til ný skilaboð" - "Setja á bannlista" - "Útiloka %s" - "Taka %s af bannlista" - "Loka á %s?" - "Þú heldur áfram að fá skilaboð úr þessu númeri en færð ekki lengur tilkynningar. Þetta samtal verður sett í geymslu." - "Útilokaðir tengiliðir" - "TAKA AF BANNLISTA" - "Útilokaðir tengiliðir" - "Velja mynd úr skjalasafni" - "Sendir skilaboð" - "Skilaboð send" - "Slökkt er á farsímagögnum. Athugaðu stillingarnar." - "Ekki er hægt að senda skilaboð þegar flugstilling er virk" - "Ekki var hægt að senda skilaboðin" - "Skilaboð sótt" - "Slökkt er á farsímagögnum. Athugaðu stillingarnar." - "Ekki er hægt að sækja skilaboð þegar flugstilling er virk" - "Ekki var hægt að sækja skilaboðin" - "Núll" - "Einn" - "Tveir" - "Þrír" - "Fjórir" - "Fimm" - "Sex" - "Sjö" - "Átta" - "Níu" - "Ekki er hægt að senda skilaboð í gegnum %1$s, villa %2$d" - "Ekki er hægt að senda skilaboð í gegnum óþekkt símafyrirtæki, villa %1$d" - "Áfrs.: %s" - "Skilaboðin voru ekki send. Símkerfið hefur ekki gert þjónustuna virka" - "Skilaboðin voru ekki send: Ógildur áfangastaður" - "Skilaboðin voru ekki send: Ógild skilaboð" - "Skilaboðin voru ekki send: Óstutt innihald" - "Skilaboðin voru ekki send: Óstudd skilaboð" - "Skilaboðin voru ekki send: Of stór" - "Ný skilaboð" - "Skoða" - "Mynd" - "Viðeigandi forrit fannst ekki" - "Fjarlægja viðtakanda" - "Ný skilaboð" - "Hætta við" - "Breyta aðgangsstað" - "Ekki stillt" - "Heiti" - "Aðgangsstaður" - "MMSC" - "Proxy-þjónn fyrir MMS" - "MMS-gátt" - "MCC" - "MNC" - "Tegund aðgangsstaðar" - "Eyða aðgangsstað" - "Nýr aðgangsstaður" - "Vista" - "Fleygja" - "Reiturinn fyrir heiti má ekki vera auður." - "Heiti aðgangsstaðar má ekki vera autt." - "MCC-reitur verður að innihalda 3 tölustafi." - "MNC-reitur verður að innihalda 2 eða 3 tölustafi." - "Endurheimtir sjálfgefnar stillingar aðgangsstaðar." - "Núllstilla" - "Sjálfgefnar stillingar aðgangsstaðar endurheimtar." - "Ónefnt" - "Heiti aðgangsstaða" - "Aðgangsstaðir" - "Nýr aðgangsstaður" - "Stillingar fyrir heiti aðgangsstaðar eru ekki í boði fyrir þennan notanda" - "Afrita á klippiborð?" - "Afrita" - %s" - "Almennt" - "Ítarlegt" - "Almennar stillingar" - "Ítarlegar stillingar" - "SIM-kortið „%s“" - "Senda sérstök SMS-skilaboð til hvers viðtakanda fyrir sig. Svör verða aðeins send til þín" - "Senda ein MMS-skilaboð til allra viðtakenda" - "Óþekkt númer" - "Ný skilaboð" - "Ný skilaboð." - "Val á SIM-korti" - "%1$s valið, val á SIM-korti" - "Breyta efni" - "Velja SIM-kort eða breyta efni" - "Haltu inni til að taka upp hljóð" - "Hefja nýtt samtal" - "Skilaboð" - "Skilaboðalisti" - "Skilaboð" - "Ný skilaboð" - "Samtalalisti" - "Hleður samtöl" - "Hleður skilaboð" - "Skoða fleiri samtöl" - "Skoða fleiri skilaboð" - "Samtali eytt" - "Samtali eytt. Snertu til að sýna annað samtal í Skilaboðum" - "Á bannlista" - "Tekin(n) af bannlista" - "Geymslupláss er á þrotum. Einhver gögn kunna að glatast." - "Veldu viðhengi" - "Staðfesta val" - "%d valin" - "Fjarlægðu eitt eða fleiri viðhengi og reyndu aftur." - "Þú getur prófað að senda skilaboðin en þau komast ef til vill ekki til skila nema þú fjarlægir eitt eða fleiri viðhengi." - "Þú getur aðeins sent eitt myndskeið með hverjum skilaboðum. Fjarlægðu myndskeiðin sem eru umfram og reyndu aftur." - "Skilaboð gátu ekki hlaðið viðhengið." - "Senda samt" - "Ekki tókst að hefja samtal" - "%1$s (%2$s)" - "%s valið" - diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml deleted file mode 100644 index 16f7792..0000000 --- a/res/values-it/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "nessun oggetto" - "nosubject" - - - "Sì" - "No" - "OK" - "He he" - "Grazie" - "Accetto" - "Bene" - "Sto arrivando" - "OK, ti ricontatto più tardi" - ":)" - ":(" - - diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml deleted file mode 100644 index 7261f25..0000000 --- a/res/values-it/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messaggi" - "Messaggi" - "Seleziona conversazione" - "Impostazioni" - "Invia messaggio" - "Aggiungi un allegato" - "Guida" - "Benvenuto" - "Ignora" - "Avanti >" - "Avanti" - "Esci" - "Impostazioni >" - "Impostazioni" - "Messaggi richiede l\'autorizzazione per SMS, Telefono e Contatti." - "Puoi modificare le autorizzazioni in Impostazioni > Applicazioni > Messaggi > Autorizzazioni." - "Puoi modificare le autorizzazioni in Impostazioni > Applicazioni > Messaggi > Autorizzazioni." - "Frequenti" - "Tutti i contatti" - "Invia a %s" - "Scatta foto o registra video" - "Scegli immagini su questo dispositivo" - "Registra audio" - "Scegli foto" - "Il contenuto multimediale è selezionato." - "Il contenuto multimediale è deselezionato." - "%d selezionati" - "immagine: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "immagine" - "Registrazione audio" - "Condividi" - "Adesso" - "Ora" - - %d min - %d min - - - %d ore - %d ora - - - %d giorni - %d giorno - - - %d settimane - una settimana - - - %d mesi - un mese - - - %d anni - un anno - - "Messaggio classe 0" - "Salva" - "Spazio insufficiente sul dispositivo. Messaggi eliminerà automaticamente i messaggi meno recenti per liberare spazio." - "Spazio di archiviazione in esaurimento" - "Messaggi potrebbe non inviare o ricevere messaggi finché non si libererà spazio sul dispositivo." - "Memoria per gli SMS scarsa. Potresti dover eliminare alcuni messaggi." - "Conferma il tuo numero di telefono" - "Con questo passaggio da eseguire una sola volta, puoi assicurarti che Messaggi invii correttamente i messaggi di gruppo." - "Numero di telefono" - "Elimina tutti i messaggi con contenuti multimediali" - "Elimina messaggi più vecchi di %s" - "Elimina automaticamente messaggi più vecchi di %s" - "Ignora" - "Eliminare tutti i messaggi con contenuti multimediali?" - "Eliminare i messaggi più vecchi di %s?" - "Eliminare i messaggi più vecchi di %s e attivare l\'eliminazione automatica?" - "Messaggio di %s" - "Il tuo messaggio" - "Messaggio di %s" - "Hai inviato un messaggio" - "Invio…" - "Non inviato. Tocca per riprovare." - "Non inviato. Nuovo tentativo…" - "Invia di nuovo o elimina" - "Effettua una chiamata vocale al numero di emergenza. Al momento non è possibile recapitare l\'SMS." - "Operazione non riuscita" - "Nuovo messaggio MMS da scaricare" - "Nuovo messaggio MMS" - "Download non riuscito" - "Tocca per riprovare" - "Tocca per scaricare" - "Scarica o elimina" - "Download..." - "Messaggio scaduto o non disponibile" - "dimensioni: %1$s, scadenza: %2$s" - "Impossibile inviare. Destinatario non valido." - "Servizio non attivato sulla rete" - "Invio non riuscito a causa di problemi di rete" - "Messaggio scaduto o non disponibile" - "(Nessun oggetto)" - "Mittente sconosciuto" - "Recapitato" - "Download del messaggio %1$s inviato da %2$s non riuscito." - "Completamento dell\'operazione nel database non riuscito a causa di memoria insufficiente" - "Messaggio non inviato" - "Alcuni messaggi non inviati in Messaggi" - - %d messaggi in %d conversazioni - %d messaggi in una conversazione - - "Messaggio non scaricato" - "Alcuni messaggi non scaricati in Messaggi" - - %d messaggi in %d conversazioni - %d messaggi in una conversazione - - "Messaggio al numero %1$s non inviato" - "Effettua una chiamata vocale al numero di emergenza. Al momento non è possibile recapitare l\'SMS al numero %1$s." - - %d nuovi messaggi - Nuovo messaggio - - "Avvia" - "Fotocamera non disponibile" - "Fotocamera non disponibile" - "Acquisizione video non disponibile" - "Impossibile salvare i contenuti multimediali" - "Impossibile scattare una foto" - "Indietro" - "Archiviate" - "Elimina" - "Archivio" - "Annulla archiviazione" - "Disattiva notifiche" - "Attiva notifiche" - "Aggiungi contatto" - "Scarica" - "Invia" - "Elimina" - "Eliminare questo messaggio?" - "L\'operazione non può essere annullata." - "Elimina" - - Eliminare queste conversazioni? - Eliminare questa conversazione? - - "Elimina" - "Annulla" - "A" - "Seleziona diverse immagini" - "Conferma selezione" - "e altri %d" - "Impossibile registrare l\'audio. Riprova." - "Impossibile riprodurre l\'audio. Riprova." - "Impossibile salvare l\'audio. Riprova." - "Tocca e tieni premuto" - ", " - " " - ": " - " " - "Immagine" - "Clip audio" - "Video" - "Scheda contatto" - "Scarica" - "Rispondi tramite SMS" - "Rispondi con MMS" - "Rispondi" - - %d partecipanti - %d participante - - "Io" - "Contatto bloccato e archiviato" - "Contatto sbloccato e non archiviato" - "%d archiviate" - "%d rimosse dall\'archivio" - "Notifiche disattivate" - "Notifiche attivate" - "Fatto. Tocca di nuovo Invia." - "Messaggi impostato correttamente come app predefinita per gli SMS." - - Elimina allegati - Elimina allegato - - "Allegato audio" - "Riproduci allegato audio" - "Metti in pausa" - "Messaggio da %s: %s." - "Messaggio di %s non recapitato: %s. Data/ora: %s." - "Messaggio di %s: %s. Data/ora: %s." - "Messaggio per %s non inviato: %s. Data/ora: %s." - "Invio del messaggio a %s: %s. Data/ora: %s." - "Messaggio per %s non recapitato: %s. Data/ora: %s." - "Messaggio per %s: %s. Data/ora: %s." - "Messaggio di %s non recapitato: %s. Data/ora: %s. %s." - "Messaggio di %s: %s. Data/ora: %s. %s." - "Messaggio per %s non inviato: %s. Data/ora: %s." - "Invio del messaggio a %s: %s. Data/ora: %s." - "Messaggio per %s non recapitato: %s. Data/ora: %s." - "Messaggio per %s: %s. Data/ora: %s." - "Messaggio non recapitato. Tocca per riprovare." - "Conversazione con %s" - "Elimina oggetto" - "Registra video" - "Scatta una foto in posa" - "Scatta una foto" - "Inizia a registrare il video" - "Passa alla fotocamera a schermo intero" - "Passa dalla fotocamera frontale a quella posteriore e viceversa" - "Interrompi registrazione e aggiungi video" - "Interrompi registrazione video" - "Foto in Messaggi" - - %d foto salvate nell\'album \"%s\" - %d foto salvata nell\'album \"%s\" - - - %d video salvati nell\'album \"%s\" - %d video salvato nell\'album \"%s\" - - - %d allegati salvati nell\'album \"%s\" - %d allegato salvato nell\'album \"%s\" - - - %d allegati salvati in \"Download\" - %d allegato salvato in \"Download\" - - - %d allegati salvati - %d allegato salvato - - - Impossibile salvare %d allegati - Impossibile salvare %d allegato - - "Allegato MMS salvato" - "Impostazioni" - "Archiviate" - "Chiudi" - "MMS" - "Avanzate" - "Debug" - "Notifiche" - "Suono" - "Silenzioso" - "Vibrazione" - "Bloccato" - "Rapporti di consegna SMS" - "Richiedi conferma di recapito per ogni SMS inviato" - "Recupero automatico" - "Recupera messaggi MMS automaticamente" - "Recupero automatico in roaming" - "Recupero automatico di MMS in roaming" - "Messaggi di gruppo" - "Utilizza gli MMS per inviare un unico messaggio a più destinatari" - "App SMS predefinita" - "App SMS predefinita" - - - "Il tuo numero di telefono" - "Sconosciuto" - "Riproduzione audio per messaggi in uscita" - "Scarica SMS" - "Scarica dati non elaborati SMS ricevuti in file di archiviazione esterna" - "Scarica MMS" - "Scarica dati non elaborati MMS ricevuti in file di archiviazione esterna" - "Avvisi wireless" - "Opzioni messaggio" - "Copia testo" - "Visualizza dettagli" - "Elimina" - "Inoltra" - "Dettagli messaggio" - "Tipo: " - "SMS" - "Messaggio multimediale" - "Da: " - "A: " - "Inviato: " - "Ricevuto in data: " - "Oggetto: " - "Dimensioni: " - "Priorità: " - "SIM: " - "Alta" - "Normale" - "Bassa" - "SIM %s" - "Indirizzo mittente nascosto" - "Impossibile inviare il messaggio durante il caricamento degli allegati." - "Impossibile caricare l\'allegato. Riprova." - "La rete non è pronta. Riprova." - "Elimina testo" - "Passa tra immissione testo e numeri" - "Aggiungi altri partecipanti" - "Conferma partecipanti" - "Inizia nuova conversazione" - "Seleziona questo elemento" - "Riproduci video" - "Persone e opzioni" - "Debug" - "Persone e opzioni" - "Generali" - "Persone presenti nella conversazione" - "Fai una chiamata" - "Invia messaggio" - "Invia messaggio<br/><small>da %s</small>" - - Invia foto - Invia foto - - - Invia audio - Invia audio - - - Invia video - Invia video - - - Invia schede contatti - Invia scheda contatto - - - Invia allegati - Invia allegato - - - %d allegati pronti da inviare - Un allegato pronto da inviare - - "Invia feedback" - "Visualizza nel Google Play Store" - "Info versione" - "Versione %1$s" - "Licenze open source" - "Notifiche" - "Limite di allegati raggiunto" - "Impossibile caricare l\'allegato." - "Aggiungere a Contatti?" - "Aggiungi contatto" - "Oggetto" - "Oggetto: " - "%s%s" - "Caricamento scheda contatto in corso" - "Impossibile caricare la scheda del contatto" - "Visualizza scheda contatto" - - %d contatti - %d contatto - - "Schede contatti" - "Compleanno" - "Note" - "Inoltra messaggio" - "Rispondi" - "+1" - "+%d" - "SMS disattivati" - "Per inviare messaggi, imposta Messaggi come app predefinita per gli SMS" - "Imposta Messaggi come app predefinita per gli SMS" - "Modifica" - "Per ricevere messaggi, imposta Messaggi come app predefinita per gli SMS" - "Nessuna SIM preferita selezionata per l\'invio di messaggi SMS" - "Questa app non è consentita dal proprietario del dispositivo." - "OK" - "Troppi partecipanti alla conversazione" - - Contatti non validi - Contatto non valido - - "Impossibile caricare l\'immagine della fotocamera" - "Tu: " - "%s: " - "Bozza" - "Le nuove conversazioni che inizi verranno elencate qui" - "Le conversazioni archiviate vengono visualizzate qui" - "Caricamento conversazioni in corso..." - "Immagine" - "Clip audio" - "Video" - "Scheda contatto" - "MMS" - "Annulla" - "Riprova" - "Inserisci il nome o il numero di telefono di un contatto per iniziare un nuovo messaggio" - "Blocca" - "Blocca %s" - "Sblocca %s" - "Bloccare %s?" - "Continuerai a ricevere messaggi da questo numero ma non riceverai più notifiche. Questa conversazione verrà archiviata." - "Contatti bloccati" - "SBLOCCA" - "Contatti bloccati" - "Scegli immagine da raccolta documenti" - "Invio messaggio" - "Messaggio inviato" - "Dati mobili non attivi. Controlla le impostazioni." - "Impossibile inviare messaggi in modalità Aereo" - "Messaggio non inviato" - "Messaggio scaricato" - "Dati mobili non attivi. Controlla le impostazioni." - "Impossibile scaricare messaggi in modalità aereo" - "Impossibile scaricare il messaggio" - "Zero" - "Uno" - "Due" - "Tre" - "Quattro" - "Cinque" - "Sei" - "Sette" - "Otto" - "Nove" - "Impossibile inviare il messaggio tramite %1$s. Errore %2$d" - "Impossibile inviare il messaggio tramite un operatore sconosciuto. Errore %1$d" - "Fwd: %s" - "Messaggio non inviato: servizio non attivato nella rete" - "Messaggio non inviato: indirizzo di destinazione non valido" - "Messaggio non inviato: messaggio non valido" - "Messaggio non inviato: contenuti non supportati" - "Messaggio non inviato: messaggio non supportato" - "Messaggio non inviato: troppo grande" - "Nuovo messaggio" - "Visualizza" - "Immagine" - "Impossibile trovare un\'applicazione adeguata" - "Rimuovi destinatario" - "Nuovo messaggio" - "Annulla" - "Modifica punto di accesso" - "Non impostato" - "Nome" - "APN" - "MMSC" - "Proxy MMS" - "Porta MMS" - "MCC" - "MNC" - "Tipo APN" - "Elimina APN" - "Nuovo APN" - "Salva" - "Elimina" - "Il campo Nome non può essere vuoto." - "Il campo APN non può essere vuoto." - "Il campo MCC deve contenere 3 cifre." - "Il campo MNC deve contenere 2 o 3 cifre." - "Ripristino delle impostazioni APN predefinite." - "Ripristina impostazioni predefinite" - "Ripristino delle impostazioni APN predefinite completato." - "Senza titolo" - "Nomi punti di accesso" - "APN" - "Nuovo APN" - "Le impostazioni del nome punto di accesso non sono disponibili per questo utente" - "Copiare negli appunti?" - "Copia" - "a %s" - "Generali" - "Avanzate" - "Impostazioni generali" - "Impostazioni avanzate" - "SIM \"%s\"" - "Invia SMS singoli a tutti i destinatari. Soltanto tu riceverai le risposte" - "Invia un singolo MMS a tutti i destinatari" - "Numero sconosciuto" - "Nuovo messaggio" - "Nuovo messaggio." - "Selettore SIM" - "Scheda %1$s selezionata, selettore SIM" - "Modifica oggetto" - "Seleziona la SIM o modifica l\'oggetto" - "Tocca e tieni premuto per registrare l\'audio" - "Inizia nuova conversazione" - "Messaggi" - "Elenco messaggi" - "Messaggi" - "Nuovo messaggio" - "Elenco conversazioni" - "Caricamento conversazioni..." - "Caricamento dei messaggi in corso" - "Visualizza altre conversazioni" - "Mostra altri messaggi" - "Conversazione eliminata" - "Conversazione eliminata. Tocca per vedere un\'altra conversazione di Messaggi" - "Bloccato" - "Sbloccato" - "Lo spazio di archiviazione è quasi esaurito. Alcuni dati potrebbero andare perduti." - "Seleziona allegati" - "Conferma selezione" - "%d selezionati" - "Rimuovi uno o più allegati e riprova." - "Puoi provare a inviare il messaggio, che però potrebbe non essere recapitato se non rimuovi uno o più allegati." - "Puoi inviare un solo video per messaggio. Rimuovi i video aggiuntivi e riprova." - "Messaggi non ha potuto caricare l\'allegato." - "Invia comunque" - "Impossibile avviare la conversazione" - "%1$s (%2$s)" - "SIM selezionata: %s" - diff --git a/res/values-iw/arrays.xml b/res/values-iw/arrays.xml deleted file mode 100644 index 91bd7db..0000000 --- a/res/values-iw/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ללא נושא" - "ללא נושא" - - - "כן" - "לא" - "אישור" - "חחח" - "תודה" - "אני מסכים" - "נחמד" - "אני בדרך" - "אוקיי, נדבר מאוחר יותר" - ":)" - ":(" - - diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml deleted file mode 100644 index 74e46bf..0000000 --- a/res/values-iw/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "העברת הודעות" - "העברת הודעות" - "בחר שיחה" - "הגדרות" - "שלח הודעה" - "הוסף קובץ מצורף" - "עזרה" - "ברוכים הבאים!" - "דלג" - "‏הבא >" - "הבא" - "צא" - "‏הגדרות >" - "הגדרות" - "‏\'העברת הודעות\' זקוק להרשאות SMS, טלפון ואנשי קשר." - "‏תוכל לשנות את ההרשאות ב\'הגדרות\' > \'אפליקציות\' >‏ \'העברת הודעות\' ‏> \'הרשאות\'." - "תוכל לשנות את ההרשאות ב\'הגדרות\', \'אפליקציות\', \'העברת הודעות\', \'הרשאות\'." - "בתדירות גבוהה" - "כל אנשי הקשר" - "שלח אל %s" - "צלם תמונות או סרטון" - "בחר תמונות ממכשיר זה" - "הקלט אודיו" - "בחר תמונה" - "בחרת במדיה." - "ביטלת את הבחירה במדיה." - "%d נבחרו" - "תמונה %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "תמונה" - "הקלט אודיו" - "שיתוף" - "ממש עכשיו" - "עכשיו" - - %d דקות - %d דקות - %d דקות - דקה %d - - - %d שעות - %d שעות - %d שעות - שעה %d - - - %d ימים - %d ימים - %d ימים - יום %d - - - שבועות - שבועות%d - שבועות - שבוע - - - %d חודשים - %d חודשים - %d חודשים - חודש - - - שנים - שנים%d - שנים - שנה - - "הודעה ברמה 0" - "שמור" - "נותר מעט שטח פנוי במכשיר. \'העברת הודעות\' ימחק הודעות ישנות באופן אוטומטי כדי לפנות שטח." - "שטח האחסון אוזל" - "לא תוכל לשלוח או לקבל הודעות ב\'העברת הודעות\' עד שתפנה שטח במכשיר." - "‏אין מספיק שטח אחסון להודעות SMS. ייתכן שתצטרך למחוק הודעות." - "אשר את מספר הטלפון שלך" - "שלב חד-פעמי זה יבטיח שהאפליקציה \'העברת הודעות\' תעביר כראוי את ההודעות הקבוצתיות שלך." - "מספר טלפון" - "מחק את כל ההודעות עם מדיה" - "מחק הודעות בנות יותר מ-%s" - "מחק אוטומטית הודעות שגילן עולה על %s" - "התעלם" - "למחוק את כל ההודעות עם המדיה?" - "למחוק הודעות שגילן עולה על %s?" - "למחוק הודעות שגילן עולה על %s ולהפעיל מחיקה אוטומטית?" - "%s אמר" - "אמרת" - "הודעה מאת %s" - "שלחת הודעה" - "שולח..." - "ההודעה לא נשלחה. גע כדי לנסות שוב." - "ההודעה לא נשלחה. מנסה שוב…" - "שלח שוב או מחק" - "בצע שיחה קולית לשירותי חירום. לא ניתן היה לשלוח את הודעת הטקסט." - "נכשל" - "‏הודעת MMS חדשה להורדה" - "‏הודעת MMS חדשה" - "לא ניתן לבצע את ההורדה" - "גע כדי לנסות שוב" - "גע כדי להוריד" - "הורד או מחק" - "מוריד…" - "תוקף ההודעה פג או שהיא לא זמינה" - "גודל: %1$s, תפוגה: %2$s" - "לא ניתן לשלוח. הנמען אינו חוקי." - "השירות לא הופעל ברשת" - "לא ניתן לשלוח בגלל בעיה ברשת" - "התוקף של ההודעה פג או שהיא לא זמינה" - "(ללא נושא)" - "שולח לא ידוע" - "נמסר" - "לא ניתן היה להוריד את ההודעה %1$s מ-%2$s." - "לא ניתן היה להשלים את הפעולה של מסד הנתונים עקב זיכרון לא מספיק" - "ההודעה לא נשלחה" - "\'העברת הודעות\' לא שלח חלק מההודעות" - - %d הודעות ב-%d שיחות - %d הודעות ב-%d שיחות - %d הודעות ב-%d שיחות - %d הודעות בשיחה אחת - - "ההודעה לא הורדה" - "\'העברת הודעות\' לא הוריד חלק מההודעות" - - %d הודעות ב-%d שיחות - %d הודעות ב-%d שיחות - %d הודעות ב-%d שיחות - %d הודעות בשיחה אחת - - "ההודעה אל %1$s לא נשלחה" - "התקשר לשירותי חירום. לא ניתן היה לשלוח את הודעת הטקסט אל %1$s." - - %d הודעות חדשות - %d הודעות חדשות - %d הודעות חדשות - הודעה חדשה - - "התחל" - "המצלמה לא זמינה" - "המצלמה לא זמינה" - "לכידת וידאו אינה זמינה" - "לא ניתן לשמור מדיה" - "לא ניתן לצלם את התמונה" - "הקודם" - "הועברו לארכיון" - "מחק" - "העבר לארכיון" - "הוצאה מארכיון" - "כבה התראות" - "הפעל התראות" - "הוסף איש קשר" - "הורד" - "שלח" - "מחק" - "למחוק הודעה זו?" - "פעולה זו אינה הפיכה." - "מחק" - - האם למחוק את השיחות האלו? - האם למחוק את השיחות האלו? - האם למחוק את השיחות האלו? - האם למחוק את השיחה הזו? - - "מחק" - "בטל" - "אל" - "בחר תמונות מרובות" - "אשר את הבחירה" - "+%d" - "לא ניתן להקליט אודיו. נסה שוב." - "לא ניתן להפעיל אודיו. נסה שוב." - "לא ניתן לשמור אודיו. נסה שוב." - "גע והחזק" - ", " - " " - ": " - " " - "תמונה" - "קטע אודיו" - "סרטון" - "כרטיס איש קשר" - "הורד" - "‏השב באמצעות SMS" - "‏השב באמצעות MMS" - "השב" - - %d משתתפים - %d משתתפים - %d משתתפים - משתתף %d - - "אני" - "איש הקשר נחסם והועבר לארכיון" - "חסימת איש הקשר בוטלה והוא הוצא מהארכיון" - "%d הועברו לארכיון" - "%d הוצאו מהארכיון" - "ההודעות כבויות" - "ההודעות פועלות" - "הכל מוכן. גע באפשרות \'שלח שוב\'." - "‏האפליקציה \'העברת הודעות\' הוגדרה בהצלחה כאפליקציית ברירת המחדל ל-SMS." - - מחק את הקבצים המצורפים - מחק את הקבצים המצורפים - מחק את הקבצים המצורפים - מחק את הקובץ המצורף - - "קובץ אודיו מצורף" - "הפעל את קובץ האודיו המצורף" - "השהה" - "הודעה מאת %s: %s." - "הודעה מאת %s שנכשלה: %s. שעה: %s." - "הודעה מאת %s: %s. שעה: %s." - "הודעה אל %s שלא נשלחה: %s. שעה: %s." - "שולח הודעה אל %s: %s. שעה: %s." - "הודעה אל %s שנכשלה: %s. שעה: %s." - "הודעה אל %s: %s. שעה: %s." - "הודעה שנכשלה מאת %s‏: %s. שעה: %s. %s." - "הודעה מאת %s‏: %s. שעה: %s. %s." - "הודעה אל %s שלא נשלחה: %s. שעה: %s." - "שולח הודעה אל %s: %s. שעה: %s." - "הודעה אל %s שנכשלה: %s. שעה: %s." - "הודעה אל %s: %s. שעה: %s." - "ההודעה נכשלה. גע כדי לנסות שוב." - "שיחה עם %s" - "מחק נושא" - "צלם סרטון" - "צלם תמונת סטילס" - "צלם תמונה" - "התחל הקלטת סרטון" - "עבור למצלמה במסך מלא" - "החלף בין המצלמה הקדמית לאחורית" - "הפסק להקליט וצרף סרטון" - "עצור הקלטת וידאו" - "תמונות ב\'העברת הודעות\'" - - %d תמונות נשמרו באלבום \"%s\" - %d תמונות נשמרו באלבום \"%s\" - %d תמונות נשמרו באלבום \"%s\" - תמונה %d נשמרה באלבום \"%s\" - - - %d סרטונים נשמרו באלבום \"%s\" - %d סרטונים נשמרו באלבום \"%s\" - %d סרטונים נשמרו באלבום \"%s\" - סרטון %d נשמר באלבום \"%s\" - - - %d קבצים מצורפים נשמרו באלבום \"%s\" - %d קבצים מצורפים נשמרו באלבום \"%s\" - %d קבצים מצורפים נשמרו באלבום \"%s\" - קובץ מצורף %d נשמר באלבום \"%s\" - - - %d קבצים מצורפים נשמרו ב\"הורדות\" - %d קבצים מצורפים נשמרו ב\"הורדות\" - %d קבצים מצורפים נשמרו ב\"הורדות\" - קובץ מצורף %d נשמר ב\"הורדות\" - - - %d קבצים מצורפים נשמרו - %d קבצים מצורפים נשמרו - %d קבצים מצורפים נשמרו - קובץ מצורף %d נשמר - - - לא ניתן היה לשמור %d קבצים מצורפים - לא ניתן היה לשמור %d קבצים מצורפים - לא ניתן היה לשמור %d קבצים מצורפים - לא ניתן היה לשמור קובץ מצורף %d - - "‏קובץ ה-MMS המצורף נשמר" - "הגדרות" - "הועברו לארכיון" - "סגור" - "MMS" - "מתקדם" - "ניפוי באגים" - "הודעות" - "צליל" - "מצב שקט" - "רטט" - "חסום" - "‏דוחות מסירת SMS" - "‏בקש דוח מסירה לכל SMS שאתה שולח" - "אחזור אוטומטי" - "‏אחזור אוטומטי של הודעות MMS" - "אחזור אוטומטי בנדידה" - "‏אחזר הודעות MMS באופן אוטומטי בזמן נדידה" - "העברת הודעות לקבוצה" - "‏השתמש ב-MMS לשליחת הודעה אחת לנמענים מרובים" - "‏אפליקציית SMS המוגדרת כברירת מחדל" - "‏אפליקציית SMS המוגדרת כברירת מחדל" - - - "מספר הטלפון שלך" - "לא ידוע" - "צלילים של הודעות יוצאות" - "‏צור Dump של SMS" - "‏צור Dump בקובץ אחסון חיצוני עבור נתוני SMS גולמיים שהתקבלו" - "‏צור Dump של MMS" - "‏צור Dump בקובץ אחסון חיצוני עבור נתוני MMS גולמיים שהתקבלו" - "התראות אלחוטיות" - "אפשרויות הודעה" - "העתק טקסט" - "הצגת פרטים" - "מחיקה" - "העבר" - "פרטי הודעה" - "סוג: " - "הודעת טקסט" - "הודעת מולטימדיה" - "מאת: " - "אל: " - "נשלחה ב: " - "התקבלה ב: " - "נושא: " - "גודל: " - "עדיפות: " - "SIM: " - "גבוהה" - "רגילה" - "נמוכה" - "‏SIM‏ %s" - "כתובת שולח מוסתרת" - "לא ניתן לשלוח הודעה בעת טעינת קבצים מצורפים." - "לא ניתן לטעון קובץ מצורף. נסה שוב." - "הרשת לא מוכנה. נסה שוב." - "מחק טקסט" - "עבור בין הזנה של אותיות וספרות" - "הוסף עוד משתמשים" - "אשר משתתפים" - "התחל שיחה חדשה" - "בחר בפריט הזה" - "הפעל סרטון" - "אנשים ואפשרויות" - "ניפוי באגים" - "אנשים ואפשרויות" - "כללי" - "אנשים בשיחה הזו" - "התקשר" - "שלח הודעה" - "‏שלח הודעה<br/><small>מ-%s</small>" - - שלח את התמונות - שלח את התמונות - שלח את התמונות - שלח את התמונה - - - שלח את הקלטות האודיו - שלח את הקלטות האודיו - שלח את הקלטות האודיו - שלח את הקלטת האודיו - - - שלח את הסרטונים - שלח את הסרטונים - שלח את הסרטונים - שלח את הסרטון - - - שלח את כרטיסי אנשי הקשר - שלח את כרטיסי אנשי הקשר - שלח את כרטיסי אנשי הקשר - שלח את כרטיס איש הקשר - - - שלח את הקבצים המצורפים - שלח את הקבצים המצורפים - שלח את הקבצים המצורפים - שלח את הקובץ המצורף - - - יש %d קבצים מצורפים המוכנים לשליחה - יש %d קבצים מצורפים המוכנים לשליחה - יש %d קבצים מצורפים המוכנים לשליחה - יש קובץ מצורף אחד המוכן לשליחה - - "שלח משוב" - "‏הצג בחנות Google Play" - "פרטי גרסה" - "‏גרסה %1$s" - "רישיונות קוד פתוח" - "הודעות" - "צירפת את מספר הקבצים המקסימלי" - "טעינת הקובץ המצורף נכשלה." - "האם להוסיף לאנשי הקשר?" - "הוסף איש קשר" - "נושא" - "נושא: " - "%s%s" - "טוען את כרטיס איש הקשר" - "לא ניתן לטעון כרטיס איש קשר" - "הצג את כרטיס איש הקשר" - - %d אנשי קשר - %d אנשי קשר - %d אנשי קשר - איש קשר %d - - "כרטיסי אנשי קשר" - "יום הולדת" - "הערות" - "העבר הודעה" - "השב" - "+1" - "+%d" - "‏SMS מושבת" - "‏כדי לשלוח, הגדר את האפליקציה \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS" - "‏הגדר את האפליקציה \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS" - "שנה" - "‏כדי לקבל הודעות, הגדר את \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS" - "‏לא נבחר SIM מועדף עבור שליחה של הודעות SMS" - "אפליקציה זו אינה מורשית על ידי בעל המכשיר." - "אישור" - "משתתפים רבים מדי בשיחה" - - אנשי קשר לא חוקיים - אנשי קשר לא חוקיים - אנשי קשר לא חוקיים - איש קשר לא חוקי - - "לא ניתן לטעון תמונת מצלמה" - "אתה: " - "%s: " - "טיוטה" - "כשתתחיל שיחה חדשה, תראה אותה ברשימה כאן" - "שיחות שהועברו לארכיון מופיעות כאן" - "טוען שיחות..." - "תמונה" - "קטע אודיו" - "סרטון" - "כרטיס איש קשר" - "MMS" - "בטל" - "נסה שוב" - "הזן שם איש קשר או מספר טלפון כדי להתחיל הודעה חדשה" - "חסום" - "חסום את %s" - "בטל חסימה של %s" - "לחסום את %s?" - "תמשיך לקבל הודעות ממספר זה אך לא יוצגו לך עוד דיווחים. שיחה זו תועבר לארכיון." - "אנשי קשר חסומים" - "אפשר" - "אנשי קשר חסומים" - "בחר תמונה מספריית המסמכים" - "שולח את ההודעה" - "ההודעה נשלחה" - "נתונים סלולריים כבויים. בדוק את ההגדרות." - "לא ניתן לשלוח הודעות במצב טיסה" - "לא ניתן היה לשלוח את ההודעה" - "הורדת ההודעה בוצעה" - "נתונים סלולריים כבויים. בדוק את ההגדרות." - "לא ניתן להוריד הודעות במצב טיסה" - "לא ניתן היה להוריד את ההודעה" - "אפס" - "אחת" - "שתיים" - "שלוש" - "ארבע" - "חמש" - "שש" - "שבע" - "שמונה" - "תשע" - "לא ניתן לשלוח הודעה דרך %1$s, שגיאה %2$d" - "לא ניתן לשלוח הודעה דרך ספק לא ידוע, שגיאה %1$d" - "העבר: %s" - "ההודעה לא נשלחה: השירות לא הופעל ברשת." - "ההודעה לא נשלחה: כתובת היעד לא חוקית" - "ההודעה לא נשלחה: הודעה לא חוקית" - "ההודעה לא נשלחה: תוכן לא נתמך" - "ההודעה לא נשלחה: הודעה לא נתמכת" - "ההודעה לא נשלחה מפני שהיא גדולה מדי" - "הודעה חדשה" - "הצג" - "תמונה" - "לא נמצא יישום מתאים" - "הסר נמען" - "הודעה חדשה" - "בטל" - "ערוך נקודת גישה" - "לא מוגדרת" - "שם" - "APN" - "MMSC" - "‏שרת Proxy של MMS" - "‏יציאת MMS" - "MCC" - "MNC" - "‏סוג APN" - "‏מחק APN" - "‏APN חדש" - "שמור" - "מחק" - "השדה \'שם\' לא יכול להיות ריק." - "‏ה-APN לא יכול להיות ריק." - "‏שדה MCC חייב להכיל 3 ספרות." - "‏שדה MNC חייב להכיל 2 או 3 ספרות." - "‏משחזר את הגדרות ברירת המחדל של APN." - "אפס לברירת מחדל" - "‏איפוס ההגדרות עבור ברירת המחדל של APN הושלם." - "ללא שם" - "שמות של נקודות גישה" - "APNs" - "‏APN חדש" - "‏הגדרות עבור שם נקודת גישה (APN) אינן זמינות עבור המשתמש הזה" - "האם להעתיק אל לוח העריכה?" - "העתק" - "אל %s" - "כללי" - "מתקדם" - "הגדרות כלליות" - "הגדרות מתקדמות" - "\"%s\" SIM" - "‏שלח הודעות SMS אישיות לכל הנמענים. רק אתה תקבל תשובות" - "‏שלח הודעת MMS אחת לכל הנמענים" - "מספר לא ידוע" - "הודעה חדשה" - "הודעה חדשה." - "‏בורר כרטיסי SIM" - "‏%1$s נבחר, בוחר כרטיסי SIM" - "ערוך נושא" - "‏בחר כרטיס SIM או ערוך את הנושא" - "גע נגיעה ממושכת כדי להקליט אודיו" - "התחל שיחה חדשה" - "\'העברת הודעות\'" - "רשימת \'העברת הודעות\'" - "\'העברת הודעות\'" - "הודעה חדשה" - "רשימת שיחות" - "טוען שיחות" - "טוען הודעות" - "הצג שיחות נוספות" - "הצג עוד הודעות" - "השיחה נמחקה" - "השיחה נמחקה. גע כדי להציג שיחה אחרת ב\'העברת הודעות\'" - "חסום" - "חסימה בוטלה" - "אין מספיק שטח אחסון. ייתכן שתאבד חלק מהנתונים." - "בחר קבצים מצורפים" - "אשר את הבחירה" - "בחרת %d" - "הסר קובץ מצורף אחד או יותר ונסה שוב." - "תוכל לנסות לשלוח את ההודעה, אך ייתכן שהיא לא תימסר אלא אם תסיר קובץ מצורף אחד או יותר." - "ניתן לשלוח רק סרטון אחד בכל הודעה. הסר סרטונים נוספים ונסה שוב." - "טעינת הקובץ המצורף נכשלה ב\'העברת הודעות\'." - "שלח בכל זאת" - "לא ניתן היה להתחיל את השיחה" - "%1$s (%2$s)" - "%s נבחר" - diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml deleted file mode 100644 index cf9e1a6..0000000 --- a/res/values-ja/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "件名なし" - "件名なし" - - - "はい" - "いいえ" - "OK" - "笑" - "ありがとう" - "賛成" - "ナイス" - "すぐに連絡します" - "後で連絡します" - ":)" - ":(" - - diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml deleted file mode 100644 index 49df2fc..0000000 --- a/res/values-ja/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "メッセージ" - "メッセージ" - "会話の選択" - "設定" - "メッセージを送信" - "添付ファイルを追加" - "ヘルプ" - "ようこそ" - "スキップ" - "次へ >" - "次へ" - "終了" - "設定 >" - "設定" - "SMSには、SMS、電話、連絡先に対する許可が必要です。" - "許可の変更は、[設定]>[アプリ]>[SMS]>[許可]で行えます。" - "許可の変更は、[設定]、[アプリ]、[SMS]、[許可]で行えます。" - "よく使う連絡先" - "すべての連絡先" - "%sに送信" - "写真や動画を撮影" - "この端末から画像を選択" - "音声の録音" - "写真を選択" - "メディアを選択しました。" - "メディアの選択を解除しました。" - "%d件選択済み" - "画像%1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "画像" - "音声の録音" - "共有" - "たった今" - "今" - - %d - %d - - - %d時間 - %d時間 - - - %d - %d - - - %d週間 - 1週間 - - - %dか月 - 1か月 - - - %d - 1年 - - "クラス0メッセージ" - "保存" - "端末の空き容量が少なくなっています。SMSでは空き容量を確保するため古いメッセージが自動的に削除されます。" - "空き容量が少なくなっています" - "端末上の空き容量が増えるまで、SMSでメッセージの送受信ができない場合があります。" - "SMSの空き容量が少なくなっています。メッセージの削除が必要な場合があります。" - "電話番号の確認" - "この1回の手順で、SMSはグループにメッセージを確実に配信します。" - "電話番号" - "メディア付きのメッセージをすべて削除" - "%sより前のメッセージを削除" - "%sより前のメッセージを自動削除" - "無視" - "メディア付きのメッセージをすべて削除しますか?" - "%sより前のメッセージを削除しますか?" - "%sより前のメッセージを削除し、自動削除をONにしますか?" - "%sさんからのメッセージ" - "メッセージ内容" - "%sさんからのメッセージ" - "メッセージを送信しました" - "送信しています…" - "送信されていません。タップしてもう一度お試しください。" - "送信されていません。もう一度送信しています…" - "再送信または削除" - "緊急通報には音声通話を使用してください。テキストメッセージを配信できませんでした。" - "失敗しました" - "ダウンロードする新しいMMSメッセージ" - "新しいMMSメッセージ" - "ダウンロードできませんでした" - "再試行するにはタップしてください" - "ダウンロードするにはタップしてください" - "ダウンロードまたは削除" - "ダウンロードしています…" - "メッセージが期限切れか参照できなくなっています" - "サイズ: %1$s、期限: %2$s" - "送信できません。受信者が無効です。" - "サービスがネットワークで有効になっていません" - "ネットワークに問題があるため送信できませんでした" - "メッセージが期限切れか参照できなくなっています" - "(無題)" - "送信者不明" - "配信済み" - "件名「%1$s」、送信者「%2$s」のメッセージをダウンロードできませんでした。" - "メモリ不足のためデータベース操作を完了できませんでした" - "メッセージが送信されていません" - "SMSの一部のメッセージが送信されていません" - - %d件のスレッドのメッセージ%d - 1件のスレッドのメッセージ%d - - "メッセージがダウンロードされていません" - "SMSの一部のメッセージがダウンロードされていません" - - %d件のスレッドのメッセージ%d - 1件のスレッドのメッセージ%d - - "%1$sへのメッセージは送信されませんでした" - "緊急通報には音声通話を使用してください。%1$sへのテキストメッセージを配信できませんでした。" - - %d件の新しいメッセージ - 新しいメッセージ - - "開始" - "カメラは利用できません" - "カメラは利用できません" - "動画撮影は利用できません" - "メディアを保存できません" - "写真を撮ることはできません" - "戻る" - "アーカイブ済み" - "削除" - "アーカイブ" - "アーカイブ解除" - "通知を無効化" - "通知を有効化" - "連絡先を追加" - "ダウンロード" - "送信" - "削除" - "このメッセージを削除しますか?" - "この操作は元に戻せません。" - "削除" - - これらのスレッドを削除しますか? - このスレッドを削除しますか? - - "削除" - "キャンセル" - "宛先" - "複数の画像を選択" - "選択の確認" - "+%d" - "音声を録音できません。もう一度お試しください。" - "音声を再生できません。もう一度お試しください。" - "音声を保存できませんでした。もう一度お試しください。" - "長押し" - "、 " - " " - ": " - " " - "写真" - "音声クリップ" - "動画" - "連絡先カード" - "ダウンロード" - "SMSで返信" - "MMSで返信" - "返信" - - 参加者%d - 参加者%d - - "自分" - "連絡先がブロックされ、アーカイブされました" - "連絡先をブロック解除し、アーカイブを解除しました" - "%d件をアーカイブしました" - "%d件のアーカイブを解除しました" - "通知をオフにしました" - "通知をオンにしました" - "すべて設定されました。もう一度[送信]をタップしてください。" - "SMSを既定のSMSアプリとして設定しました。" - - 添付ファイルを破棄 - 添付ファイルを破棄 - - "音声添付ファイル" - "音声添付ファイルの再生" - "一時停止" - "%sさんからのメッセージは「%s」です。" - "%sからのメッセージ「%s」を受信できませんでした(時間: %s)。" - "%sからのメッセージ「%s」を受信しました(時間: %s)。" - "%sへのメッセージ「%s」が送信されていません(時間: %s)。" - "%sへのメッセージ「%s」を送信しています(時間: %s)。" - "%sへのメッセージ「%s」を送信できませんでした(時間: %s)。" - "%sへのメッセージ「%s」を送信しました(時間: %s)。" - "%sからのメッセージ「%s」を受信できませんでした(時間: %s%s)。" - "%sからのメッセージ「%s」を受信しました(時間: %s%s)。" - "%sへのメッセージ「%s」が送信されていません(時間: %s)。" - "%sへのメッセージ「%s」を送信しています(時間: %s)。" - "%sへのメッセージ「%s」を送信できませんでした(時間: %s)。" - "%sへのメッセージ「%s」を送信しました(時間: %s)。" - "失敗したメッセージ。再試行するにはタップしてください。" - "%sとのスレッド" - "件名を削除" - "動画を撮影" - "静止画を撮影" - "画像を撮影" - "動画の録画を開始" - "全画面カメラに切り替える" - "前面カメラと背面カメラを切り替える" - "録画を停止し、動画を添付する" - "動画の録画を停止" - "SMSの写真" - - %d枚の写真を「%s」アルバムに保存しました - %d枚の写真を「%s」アルバムに保存しました - - - %d本の動画を「%s」アルバムに保存しました - %d本の動画を「%s」アルバムに保存しました - - - %d個の添付ファイルを「%s」アルバムに保存しました - %d個の添付ファイルを「%s」アルバムに保存しました - - - %d個の添付ファイルを「ダウンロード」に保存しました - %d個の添付ファイルを「ダウンロード」に保存しました - - - %d個の添付ファイルを保存しました - %d個の添付ファイルを保存しました - - - %d個の添付ファイルを保存できませんでした - %d個の添付ファイルを保存できませんでした - - "MMS添付ファイルを保存しました" - "設定" - "アーカイブ済み" - "閉じる" - "MMS" - "詳細設定" - "デバッグ" - "通知" - "通知音" - "マナーモード" - "バイブレーション" - "ブロック済み" - "SMS受取確認通知" - "送信するSMSの配信レポートを毎回リクエストする" - "自動取得" - "MMSメッセージを自動的に取得する" - "ローミング時に自動取得" - "ローミング時にMMSを自動取得" - "グループメッセージング" - "宛先が複数ある場合にMMSを使用して1通のメッセージとして送信する" - "既定のSMSアプリ" - "既定のSMSアプリ" - - - "電話番号" - "不明" - "メッセージ送信音" - "SMSのダンプ" - "受信したSMS生データを外部ストレージファイルにダンプする" - "MMSのダンプ" - "受信したMMS生データを外部ストレージファイルにダンプする" - "無線警報" - "メッセージオプション" - "テキストをコピー" - "詳細を表示" - "削除" - "転送" - "メッセージの詳細" - "形式: " - "テキストメッセージ" - "マルチメディアメッセージ" - "From: " - "To: " - "送信: " - "受信: " - "件名: " - "サイズ: " - "優先度: " - "SIM: " - "高" - "標準" - "低" - "SIM %s" - "差出人アドレスは非表示" - "添付ファイルの読み込み中はメッセージを送信できません。" - "添付ファイルを読み込めません。もう一度お試しください。" - "ネットワークの準備ができていません。もう一度お試しください。" - "テキストを削除" - "文字と数字の入力を切り替える" - "参加者を追加" - "参加者を確認" - "新しいスレッドを開始" - "この項目を選択する" - "動画を再生" - "ユーザーとオプション" - "デバッグ" - "ユーザーとオプション" - "全般" - "このスレッドのユーザー" - "発信" - "メッセージの送信" - "<small>%sから</small>&lt:br/>メッセージを送信" - - 写真を送信します - 写真を送信します - - - 音声を送信します - 音声を送信します - - - 動画を送信します - 動画を送信します - - - 連絡先カードを送信します - 連絡先カードを送信します - - - 添付ファイルを送信します - 添付ファイルを送信します - - - %d個の添付ファイルを送信する準備ができました - 1個の添付ファイルを送信する準備ができました - - "フィードバックを送信" - "Google Playストアで表示" - "バージョン情報" - "バージョン%1$s" - "オープンソースライセンス" - "通知" - "添付ファイルの上限に達しました" - "添付ファイルを読み込めませんでした。" - "連絡先に追加しますか?" - "連絡先を追加" - "件名" - "件名: " - "%s%s" - "連絡先カードを読み込んでいます" - "連絡先カードを読み込めませんでした" - "連絡先カードを表示" - - %d件の連絡先 - %d件の連絡先 - - "連絡先カード" - "誕生日" - "メモ" - "メールを転送" - "返信" - "、他1人" - "、他%d人" - "SMSは無効になっています" - "送信するには、SMSを既定のSMSアプリとして設定してください" - "SMSを既定のSMSアプリとして設定してください" - "変更" - "メッセージを受信するには、SMSを既定のSMSアプリとして設定してください" - "SMSメッセージ送信用の優先SIMが選択されていません" - "このアプリは端末の所有者によって許可されていません。" - "OK" - "スレッドへの参加者が多すぎます" - - 連絡先が無効です - 連絡先が無効です - - "カメラ画像を読み込めませんでした" - "あなた: " - "%s: " - "下書き" - "新しいスレッドを開始すると、ここにリストが表示されます" - "アーカイブされたスレットがここに表示されます" - "スレッドを読み込んでいます…" - "画像" - "音声クリップ" - "動画" - "連絡先カード" - "MMS" - "元に戻す" - "再試行" - "新しいメッセージを開始するには連絡先の名前か電話番号を入力してください" - "ブロック" - "%sさんをブロック" - "%sさんのブロックを解除" - "%sをブロックしますか?" - "この番号からのメッセージは引き続き受信しますが、通知は表示されなくなります。このスレッドはアーカイブされます。" - "ブロック中の連絡先" - "ブロックを解除" - "ブロック中の連絡先" - "ドキュメントライブラリから画像を選択" - "メールを送信しています" - "メッセージを送信しました" - "モバイルデータがOFFになっています。設定を確認してください。" - "機内モードではメッセージを送信できません" - "メッセージを送信できませんでした" - "メッセージをダウンロードしました" - "モバイルデータがOFFになっています。設定を確認してください。" - "機内モードではメッセージをダウンロードできません" - "メッセージをダウンロードできませんでした" - "0" - "1" - "2" - "3" - "4" - "5" - "6" - "7" - "8" - "9" - "メッセージを送信できません(%1$s)、エラー%2$d" - "メッセージを送信できません(不明な携帯通信会社)、エラー%1$d" - "Fwd: %s" - "メッセージの送信失敗: サービスがネットワークで有効になっていません" - "メッセージの送信失敗: 無効な宛先アドレスです" - "メッセージの送信失敗: 無効なメッセージです" - "メッセージの送信失敗: サポートされていないコンテンツです" - "メッセージの送信失敗: サポートされていないメッセージです" - "メッセージの送信失敗: 大きすぎます" - "新しいメッセージ" - "表示" - "画像" - "適切なアプリが見つかりませんでした" - "宛先を削除" - "新しいメッセージ" - "キャンセル" - "アクセスポイントの編集" - "未設定" - "名前" - "APN" - "MMSC" - "MMSプロキシ" - "MMSポート" - "MCC" - "MNC" - "APNタイプ" - "APNを削除" - "新しいAPN" - "保存" - "破棄" - "[名前]欄は必須です。" - "APNは必須です。" - "MCC欄は3桁で指定してください。" - "MNC欄は2桁か3桁で指定してください。" - "デフォルトのAPN設定を復元しています。" - "デフォルトにリセット" - "デフォルトのAPN設定にリセットしました。" - "無題" - "アクセスポイント名" - "APN" - "新しいAPN" - "このユーザーはアクセスポイント名設定を利用できません" - "クリップボードにコピーしますか?" - "コピー" - "宛先: %s" - "全般" - "詳細設定" - "全般設定" - "詳細設定" - "「%s」SIM" - "SMSメッセージを全員に個別に送信し、返信があなただけに返されるようにします" - "1つのMMSを全員に送信します" - "不明な番号" - "新しいメッセージ" - "新しいメッセージがあります。" - "SIMの切り替え" - "SIMの切り替え(%1$sを選択済み)" - "件名を編集" - "SIMを選択するか件名を編集します" - "録音するには押し続けます" - "新しいスレッドを開始" - "SMS" - "SMSリスト" - "SMS" - "新しいメッセージ" - "スレッドリスト" - "スレッドを読み込んでいます" - "メールを読み込んでいます" - "他のスレッドを見る" - "他のメールを見る" - "スレッドを削除しました" - "スレッドを削除しました。タップしてSMSの別のスレッドを表示します" - "ブロックしました" - "ブロックを解除しました" - "空き容量が少なくなっています。一部のデータが失われる可能性があります。" - "添付ファイルの選択" - "選択の確認" - "%d件選択済み" - "添付ファイルを1つ以上削除してから、もう一度お試しください。" - "メッセージの送信を試すことはできますが、添付ファイルを1つ以上削除しないと配信されない可能性があります。" - "送信できる動画は1件のメッセージにつき1本までです。追加した動画を削除してもう一度お試しください。" - "SMSで添付ファイルを読み込めませんでした。" - "このまま送信" - "スレッドを開始できませんでした" - "%1$s%2$s)" - "%sが選択されています" - diff --git a/res/values-ka-rGE/arrays.xml b/res/values-ka-rGE/arrays.xml deleted file mode 100644 index 0f50610..0000000 --- a/res/values-ka-rGE/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "უსათაურო" - "nosubject" - - - "კი" - "არა" - "კარგი" - "ჰე-ჰე" - "დიდი მადლობა" - "თანახმა ვარ" - "მშვენიერია" - "მოვდივარ" - "კარგი, მოგვიანებით შეგეხმიანები" - ":)" - ":(" - - diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml deleted file mode 100644 index 9605c6b..0000000 --- a/res/values-ka-rGE/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "შეტყობინებები" - "შეტყობინებები" - "მიმოწერის არჩევა" - "პარამეტრები" - "წერილის გაგზავნა" - "დანართის დამატება" - "დახმარება" - "მოგესალმებით" - "გამოტოვება" - "შემდეგი >" - "შემდეგი" - "გასვლა" - "პარამეტრები >" - "პარამეტრები" - "შეტყობინებები საჭიროებს ნებართვას SMS-ზე, ტელეფონზე და კონტაქტებზე." - "ნებართვების შეცვლა შეგიძლიათ აქ: პარამეტრები > აპები > შეტყობინებები > ნებართვები." - "ნებართვების შეცვლა შეგიძლიათ აქ: პარამეტრები, აპები, შეტყობინებები, ნებართვები." - "ხშირად გამოყენებული კონტაქტები" - "ყველა კონტაქტი" - "%s-ზე გაგზავნა" - "სურათების ან ვიდეოს გადაღება" - "ამ მოწყობილობიდან სურათების არჩევა" - "აუდიოს ჩაწერა" - "ფოტოს არჩევა" - "მედია შერჩეულია." - "მედია არ არის შერჩეული." - "%d შერჩეულია" - "სურათის %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "სურათი" - "აუდიოს ჩაწერა" - "გაზიარება" - "ახლახანს" - "ახლა" - - %d წთ. - %d წთ. - - - %d საათის - %d საათის - - - %d დღის - %d დღის - - - %d კვირის - კვირის - - - %d თვის - თვის - - - %d წლის - წლის - - "Class 0 შეტყობინება" - "შენახვა" - "მოწყობილობის მეხსიერებაში თავისუფალი სივრცე ცოტაა. შეტყობინებები ავტომატურად წაშლის ძველ შეტყობინებებს მეხსიერების გასათავისუფლებლად." - "თავისუფალი ადგილი იწურება" - "შეტყობინებებმა შესაძლოა ვერ მოახერხოს შეტყობინებების გაგზავნა ან მიღება მანამ, სანამ თქვენს მოწყობილობაზე ხელმისაწვდომი არ გახდება მეტი სივრცე." - "SMS სივრცე იწურება. შესაძლოა, მოგიწიოთ ძველი შეტყობინებების წაშლა" - "დაადასტურეთ თქვენი ტელეფონის ნომერი" - "ეს ერთჯერადი ნაბიჯი უზრუნველყოფს, რომ შეტყობინებებმა სწორად მოგაწოდოთ ჯგუფური შეტყობინებები." - "ტელეფონის ნომერი" - "ყველა შეტყობინების წაშლა, რომელსაც მედია აქვს" - "%s-ზე ძველი შეტყობინებების წაშლა" - "%s-ზე ძველი შეტყობინებების ავტომატური წაშლა" - "იგნორირება" - "წაიშალოს ყველა შეტყობინება, რომელსაც მედია აქვს?" - "წაიშალოს %s-ზე ძველი შეტყობინებები?" - "გსურთ %s-ზე უფრო ძველი შეტყობინებების წაშლა და ავტომატური წაშლის ჩართვა?" - "%s თქვა" - "თქვენ სთქვით," - "შეტყობინება %s -საგან" - "თქვენ გააგზავნეთ შეტყობინება" - "იგზავნება..." - "არ გაიგზავნა. შეეხეთ ისევ საცდელად." - "არ გაიგზავნა. მიმდინარეობს ხელახლა ცდა…" - "ხელახლა გაგზავნა ან წაშლა" - "გამოიძახეთ საგანგებო სამსახურები ხმოვანი ზარით. ტექსტური შეტყობინების გაგზავნა ამჟამად შეუძლებელია." - "ვერ განხორციელდა" - "ახალი ჩამოსატვირთი MMS შეტყობინება" - "ახალი MMS შეტ" - "ჩამოტვირთვა ვერ მოხერხდა" - "ხელახლა საცდელად შეეხეთ" - "შეეხეთ ჩამოსატვირთად" - "ჩამოტვირთვა ან წაშლა" - "მიმდინარეობს ჩამოტვირთვა…" - "შეტყობინებას ვადა გაუვიდა ან მიუწვდომელია." - "ზომა: %1$s, ვადა: %2$s" - "გაგზავნა ვერ ხერხდება. მიმღები არასწორია." - "სერვისი არ გააქტიურებულა ქსელში" - "ქსელის პრობლემის გამო ვერ გაიგზავნა" - "შეტყობინებას ვადა გაუვიდა ან მიუწვდომელია." - "(თემის გარეშე)" - "უცნობი გამგზავნი" - "მიწოდებულია" - "%1$s შეტყობინების ჩამოტვირთვა ვერ მოხერხდა %2$s-დან." - "მცირე მეხსიერების გამო მონაცემთა ბაზასთან დაკავშირებული ოპერაციის დასრულება ვერ მოხერხდა" - "შეტყობინება ვერ გაიგზავნა" - "შეტყობინებებში ზოგიერთი შეტყობინება ვერ გაიგზავნა" - - %d შეტყობინება %d მიმოწერაში - %d შეტყობინება ერთ მიმოწერაში - - "შეტყობინება არ ჩამოტვირთულა" - "შეტყობინებებში ზოგიერთი შეტყობინება ვერ ჩამოიტვირთა" - - %d შეტყობინება %d მიმოწერაში - %d შეტყობინება ერთ მიმოწერაში - - "შეტყობინება ნომერზე %1$s ვერ გაიგზავნა" - "გამოიძახეთ საგანგებო სამსახურები ხმოვანი ზარით. ტექსტური შეტყობინების გაგზავნა ნომერზე %1$s ამჟამად შეუძლებელია." - - %d ახალი შეტყობინება - ახალი შეტყობინება - - "დაწყება" - "კამერა მიუწვდომელია" - "კამერა მიუწვდომელია" - "ვიდეო ჩაწერა მიუწვდომელია" - "მედიას შენახვა ვერ ვერხდება" - "ვერ ხორციელდება სურათის გადაღება" - "უკან" - "დაარქივებული" - "წაშლა" - "დაარქივება" - "დეარქივაცია" - "შეტყობინებების გამორთვა" - "შეტყობინებების ჩართვა" - "კონტაქტის დამატება" - "ჩამოტვირთვა" - "გაგზავნა" - "წაშლა" - "წავშალო ეს შეტყობინება?" - "ამ ქმედებას ვერ გააუქმებთ." - "წაშლა" - - წაიშალოს ეს მიმოწერები? - წაიშალოს ეს მიმოწერა? - - "წაშლა" - "გაუქმება" - "მიმღები:" - "მრავალი სურათის არჩევა" - "არჩევანის დადასტურება" - "+%d" - "ხმის ჩაწერა ვერ ხერხდენა. სცადეთ ისევ." - "ხმის დაკვრა ვერ ხერხდება. სცადეთ ისევ." - "ხმის ჩაწერა ვერ მოხერხდა. სცადეთ ისევ." - "შეეხეთ & დააყოვნეთ" - ", " - " " - ": " - " " - "სურათი" - "აუდიო კლიპი" - "ვიდეო" - "კონტაქტის ბარათი" - "ჩამოტვირთვა" - "SMS-ით პასუხი" - "MMS-ით პასუხი" - "პასუხი" - - %d მონაწილე - %d მონაწილე - - "მე" - "კონტაქტი დაბლოკილია & დაარქივებულია" - "კონტაქტი განბლოკილია & amp; დაუარქივებელი" - "%d დაარქივებული" - "%d დაუარქივებელი" - "შეტყობინებები გამორთულია" - "შეტყობინებები ჩართულია" - "მზად არის. შეეხეთ ხელახლა გაგზავნას." - "შეტყობინებები წარმატებით დაყენებულია ნაგულისხმევ SMS აპად." - - დანართების გაუქმება - დანართის გაუქმება - - "აუდიო დანართი" - "აუდიო დანართის ჩართვა" - "პაუზა" - "შეტყობინება %s-საგან: %s." - "%s-საგან შეტყობინების შეცდომა: %s. დრო: %s." - "შეტყობინება %s-საგან: %s. დრო: %s." - "%s-სთვის გაუგზავნელი შეტყობინება: %s. დრო: %s." - "იგზავნება შეტყობინება %s-სთვის: %s. დრო: %s." - "%s-სთვის შეტყობინების შეცდომა: %s. დრო: %s." - "შეტყობინება %s-სთვის: %s. დრო: %s." - "%s-საგან შეტყობინების შეცდომა: %s. დრო: %s. %s." - "შეტყობინება %s-საგან: %s. დრო: %s. %s." - "გაუგზავნელი შეტყობინება %s-სთვის: %s. დრო: %s." - "იგზავნება შეტყობინება %s-სთვის: %s. დრო: %s." - "%s-სთვის შეტყობინების შეცდომა: %s. დრო: %s." - "შეტყობინება %s-სთვის: %s. დრო: %s." - "შეტყობინების შეცდომა. შეეხეთ, რათა ხელახლა სცადოთ." - "საუბარი %s -სთან" - "თემის წაშლა" - "ვიდეოს გადაღება" - "ფოტოსურათის აღბეჭდვა" - "სურათის გადაღება" - "ვიდეოს ჩაწერის დაწყება" - "სრულეკრანიან კამერაზე გადართვა" - "წინა და უკანა კამერებს შორის გადართვა" - "ჩაწერის შეჩერება და ვიდეოს მიბმა" - "ვიდეოს ჩაწერის შეჩერება" - "შეტყობინებების ფოტოები" - - %d ფოტო შენახულია ალბომში „%s - %d ფოტო შენახულია ალბომში „%s - - - %d ვიდეო შენახულია ალბომში „%s - %d ვიდეო შენახულია ალბომში „%s - - - %d მიმაგრებული ფაილი შენახულია ალბომში „%s - %d მიმაგრებული ფაილი შენახულია ალბომში „%s - - - %d მიმაგრებული ფაილი შენახულია „ჩამოტვირთვებში“ - %d მიმაგრებული ფაილი შენახულია „ჩამოტვირთვებში“ - - - შენახულია %d დანართი - შენახულია %d დანართი - - - %d დანართის შენახვა ვერ მოხერხდა - %d დანართის შენახვა ვერ მოხერხდა - - "MMS დანართი შენახულია" - "პარამეტრები" - "დაარქივებული" - "დახურვა" - "MMS" - "გაფართოებული" - "გამართვა" - "შეტყობინებები" - "ხმა" - "დადუმებული" - "ვიბრაცია" - "დაბლოკილი" - "SMS მისვლის შეტყობინებები" - "ყოველ გაგზავნილ SMS-ზე მიწოდების დადასტურების მოთხოვნა" - "ავტო-მოძიება" - "MMS შეტყობინებების ავტომატურად ჩამოტვირთვა" - "ავტო ჩამოტვირთვა როუმინგში" - "როუმინგის დროს MMS-ის ავტომატური ჩამოტვირთვა" - "ჯგუფური შეტყობინება" - "მრავალი მიმღების შემთხვევაში ერთიანი შეტყობინების გასაგზავნად MMS-ის გამოყენება" - "ნაგულისხმევი SMS აპლიკაცია" - "ნაგულისხმევი SMS აპლიკაცია" - - - "თქვენი ტელეფონის ნომერი" - "უცნობი" - "გამავალი შეტყობინების ჟღერადობები" - "SMS-ის ჩაწერა" - "მიღებული SMS-ის დაუმუშავებელი მონაცემების გარე საცავის ფაილში ჩაწერა" - "MMS-ის ჩაწერა" - "მიღებული MMS-ის დაუმუშავებელი მონაცემების გარე საცავის ფაილში ჩაწერა" - "უსადენო გაფრთხილებები" - "შეტყობინების პარამეტრები" - "ტექსტის კოპირება" - "დეტალების ნახვა" - "წაშლა" - "გადაგზავნა" - "შეტყობინების მონაცემები" - "ტიპი: " - "ტექსტური შეტყობინება" - "მულტიმედია შეტყობინება" - "გამგზავნი: " - "მიმღები: " - "გაიგზავნა: " - "მიღებულია: " - "თემა: " - "ზომა: " - "პრიორიტეტი: " - "SIM: " - "მაღალი" - "ჩვეულებრივი" - "დაბალი" - "SIM %s" - "გამგზავნის მისამართი დამალულია" - "დანართების ჩატვირთვისას შეტყობინება ვერ გაიგზავნება." - "დანართი ვერ იტვირთება. სცადეთ ისევ." - "ქსელი მზად არ არის. ისევ სცადეთ." - "შემდეგის წაშლა" - "ტექსტისა და რიცხვების შეყვანას შორის გადართვა" - "მეტი მონაწილის დამატება" - "მონაწილეების დადასტურება" - "ახალი მიმოწერის დაწყება" - "ამ ერთეულის არჩევა" - "ვიდეოს დაკვრა" - "ხალხი & ვარიანტები" - "გამართვა" - "ხალხი & ვარიანტები" - "ზოგადი" - "ამ საუბრის მონაწილეები" - "დარეკვა" - "შეტყობინების გაგზავნა" - "შეტყობინების გაგზავნა<br/><small>%s-დან</small>" - - ფოტოების გაგზავნა - ფოტოს გაგზავნა - - - აუდიო ჩანაწერების გაგზავნა - აუდიო ჩანაწერის გაგზავნა - - - ვიდეოების გაგზავნა - ვიდეოს გაგზავნა - - - კონტაქტის ბარათების გაგზავნა - კონტაქტის ბარათის გაგზავნა - - - დანართების გაგზავნა - დანართის გაგზავნა - - - %d დანართი მზად არის გასაგზავნად - გასაგზავნად მზად არის ერთი დანართი - - "უკუკავშირის გაგზავნა" - "Google Play Store-ში ნახვა" - "ვერსიის ინფორმაცია" - "ვერსია %1$s" - "ღია კოდის ლიცენზიები" - "შეტყობინებები" - "მიღწეულია დანართების ლიმიტი" - "დანართი ვერ ჩაიტვირთა." - "დაემატოს კონტაქტებში?" - "კონტაქტის დამატება" - "თემა" - "თემა: " - "%s%s" - "იტვირთება კონტაქტის ბარათი" - "საკონტაქტო ბარათი ვერ იტვირთება" - "კონტაქტის ბარათის ნახვა" - - %d კონტაქტი - %d კონტაქტი - - "კონტაქტის ბარათები" - "დაბადების თარიღი" - "ჩანიშვნები" - "შეტყობინების გადაგზავნა" - "პასუხი" - "+1" - "+%d" - "SMS გამორთულია" - "გასაგზავნად დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად" - "დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად" - "შეცვლა" - "შეტყობინებების მისაღებად დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად" - "მოკლე ტექსტური შეტყობინებების გაგზავნისათვის, არ არის არჩეული უპირატესი SIM ბარათი" - "ეს აპი მოწყობილობის მფლობელის მიერ ნებადართული არ არის." - "კარგი" - "მიმოწერაში ძალიან ბევრი მონაწილეა" - - კონტაქტები არასწორია - კონტაქტი არასწორია - - "კამერის სურათი ვერ ჩაიტვირთა" - "თქვენ: " - "%s: " - "მონახაზი" - "როდესაც საუბარს წამოიწყებთ, ისინი აქ გამოჩნდება" - "დაარქივებული მიმოწერები აქ გამოჩნდება" - "მიმოწერები იტვირთება…" - "სურათი" - "აუდიო კლიპი" - "ვიდეო" - "კონტაქტის ბარათი" - "MMS" - "დაბრუნება" - "ხელახლა ცდა" - "ახალი შეტყობინების დასაწყებად შეიყვანეთ საკონტაქტო სახელი ან ტელეფონის ნომერი" - "დაბლოკვა" - "%s-ის დაბლოკვა" - "%s-ის განბლოკვა" - "%s-ის დაბლოკვა?" - "ამ ნომრიდან შეტყობინებებს კვლავ მიიღებთ, თუმცა შეხსენებები აღარ გამოგეგზავნებათ. ეს მიმოწერა დაარქივდება." - "დაბლოკილი კონტაქტები" - "განბლოკვა" - "დაბლოკილი კონტაქტები" - "აირჩიეთ სურათი დოკუმენტების ბიბლიოთეკიდან" - "შეტყობინება იგზავნება" - "შეტყობინება გაიგზავნა" - "მობილური ინტერნეტი გამორთულია. გადაამოწმეთ პარამეტრები." - "თვითმფრინავის რეჟიმში შეტყობინებების გაგზავნა ვერ ხერხდება" - "შეტყობინება ვერ გაიგზავნა" - "შეტყობინება გადმოწერილია" - "მობილური ინტერნეტი გამორთულია. გადაამოწმეთ პარამეტრები." - "თვითმფრინავის რეჟიმში შეტყობინების ჩამოტვირთვა ვერ ხორციელდება" - "შეტყობინების ჩამოტვირთვა ვერ ხორციელდება" - "ნული" - "ერთი" - "ორი" - "სამი" - "ოთხი" - "ხუთი" - "ექვსი" - "შვიდი" - "რვა" - "ცხრა" - "შეტყობინება ვერ იგზავნება: %1$s-ის შეცდომა %2$d" - "შეტყობინება ვერ იგზავნება უცნობი ოპერატორით, შეცდომა %1$d" - "Fwd: %s" - "შეტყობინება ვერ გაიგზავნა: სერვისი ქსელში გააქტიურებელი არ არის" - "შეტყობინება ვერ გაიგზავნა: სამიზნე მისამართი არასწორია" - "შეტყობინება ვერ გაიგზავნა: შეტყობინება არასწორია" - "შეტყობინება ვერ გაიგზავნა: კონტენტი მხარდაჭერილი არ არის" - "შეტყობინება ვერ გაიგზავნა: შეტყობინება მხარდაჭერილი არ არის" - "შეტყობინება არ გაიგზავნა: ის ძალიან დიდია" - "ახალი შეტყობინება" - "ნახვა" - "სურათი" - "შესაბამისი აპლიკაცია ვერ მოიძებნა" - "მიმღების ამოღება" - "ახალი შეტყობინება" - "გაუქმება" - "წვდომის წერტილის რედაქტირება" - "არ არის დაყენებული" - "სახელი" - "APN" - "MMSC" - "MMS პროქსი-სერვერი" - "MMS პორტი" - "MCC" - "MNC" - "APN ტიპი" - "APN-ის წაშლა" - "ახალი APN" - "შენახვა" - "გაუქმება" - "სახელის ველი არ შეიძლება იყოს ცარიელი." - "APN არ უნდა იყოს ცარიელი." - "MCC ველი უნდა შეიცავდეს 3 ციფრს." - "MNC ველი უნდა შეიცავდეს ორ ან სამ ციფრს." - "ნაგულისხმევი APN პარამეტრების აღდგენა." - "საწყის პარამეტრებზე ჩამოყრა" - "ნაგულისხმევი APN პარამეტრების აღდგენა დასრულებულია." - "უსათაურო" - "წვდომის წერტილის სახელები (APNs)" - "APN-ები" - "ახალი APN" - "წვდომის წერტილის (APN) პარამეტრები ამ მომხმარებლისათვის მიუწვდომელია" - "გსურთ ბუფერში კოპირება?" - "კოპირება" - "%s-ზე" - "ზოგადი" - "გაფართოებული" - "ძირითადი პარამეტრები" - "დამატებითი პარამეტრები" - "„%s“ SIM" - "ყველა მიმღებთან ინდივიდუალური SMS შეტყობინების გაგზავნა. პასუხებს მხოლოდ თქვენ მიიღებთ" - "ყველა მიმღებთან ერთი MMS-ის გაგზავნა" - "უცნობი ნომერი" - "ახალი შეტყობინება" - "ახალი შეტყობინება." - "SIM სელექტორი" - "არჩეულია %1$s, SIM სელექტორი" - "თემის რედაქტირება" - "აირჩიეთ SIM ან შეცვალეთ სათაური" - "აუდიო ჩაწერისათვის შეეხეთ და დააყოვნეთ" - "ახალი მიმოწერის დაწყება" - "შეტყობინებები" - "შეტყობინებების სია" - "შეტყობინებები" - "ახალი შეტყობინება" - "მიმოწერათა სია" - "მიმოწერები იტვირთება…" - "მიმდინარეობს შეტყობინებების ჩატვირთვა" - "მეტი მიმოწერის ნახვა" - "იხილეთ სხვა შეტყობინებები" - "მიმოწერა წაშლილია" - "მიმოწერა წაშლილია. შეეხეთ შეტყობინებების სხვა მიმოწერის საჩვენებლად" - "დაბლოკილი" - "განბლოკილი" - "შენახვის სივრცეში შემცირებულია. ზოგიერთი მონაცემი შეიძლება დაიკარგოს." - "დანართის არჩევა" - "არჩევანის დადასტურება" - "%d შერჩეულია" - "წაშალეთ ერთი ან რამდენიმე დანართი და ხელახლა სცადეთ." - "შეგიძლიათ სცადოთ შეტყობინების გაგზავნა, მაგრამ შეიძლება მისი მიწოდება ვერ მოხერხდეს, თუ არ წაშლით ერთ ან რამდენიმე დანართს." - "შეტყობინებით თქვენ შეგიძლიათ გააგზავნოთ მხოლოდ ერთი ვიდეო. გთხოვთ ამოშალეთ დამატებითი ვიდეოები და სცადეთ კიდევ ერთხელ." - "შეტყობინებებში დანართის ჩატვირთვა ვერ მოხერხდა." - "მაინც გაგზავნა" - "საუბრის დაწყება ვერ მოხერხდა" - "%1$s (%2$s)" - "შერჩეულია %s" - diff --git a/res/values-kk-rKZ/arrays.xml b/res/values-kk-rKZ/arrays.xml deleted file mode 100644 index b49d0cc..0000000 --- a/res/values-kk-rKZ/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "тақырыпсыз" - "тақырып жоқ" - - - "Иә" - "Жоқ" - "Жарайды" - "Хехе" - "Рақмет" - "Келісемін" - "Жақсы" - "Жолдамын" - "Жарайды, кейінірек хабарласамын" - ":)" - ":(" - - diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml deleted file mode 100644 index 5164592..0000000 --- a/res/values-kk-rKZ/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Хабар алмасу" - "Хабар алмасу" - "Сөйлесуді таңдау" - "Параметрлер" - "Хабар жіберу" - "Тіркеме қосу" - "Анықтама" - "Қош келдіңіз" - "Өткізіп жіберу" - "Келесі >" - "Келесі" - "Шығу" - "Параметрлер >" - "Параметрлер" - "Messaging қолданбасына SMS, \"Телефон\" және \"Контактілер\" қолданбаларына рұқсат қажет." - "Рұқсаттарды \"Параметрлер\" > \"Қолданбалар\" > \"Messaging\" > \"Рұқсаттар\" тармағында өзгертуге болады." - "Рұқсаттарды \"Параметрлер\", \"Қолданбалар\", \"Messaging\", \"Рұқсаттар\" тармағында өзгерте аласыз." - "Жиі қолданылатын" - "Барлық контактілер" - "%s орнына жіберу" - "Суреттер немесе бейне түсіру" - "Осы құрылғыдан кескіндерді таңдау" - "Аудио жазу" - "Фотосуретті таңдау" - "Медиа таңдалды." - "Медиадан таңдау алынды." - "%d таңдалды" - "кескін %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "кескін" - "Аудио жазу" - "Бөлісу" - "Қазір ғана" - "Қазір" - - %d мин. - %d мин. - - - %d сағат - %d сағат - - - %d күн - %d күн - - - %d апта - апта - - - %d ай - ай - - - %d жыл - жыл - - "0 сыныпты хабар" - "Сақтау" - "Құрылғыдағы бос орын аз. \"Хабар алмасу\" орын босату үшін ескі хабарларды автоматты түрде жояды." - "Жадта бос орын бітуде" - "\"Хабар алмасу\" қолданбасы құрылғыда көбірек бос орын қол жетімді болғанша хабарлар жібере немесе ала алмауы мүмкін." - "SMS жадында орын аз. Хабарларды жою керек болуы мүмкін." - "Телефон нөміріңізді растау" - "Бұл бір реттік қадам Messaging қолданбасының топтық хабарларды тиісті түрде жеткізуін қамтамасыз етеді." - "Телефон нөмірі" - "Тасушы арқылы барлық хабарларды жою" - "%s уақыттан асқан хабарларды жою" - "%s ескірек хабарларды автоматты түрде жою" - "Елемеу" - "Медиа бар барлық хабарларды жою керек пе?" - "%s ескірек хабарларды жою керек пе?" - "%s ескірек хабарларды жою және автоматты түрде жоюды қосу керек пе?" - "%s айтты" - "Сіз айттыңыз" - "Хабар, кімнен: %s" - "Сіз хабар жібердіңіз" - "Жіберілуде…" - "Жіберілмеді. Әрекетті қайталау үшін түртіңіз." - "Жіберілмеді. Әрекет қайталануда…" - "Қайта жіберу немесе жою" - "Жедел қызметтерге дауыстық қоңырау соғыңыз. Бұл ретте мәтіндік хабарды жеткізу мүмкін болмады." - "Сәтсіз аяқталды" - "Жүктейтін жаңа MMS хабары бар" - "Жаңа MMS хабары" - "Жүктеу мүмкін болмады" - "Әрекетті қайталау үшін түртіңіз" - "Жүктеу үшін түртіңіз" - "Жүктеу немесе жою" - "Жүктелуде…" - "Хабар мерзімі бітті немесе ол қол жетімді емес" - "өлшемі: %1$s, мерзімі бітетін уақыт: %2$s" - "Жіберу мүмкін емес. Алушы жарамсыз." - "Қызмет желіде белсендірілмеген" - "Желілік мәселеге байланысты жіберу мүмкін болмады" - "Хабар мерзімі бітті немесе ол қол жетімді емес" - "(Тақырыпсыз)" - "Белгісіз хабар жіберуші" - "Жеткізілді" - "%1$s хабарын %2$s арқылы жүктеу мүмкін болмады." - "Жад көлемінің аз болуына байланысты дерекқор әрекетін аяқтау мүмкін болмады" - "Хабар жіберілмеді" - "Messaging қолданбасында кейбір хабарлар жіберілмеген" - - %d сөйлесуде %d хабар - бір сөйлесуде %d хабар - - "Хабар жүктелмеген" - "Messaging қолданбасында кейбір хабарлар жүктелмеген" - - %d сөйлесуде %d хабар - бір сөйлесуде %d хабар - - "%1$s нөміріне хабар жіберілмеді" - "Жедел қызметтерге дауыстық қоңырау соғыңыз. Бұл ретте %1$s нөміріне мәтіндік хабарды жеткізу мүмкін болмады." - - %d жаңа хабар - Жаңа хабар - - "Бастау" - "Камера қол жетімді емес" - "Камера қол жетімді емес" - "Бейне түсіру қол жетімді емес" - "Медианы сақтау мүмкін емес" - "Сурет түсіру мүмкін емес" - "Артқа" - "Мұрағатталды" - "Жою" - "Мұрағаттау" - "Мұрағаттан шығару" - "Хабарландыруларды өшіру" - "Хабарландыруларды қосу" - "Контакт қосу" - "Жүктеу" - "Жіберу" - "Жою" - "Бұл хабарды жою қажет пе?" - "Бұл әрекетті болдырмау мүмкін емес." - "Жою" - - Осы сөйлесулерді жою қажет пе? - Осы сөйлесуді жою қажет пе? - - "Жою" - "Бас тарту" - "Кімге" - "Бірнеше кескінді таңдау" - "Таңдауды растау" - "+%d" - "Аудионы жазу мүмкін емес. Әрекетті қайталаңыз." - "Аудионы ойнату мүмкін емес. Әрекетті қайталаңыз." - "Аудионы сақтау мүмкін болмады. Әрекетті қайталаңыз." - "Түрту және ұстап тұру" - ", " - " " - ": " - " " - "Фотосурет" - "Аудиоклип" - "Бейне" - "Контакт карточкасы" - "Жүктеу" - "SMS арқылы жауап беру" - "MMS ар. жау. б." - "Жауап беру" - - %d қатысушы - %d қатысушы - - "Мен" - "Контакт бөгелді және мұрағатталды" - "Контакт құлыптан босатылды және мұрағаттан шығарылды" - "%d мұрағатталды" - "%d мұрағаттан шығарылды" - "Хабарландырулар өшірілген" - "Хабарландырулар қосылған" - "Бәрі орнатылды. «Қайта жіберу» пәрменін түртіңіз." - "\"Хабар алмасу\" қолданбасы әдепкі SMS қолданбасы ретінде сәтті орнатылды." - - Тіркемелерді алып тастау - Тіркемені алып тастау - - "Аудио тіркеме" - "Аудио тіркемені ойнату" - "Кідірту" - "%s хабары: %s." - "%s сәтсіз хабары: %s. Уақыт: %s." - "%s хабары: %s. Уақыт: %s." - "%s тобына жіберілмеген хабар: %s. Уақыт: %s." - "%s тобына хабар жіберілуде: %s. Уақыт: %s." - "%s тобына сәтсіз хабар: %s. Уақыт: %s." - "%s тобына хабар: %s. Уақыт: %s." - "%s сәтсіз хабары: %s. Уақыт: %s. %s." - "%s хабары: %s. Уақыт: %s. %s." - "%s тобына жіберілмеген хабар: %s. Уақыт: %s." - "%s тобына хабар жіберілуде: %s. Уақыт: %s." - "%s тобына сәтсіз хабар: %s. Уақыт: %s." - "%s тобына хабар: %s. Уақыт: %s." - "Сәтсіз хабар. Қайталау үшін түртіңіз." - "%s адаммен сөйлесу" - "Тақырыпты жою" - "Бейне түсіру" - "Фотосурет түсіру" - "Сурет түсіру" - "Бейне жазуды бастау" - "Толық экранды камераға ауысу" - "Алдыңғы және артқы камера арасында ауысу" - "Жазуды тоқтату және бейне тіркеу" - "Бейне жазуды тоқтату" - "Messaging фотосуреттері" - - %d фотосурет «%s» альбомына сақталды - %d фотосурет «%s» альбомына сақталды - - - %d бейне «%s» альбомына сақталды - %d бейне «%s» альбомына сақталды - - - %d тіркеме «%s» альбомына сақталды - %d тіркеме «%s» альбомына сақталды - - - %d тіркеме «Жүктеулер» ішіне сақталды - %d тіркеме «Жүктеулер» ішіне сақталды - - - %d тіркеме сақталды - %d тіркеме сақталды - - - %d тіркемені сақтау мүмкін болмады - %d тіркемені сақтау мүмкін болмады - - "MMS тіркемесі сақталды" - "Параметрлер" - "Мұрағатталды" - "Жабу" - "MMS" - "Кеңейтілген" - "Күйін келтіру" - "Хабарландырулар" - "Дыбыс" - "Үнсіз" - "Дірілдеу" - "Бөгелген" - "SMS жеткізу туралы есептер" - "Әр жіберілген SMS үшін жеткізу туралы есепті сұрау" - "Авто шығарып алу" - "MMS хабарларын автоматты түрде шығарып алу" - "Роуминг кезінде авто-шығарып алу" - "Роумингте MMS автоматты түрде шығарып алу" - "Топтық хабар алмасу" - "Бірнеше алушыға бір хабар жіберу үшін MMS пайдалану" - "Әдепкі SMS қолданбасы" - "Әдепкі SMS қолданбасы" - - - "Телефон нөміріңіз" - "Белгісіз" - "Шығыс хабар дыбыстары" - "SMS дампын сақтау" - "Алынған SMS өңделмеген деректерінің дампын сыртқы қойма файлына сақтау" - "MMS дампын сақтау" - "Алынған MMS өңделмеген деректерінің дампын сыртқы қойма файлына сақтау" - "Сымсыз дабылдар" - "Хабар опциялары" - "Мәтінді көшіру" - "Мәліметтерді көру" - "Жою" - "Қайта жіберу" - "Хабар мәліметтері" - "Tүрі: " - "Мәтіндік-хабар" - "Mультимедиалық хабар" - "Кімнен: " - "Кімге: " - "Жіберілді: " - "Қабылданды: " - "Тақырып: " - "Өлшем: " - "Басымдық: " - "SIM: " - "Жоғары" - "Қалыпты" - "Төмен" - "SIM %s" - "Жіберуші мекенжайы жасырын" - "Тіркемелерді жүктеу кезінде хабар жіберу мүмкін емес." - "Тіркемені жүктеу мүмкін емес. Әрекетті қайталаңыз." - "Желі дайын емес. Әрекетті қайталаңыз." - "Мәтінді жою" - "Мәтінді және сандарды енгізу арасында ауысу" - "Қосымша қатысушыларды қосу" - "Қатысушыларды растау" - "Жаңа сөйлесуді бастау" - "Осы элементті таңдау" - "Бейнені ойнату" - "Адамдар және опциялар" - "Күйін келтіру" - "Адамдар және опциялар" - "Жалпы" - "Осы сөйлесудегі адамдар" - "Қоңырау шалу" - "Хабарды жіберу" - "<br/><small>%s</small> картасынан хабар жіберу" - - Фотосуреттер жіберу - Фотосурет жіберу - - - Аудиолар жіберу - Аудио жіберу - - - Бейнелер жіберу - Бейне жіберу - - - Контакті карточкаларын жіберу - Контакті карточкасын жіберу - - - Тіркемелер жіберу - Тіркеме жіберу - - - %d тіркеме жіберуге дайын - Бір тіркеме жіберуге дайын - - "Пікір жіберу" - "Google Play Store дүкенінде көру" - "Нұсқа туралы ақпарат" - "Нұсқа: %1$s" - "Ашық бастапқы код лицензиялары" - "Хабарландырулар" - "Тіркеме шегіне жеттіңіз" - "Тіркемені жүктеу сәтсіз аяқталды." - "Контактілерге қосу керек пе?" - "Контакт қосу" - "Тақырып" - "Тақырып: " - "%s%s" - "Контакт карточкасы жүктелуде" - "Контакт картасын жүктеу мүмкін болмады" - "Контакт карточкасын көру" - - %d контакт - %d контакт - - "Контакт карталары" - "Туған күн" - "Ескертпелер" - "Хабарды қайта жіберу" - "Жауап беру" - "+1" - "+%d" - "SMS өшірілген" - "Жіберу үшін \"Хабар алмасу\" қолданбасын әдепкі SMS қолданбасы ретінде орнатыңыз" - "\"Хабар алмасу\" қолданбасын әдепкі SMS қолданбасы ретінде орнату" - "Өзгерту" - "Хабарлар алу үшін Messaging қолданбасын әдепкі SMS қолданбасы ретінде орнатыңыз" - "SMS хабарларын жіберу үшін таңдаулы SIM таңдалмаған" - "Құрылғы иесі бұл қолданбаға рұқсат етпеген." - "Жарайды" - "Сөйлесуде қатысушылар тым көп" - - Жарамсыз контактілер - Жарамсыз контакті - - "Камера кескінін жүктеу мүмкін болмады" - "Сіз: " - "%s: " - "Жоба" - "Басталған жаңа сөйлесуді осы жерде тізімнен көресіз" - "Мұнда мұрағатталған сөйлесулер көрсетіледі" - "Сөйлесулер жүктелуде…" - "Сурет" - "Аудиоклип" - "Бейне" - "Контакт карточкасы" - "MMS" - "Болдырмау" - "Қайталау" - "Жаңа хабарды бастау үшін контакт атын немесе телефон нөмірін енгізіңіз" - "Бөгеу" - "%s бөгеу" - "%s бөгеуден шығару" - "%s бөгеу керек пе?" - "Сіз осы нөмірден хабарлар алуды жалғастырасыз, бірақ енді хабардар етілмейсіз. Бұл сөйлесу мұрағатталады." - "Бөгелген контактілер" - "БӨГЕУДЕН ШЫҒАРУ" - "Бөгелген контактілер" - "Құжаттар кітапханасынан кескінді таңдау" - "Хабар жіберілуде" - "Хабар жіберілді" - "Ұялы деректер өшірілген. Параметрлерді тексеріңіз." - "Ұшақ режимінде хабарлар жіберу мүмкін емес" - "Хабарды жіберу мүмкін болмады" - "Хабар жүктелді" - "Ұялы деректер өшірілген. Параметрлерді тексеріңіз." - "Ұшақ режимінде хабарларды жүктеу мүмкін емес" - "Хабарды жүктеу мүмкін болмады" - "Нөл" - "Бір" - "Екі" - "Үш" - "Төрт" - "Бес" - "Алты" - "Жеті" - "Сегіз" - "Тоғыз" - "%1$s арқылы хабар жіберілмейді, %2$d қатесі" - "Белгісіз оператормен хабар жіберілмейді, %1$d қатесі" - "Қайта жіберілген: %s" - "Хабар жіберілмеді, желіде қызмет белсендірілмеген" - "Хабар жіберілмеді: жарамсыз межелі мекенжай" - "Хабар жіберілмеді: жарамсыз хабар" - "Хабар жіберілмеді: қолдау көрсетілмейтін мазмұн" - "Хабар жіберілмеді: қолдау көрсетілмейтін хабар" - "Хабар жіберілмеді: тым үлкен" - "Жаңа хабар" - "Көру" - "Кескін" - "Тиісті қолданбаны табу мүмкін болмады" - "Алушыны жою" - "Жаңа хабар" - "Бас тарту" - "Кіру нүктесін өңдеу" - "Орнатылмаған" - "Аты" - "APN" - "MMSC" - "MMS прокси" - "MMS порты" - "MCC" - "MNC" - "APN түрі" - "APN жою" - "Жаңа APN" - "Сақтау" - "Алып тастау" - "Атау өрісі бос болмауы тиіс." - "APN бос болмауы тиіс." - "MCC өрісі 3 цифрдан тұруы тиіс." - "MNC өрісі 2 немесе 3 цифрдан тұруы тиіс." - "Әдепкі APN параметрлерін қалпына келтіру." - "Әдепкі параметрлерге қалпына келтіру" - "Әдепкі APN параметрлерін қалпына келтіру аяқталды." - "Атаусыз" - "Кіру нүктесі атаулары" - "APNs (Кіру нүктесі атаулары)" - "Жаңа APN" - "Кіру нүктесі атауының параметрлері осы пайдаланушы үшін қол жетімді емес" - "Аралық сақтағышқа көшіру керек пе?" - "Көшіру" - "%s картасына" - "Жалпы" - "Кеңейтілген" - "Жалпы параметрлер" - "Қосымша параметрлер" - %s» SIM" - "Барлық алушыларға SMS хабарларын жіберіңіз. Жауаптарды тек сіз аласыз" - "Барлық алушыларға бір MMS жіберу" - "Белгісіз нөмір" - "Жаңа хабар" - "Жаңа хабар" - "SIM таңдағыш" - "%1$s таңдалды, SIM таңдағыш" - "Тақырыпты өңдеу" - "SIM картасын таңдау немесе тақырыпты өңдеу" - "Аудио жазу үшін түртіп, ұстап тұрыңыз" - "Жаңа сөйлесуді бастау" - "Хабар алмасу" - "Хабар алмасу тізімі" - "Хабар алмасу" - "Жаңа хабар" - "Сөйлесімдер тізімі" - "Сөйлесулер жүктелуде" - "Хабарлар жүктелуде" - "Көбірек сөйлесулерді көру" - "Қосымша хабарларды көру" - "Сөйлесу жойылды" - "Сөйлесу жойылды. Басқа \"Хабар алмасу\" сөйлесуін көрсету үшін түртіңіз" - "Бөгелген" - "Бөгеуден шығарылған" - "Жадтағы бос орын аз. Кейбір деректер жоғалуы мүмкін." - "Тіркемелерді таңдау" - "Таңдауды растау" - "%d таңдалды" - "Бір немесе бірнеше тіркемені жойып, әрекетті қайталаңыз." - "Хабарды жіберіп көре аласыз, бірақ бір немесе бірнеше тіркемені жоймасаңыз, ол жеткізілмеуі мүмкін." - "Әр хабарламада бір ғана бейне жіберуге болады. Қосымша бейнелерді жойып, әрекетті қайталаңыз." - "Messaging тіркемені жүктей алмады." - "Бәрібір жіберу" - "Сөйлесуді бастау мүмкін болмады" - "%1$s (%2$s)" - "%s таңдалған" - diff --git a/res/values-km-rKH/arrays.xml b/res/values-km-rKH/arrays.xml deleted file mode 100644 index 69f6e0d..0000000 --- a/res/values-km-rKH/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "គ្មាន​ប្រធានបទ" - "គ្មាន​ប្រធាន​បទ" - - - "បាទ/ចាស" - "ទេ" - "យល់ព្រម" - "ហេហេ" - "អរគុណ" - "ខ្ញុំ​យល់​ព្រម" - "ល្អ" - "នៅ​តាម​ផ្លូវ​" - "យល់ព្រម ខ្ញុំ​នឹង​ទាក់ទង​អ្នក​ពេល​ក្រោយ" - ":)" - ":(" - - diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml deleted file mode 100644 index 1806cc7..0000000 --- a/res/values-km-rKH/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "​ការ​ផ្ញើ​សារ" - "ការ​​ផ្ញើ​សារ" - "ជ្រើសរើសការសន្ទនា" - "ការកំណត់" - "ផ្ញើ​សារ" - "បន្ថែម​ឯកសារ​ភ្ជាប់" - "ជំនួយ" - "សូមស្វាគមន៍" - "រំលង" - "បន្ទាប់ >" - "បន្ទាប់" - "ចេញ" - "ការកំណត់ >" - "ការកំណត់" - "ការផ្ញើសារត្រូវការសិទ្ធិអនុញ្ញាតដើម្បីប្រើសារ SMS, ទូរស័ព្ទ និងទំនាក់ទំនង។" - "អ្នកអាចប្តូរសិទ្ធិអនុញ្ញាតនៅក្នុង ការកំណត់ > កម្មវិធី > ការផ្ញើសារ > សិទិ្ធអនុញ្ញាត។" - "អ្នកអាចប្តូរសិទ្ធិអនុញ្ញាតនៅក្នុង ការកំណត់ , កម្មវិធី, ការផ្ញើសារ, សិទ្ធិអនុញ្ញាត។" - "ញឹកញាប់" - "ទំនាក់ទំនងទាំងអស់" - "ផ្ញើ​ទៅ %s" - "ថត​រូបភាព ឬ​វីដេអូ" - "ជ្រើស​រូបភាព​ពី​ឧបករណ៍​នេះ" - "ថត​សំឡេង" - "ជ្រើស​រូបថត" - "បានជ្រើសឯកសារកំសាន្ត។" - "បានលុបការជ្រើសឯកសារកំសាន្ត។" - "បាន​ជ្រើស %d" - "រូបភាព %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "រូបភាព" - "ថត​សំឡេង" - "ចែករំលែក​" - "ថ្មីៗ​នេះ" - "ឥឡូវ" - - %d នាទី - %d នាទី - - - %d ម៉ោង - %d ម៉ោង - - - %d ថ្ងៃ - %d ថ្ងៃ - - - %d សប្តាហ៍ - មួយសប្តាហ៍ - - - %d ខែ - មួយខែ - - - %d ឆ្នាំ - មួយឆ្នាំ - - "សារ​ថ្នាក់ ០" - "រក្សាទុក" - "ឧបករណ៍នៅសល់ទំហំផ្ទុកតិច។ ការផ្ញើសារនឹងលុបសារចាស់ៗដើម្បីធ្វើឲ្យមានទំហំផ្ទុកទំនេរ។" - "អស់​ទំហំ​ផ្ទុក" - "ការផ្ញើសារអាចនឹងមិនផ្ញើ ឬទទួលសារទេ រហូតដល់មានទំហំផ្ទុកបន្ថែមទៀតនៅលើឧបករណ៍របស់អ្នក។" - "ជិត​អស់​ទំហំ​ផ្ទុក​សារ SMS ។ អ្នក​អាច​​​លុប​សារ​មួយ​ចំនួន។" - "បញ្ជាក់លេខទូរស័ព្ទរបស់អ្នក" - "ជំហានមួយលើកនេះធានាថាការផ្ញើសារនឹងបញ្ជូនសារក្រុមរបស់អ្នកបានត្រឹមត្រូវ។" - "លេខទូរស័ព្ទ" - "លុប​សារ​​​​ដែល​មាន​មេឌៀ​ទាំងអស់" - "លុប​សារ​ចាស់​ជាង %s" - "លុប​សារ​ចាស់​ជាង %s ដោយ​ស្វ័យ​ប្រវត្តិ" - "មិន​អើពើ" - "លុប​សារ​ទាំងអស់​មាន​ក្នុង​មេឌៀ?" - "លុប​សារ​ចាស់​ជាង %s?" - "លុប​សារ​ចាស់​ជាង %s និង​បើក​ការ​លុប​ស្វ័យ​ប្រវត្តិ?" - "%s និយាយថា" - "អ្នកនិយាយថា" - "សារពី %s" - "អ្នកបានផ្ញើសារមួយ" - "កំពុង​ផ្ញើ..." - "មិន​បាន​ផ្ញើ។ ប៉ះ​ដើម្បី​ព្យាយាម​ម្ដងទៀត។" - "មិន​បាន​ផ្ញើ។ កំពុង​ព្យាយាម​ម្ដងទៀត..." - "ផ្ញើ​ម្ដង​ទៀត ឬ​លុប" - "សូមធ្វើការហៅជាសម្លេងទៅកាន់សេវាកម្មសង្គ្រោះបន្ទាន់។ សារជាអក្សររបស់អ្នកមិនអាចបញ្ជូនបានទេនៅពេលនេះ។" - "បាន​បរាជ័យ" - "សារ MMS ថ្មី​ត្រូវ​ទាញ​យក" - "សារ MMS ថ្មី" - "មិន​អាច​ទាញ​យក" - "ប៉ះ​ ដើម្បី​ព្យាយាម​ម្តង​ទៀត" - "ប៉ះ​ ដើម្បី​ទាញ​យក" - "ទាញ​យក ​ឬ​លុប" - "កំពុង​ទាញ​យក..." - "សារ​បាន​​ផុត​កំណត់​ ឬ​មិន​អាច​ប្រើ​បាន" - "ទំហំ៖ %1$s, ផុត​កំណត់៖ %2$s" - "មិន​អាច​ផ្ញើ។ អ្នក​ទទួល​មិន​ត្រឹមត្រូវ។" - "សេវា​មិន​សកម្ម​នៅ​លើ​បណ្ដាញ" - "មិន​អាច​ផ្ញើ​ដោយសារ​បញ្ហា​បណ្ដាញ" - "សារ​បាន​​ផុត​កំណត់​ ឬ​មិន​អាច​ប្រើ​បាន" - "(គ្មាន​ប្រធាន​បទ)" - "មិន​ស្គាល់​អ្នក​ផ្ញើ" - "បាន​បញ្ជូន" - "មិន​អាច​ទាញ​យក​សារ %1$s ពី %2$s បាន​ទេ។" - "មិន​អាច​​បញ្ចប់​ប្រតិបត្តិការ​​មូលដ្ឋាន​ទិន្នន័យ​ដោយសារ​អង្គ​ចងចាំ​មិន​គ្រប់គ្រាន់​​" - "មិន​បាន​ផ្ញើ​សារ" - "សារមួយចំនួនមិនបានផ្ញើនៅក្នុងការផ្ញើសារទេ" - - សារ %d នៅក្នុងការសន្ទនា %d - សារ %d នៅក្នុងការសន្ទនាមួយ - - "មិនបានទាញយកសារ" - "សារមួយចំនួនមិនបានទាញយកនៅក្នុងការផ្ញើសារទេ" - - សារ %d នៅក្នុងការសន្ទនា %d - សារ %d នៅក្នុងការសន្ទនាមួយ - - "សារទៅកាន់ %1$s មិនត្រូវបានផ្ញើទេ" - "សូមធ្វើការហៅជាសម្លេងទៅកាន់សេវាកម្មសង្គ្រោះបន្ទាន់។ សារជាអក្សររបស់អ្នកទៅ %1$s មិនអាចបញ្ជូនបានទេនៅពេលនេះ។" - - សារថ្មី %d - សារថ្មី - - "ចាប់ផ្ដើម" - "មិន​អាច​ប្រើ​ម៉ាស៊ីន​ថត​បាន" - "មិន​អាច​ប្រើ​ម៉ាស៊ីន​ថត​បាន" - "មិន​អាច​ថត​វីដេអូ​បាន" - "មិន​អាច​រក្សាទុក​​មេឌៀ" - "មិនអាចថតរូបភាព" - "ថយក្រោយ" - "ទុក​ក្នុង​ប័ណ្ណសារ" - "លុប" - "ប័ណ្ណសារ" - "មិន​ទុក​ក្នុង​ប័ណ្ណសារ" - "បិទ​ការ​ជូនដំណឹង" - "បើក​ការ​ជូនដំណឹង​" - "បន្ថែម​ទំនាក់ទំនង" - "ទាញ​យក" - "ផ្ញើ" - "លុប" - "លុប​សារ​នេះ?" - "សកម្មភាព​នេះ​មិន​អាច​ធ្វើ​វិញ​បាន​ទេ។" - "លុប" - - លុបការសន្ទនាទាំងនេះ? - លុបការសន្ទនានេះ? - - "លុប" - "បោះបង់" - "ជូន​ចំពោះ" - "ជ្រើស​រូបភាព​ច្រើន" - "បញ្ជាក់​​ការ​ជ្រើស" - "+%d" - "មិន​អាច​ថត​សំឡេង។ ព្យាយាម​ម្ដង​ទៀត។" - "មិន​អាច​ចាក់​សំឡេង។ ព្យាយាម​ម្ដងទៀត។" - "មិន​អាច​រក្សាទុក​សំឡេង។ ព្យាយាម​ម្ដងទៀត។" - "ប៉ះ & សង្កត់" - ", " - " " - ": " - " " - "រូបភាព" - "ឈុត​សំឡេង" - "វីដេអូ" - "កាត​ទំនាក់ទំនង" - "ទាញ​យក" - "ឆ្លើយតប​តាម​សារ SMS" - "ឆ្លើយតបតាម MMS" - "ឆ្លើយតប" - - អ្នកចូលរួម %d នាក់ - អ្នកចូលរួម %d នាក់ - - "ខ្ញុំ" - "ទំនាក់ទំនង​ត្រូវ​បាន​ទប់ស្កាត់ & ទុក​ក្នុង​ប័ណ្ណសារ" - "បានរារាំង និងបានដកទំនាក់ទំនងចេញពីបណ្ណ័សារ" - "បានទុកក្នុងបណ្ណសារចំនួន%d" - "មិនបានទុកក្នុងប័ណ្ណសារចំនួន %d" - "បានបិទការជូនដំណឹង" - "បានបើកការជូនដំណឹង" - "កំណត់​រួចរាល់។ ប៉ះ​ដើម្បី​ផ្ញើ​ម្ដងទៀត។" - "បានកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដោយជោគជ័យ។" - - បោះបង់ឯកសារភ្ជាប់ - បោះបង់ឯកសារភ្ជាប់ - - "ឯកសារភ្ជាប់សម្លេង" - "ឯកសារភ្ជាប់សម្លេង Play" - "ផ្អាក" - "សារពី %s%s។" - "សារបរាជ័យពី %s%s។ ម៉ោង៖ %s។" - "សារពី %s%s។ ម៉ោង៖ %s។" - "សារមិនបានផ្ញើទៅ %s%s។ ម៉ោង៖ %s។" - "កំពុងផ្ញើសារទៅ %s%s។ ម៉ោង៖ %s។" - "សារបរាជ័យទៅ %s%s។ ម៉ោង៖ %s។" - "សារទៅ %s%s។ ម៉ោង៖ %s។" - "សារបរាជ័យពី %s%s។ ម៉ោង៖ %s%s។" - "សារពី %s%s។ ម៉ោង៖ %s%s។" - "សារមិនបានផ្ញើទៅ %s%s។ ម៉ោង៖ %s។" - "កំពុងផ្ញើសារទៅ %s%s។ ម៉ោង៖ %s។" - "សារបរាជ័យទៅ %s%s។ ម៉ោង៖ %s។" - "សារទៅ %s%s។ ម៉ោង៖ %s។" - "សារបរាជ័យ។ ប៉ះដើម្បីព្យាយាមម្តងទៀត។" - "ការសន្ទនាជាមួយ %s" - "លុប​ប្រធានបទ" - "ថត​វីដេអូ" - "ថត​រូបភាព​ថេរ" - "ថតរូប" - "ចាប់ផ្ដើម​ថត​វីដេអូ" - "ប្ដូរ​ទៅ​ម៉ាស៊ីន​ថត​ពេញ​អេក្រង់" - "ប្ដូរ​រវាង​ម៉ាស៊ីន​ថត​មុន និង​ក្រោយ" - "បញ្ឈប់​ការ​ថត និង​ភ្ជាប់​វីដេអូ" - "បញ្ឈប់​ការ​ថត​វីដេអូ" - "រូបភាពសារ" - - បានរក្សាទុករូបថត %d ទៅក្នុងអាល់ប៊ុម \"%s\" - បានរក្សាទុករូបថត %d ទៅក្នុងអាល់ប៊ុម \"%s\" - - - បានរក្សាទុកវីដេអូ %d ទៅក្នុងអាល់ប៊ុម \"%s\" - បានរក្សាទុកវីដេអូ %d ទៅក្នុងអាល់ប៊ុម \"%s\" - - - បានរក្សាទុកឯកសារភ្ជាប់ %d ទៅក្នុងអាល់ប៊ុម \"%s\" - បានរក្សាទុកឯកសារភ្ជាប់ %d ទៅក្នុងអាល់ប៊ុម \"%s\" - - - បានរក្សាទុកឯកសារភ្ជាប់ %d ទៅក្នុង \"ទាញយក\" - បានរក្សាទុកឯកសារភ្ជាប់ %d ទៅក្នុង \"ទាញយក\" - - - បានរក្សាទុកឯកសារភ្ជាប់ %d - បានរក្សាទុកឯកសារភ្ជាប់ %d - - - មិនអាចរក្សាទុកឯកសារភ្ជាប់ %d - មិនអាចរក្សាទុកឯកសារភ្ជាប់ %d - - "បាន​រក្សាទុក​ឯកសារ​ភ្ជាប់​ជា​សារ MMS" - "ការកំណត់" - "ទុក​ក្នុង​ប័ណ្ណសារ" - "បិទ" - "សារ MMS" - "កម្រិត​ខ្ពស់" - "កែ​កំហុស" - "ការ​ជូនដំណឹង" - "សំឡេង" - "ស្ងាត់" - "ញ័រ" - "បាន​ទប់ស្កាត់" - "របាយការណ៍​បញ្ជូន​សារ SMS" - "ស្នើ​​​របាយ​ការណ៍​​បញ្ជូន​សម្រាប់​សារ SMS ​នីមួយៗ​ដែល​អ្នក​ផ្ញើ" - "​យក​ស្វ័យ​ប្រវត្តិ" - "ទាញ​យក​សារ MMS ដោយ​ស្វ័យ​ប្រវត្តិ​" - "ទាញ​យក​ដោយ​ស្វ័យប្រវត្តិ​ពេល​រ៉ូមីង" - "ទាញសារ MMS ត្រឡប់មកវិញដោយស្វ័យប្រវត្តិនៅពេលរ៉ូមីង" - "ការ​ផ្ញើ​សារ​​ជា​ក្រុម" - "ប្រើ MMS ដើម្បី​ផ្ញើ​សារ នៅ​ពេល​មាន​អ្នក​ទទួល​ច្រើន" - "កម្មវិធី​ផ្ញើ​សារ SMS ​លំនាំ​ដើម" - "កម្មវិធី​ផ្ញើ​សារ SMS ​លំនាំ​ដើម" - - - "លេខ​ទូរសព្ទ​របស់​អ្នក" - "មិន​ស្គាល់" - "សម្លេងសារផ្ញើចេញ" - "បោះបង់​សារ SMS" - "ដាក់​ទិន្នន័យ​ដើម​នៃ​សារ SMS បាន​ទទួល​ក្នុង​ឯកសារ​ឧបករណ៍​ផ្ទុក​ខាង​ក្រៅ" - "បោះបង់​សារ MMS" - "ដាក់​ទិន្នន័យ​ដើម​នៃ​សារ MMS បាន​ទទួល​ក្នុង​ឯកសារ​ឧបករណ៍​ផ្ទុក​ខាង​ក្រៅ" - "ការ​ជូនដំណឹង​​ឥត​​​ខ្សែ" - "ជម្រើស​សារ" - "ចម្លង​អត្ថបទ" - "មើល​​ព័ត៌មាន​លម្អិត" - "លុប" - "បញ្ជូន​បន្ត" - "ព័ត៌មាន​លម្អិត​សារ" - "ប្រភេទ៖ " - "សារ​​អត្ថបទ" - "សារ​ពហុ​​មេឌៀ" - "ពី៖ " - "ជូន​ចំពោះ៖ " - "បាន​ផ្ញើ៖ " - "បាន​ទទួល៖ " - "ប្រធានបទ៖ " - "ទំហំ៖ " - "អាទិភាព៖ " - "ស៊ីម៖ " - "ខ្ពស់" - "ធម្មតា" - "ទាប" - "ស៊ីម %s" - "​អាសយដ្ឋាន​​អ្នក​ផ្ញើ​ដែល​បាន​លាក់" - "មិន​អាច​ផ្ញើ​សារ​ខណៈ​ពេល​ផ្ទុក​ឯកសារ​ភ្ជាប់។" - "មិន​អាច​ផ្ទុក​ឯកសារ​ភ្ជាប់។ ព្យាយាម​ម្ដង​ទៀត។" - "បណ្តាញមិនទាន់រួចរាល់ទេ។ ព្យាយាមម្តងទៀត។" - "លុប​អត្ថបទ" - "ប្ដូរ​​រវាង​លេខ និង​អត្ថបទ​បញ្ចូល" - "បន្ថែម​អ្នក​ចូលរួម​ច្រើន​ទៀត" - "បញ្ជាក់​អ្នក​ចូលរួម" - "ចាប់ផ្ដើមការសន្ទនាថ្មី" - "ជ្រើស​ធាតុ​នេះ" - "ចាក់​វីដេអូ" - "មនុស្ស & ជម្រើស" - "កែ​កំហុស" - "មនុស្ស & ជម្រើស" - "ទូទៅ" - "មនុស្ស​ក្នុង​ការ​សន្ទនា​នេះ" - "ហៅ" - "ផ្ញើ​សារ" - "ផ្ញើ​សារ<br/><small>ពី %s</small>" - - ផ្ញើរូបថត - ផ្ញើរូបថត - - - ផ្ញើសំឡេង - ផ្ញើសំឡេង - - - ផ្ញើវីដេអូ - ផ្ញើវីដេអូ - - - ផ្ញើកាតទំនាក់ទំនង - ផ្ញើកាតទំនាក់ទំនង - - - ផ្ញើឯកសារភ្ជាប់ - ផ្ញើឯកសារភ្ជាប់ - - - ឯកសារភ្ជាប់ %d ត្រៀមសម្រាប់ផ្ញើ - ឯកសារភ្ជាប់មួយត្រៀមសម្រាប់ផ្ញើ - - "ផ្ដល់​មតិ​ស្ថាបនា" - "មើល​នៅ​ក្នុង​​ Google ឃ្លាំង​កម្សាន្ត" - "ព័ត៌មាន​កំណែ" - "កំណែ %1$s" - "អាជ្ញាប័ណ្ណ​កម្មវិធី​ប្រភព​កូដ​ចំហ" - "ការ​ជូនដំណឹង" - "ឯកសារភ្ជាប់ឈានដល់កំណត់" - "ការផ្ទុកឯកសារភ្ជាប់បានបរាជ័យ។" - "បន្ថែម​ទៅ​ទំនាក់ទំនង​?" - "បន្ថែម​ទំនាក់ទំនង" - "ប្រធានបទ" - "ប្រធានបទ៖ " - "%s%s" - "ផ្ទុក​កាត​ទំនាក់ទំនង" - "មិន​អាច​ផ្ទុក​កាត​ទំនាក់ទំនង" - "មើល​កាត​ទំនាក់ទំនង" - - ទំនាក់ទំនង %d - ទំនាក់ទំនង %d - - "កាត​ទំនាក់ទំនង" - "ថ្ងៃ​កំណើត" - "ចំណាំ" - "បញ្ជូន​សារ​បន្ត" - "ឆ្លើយតប" - "+1" - "+%d" - "បាន​បិទ​សារ SMS" - "សូមកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដើម្បីផ្ញើ" - "កំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើម" - "ផ្លាស់ប្ដូរ" - "សូមកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដើម្បីទទួលសារ" - "គ្មានស៊ីមកាតដែលបានប្រើត្រូវបានជ្រើសរើសឲ្យផ្ញើសារ SMS ទេ" - "កម្មវិធី​នេះ​មិន​បាន​អនុញ្ញាត​ដោយ​ម្ចាស់​ឧបករណ៍។" - "យល់ព្រម" - "មាន​អ្នក​ចូលរួម​ច្រើន​ពេក​ក្នុង​ការ​សន្ទនា" - - ទំនាក់ទំនងមិនត្រឹមត្រូវ - ទំនាក់ទំនងមិនត្រឹមត្រូវ - - "មិន​អាច​ផ្ទុក​រូបភាព​ម៉ាស៊ីន​ថត" - "អ្នក​៖ " - "%s: " - "សេចក្ដី​ព្រាង" - "នៅ​ពេល​ដែល​អ្នក​ចាប់ផ្ដើម​​ការ​សន្ទនា​ថ្មី ​អ្នក​នឹង​ឃើញ​វា​​​បង្ហាញ​នៅ​ទីនេះ" - "ការ​សន្ទនា​បាន​ទុក​ក្នុង​ប័ណ្ណសារ​បង្ហាញ​នៅ​ទីនេះ" - "កំពុង​ផ្ទុក​ការ​សន្ទនា…" - "រូបភាព" - "ឃ្លីបសម្លេង" - "វីដេអូ" - "កាត​ទំនាក់ទំនង" - "សារ MMS" - "មិនធ្វើវិញ" - "ព្យាយាមម្ដងទៀត" - "បញ្ចូល​ឈ្មោះ​ទំនាក់ទំនង​ ឬ​លេខ​ទូរសព្ទ​ដើម្បី​ចាប់ផ្ដើម​សារ​ថ្មី" - "ទប់ស្កាត់" - "ទប់ស្កាត់ %s" - "មិន​ទប់ស្កាត់ %s" - "ទប់ស្កាត់ %s?" - "អ្នក​នឹង​បន្ត​ទទួល​សារ​ពី​លេខ​នេះ ប៉ុន្តែ​នឹង​មិន​ត្រូវ​បាន​ជូន​ដំណឹង​ទៀត​ទេ។ ការ​សន្ទនា​នេះ​នឹង​ត្រូវ​បាន​ទុក​ក្នុង​ប័ណ្ណសារ។" - "បាន​ទប់ស្កាត់​ទំនាក់ទំនង" - "មិន​ទប់ស្កាត់" - "បាន​ទប់ស្កាត់​ទំនាក់ទំនង​" - "ជ្រើស​រូបភាព​ពី​បណ្ណាល័យ​ឯកសារ" - "កំពុងផ្ញើសារ" - "បានផ្ញើសារ" - "ទិន្នន័យ​ចល័ត​ត្រូវ​បាន​បិទ។ ពិនិត្យ​ការ​កំណត់​របស់​អ្នក។" - "មិន​អាច​ផ្ញើ​សារ​ក្នុង​របៀប​ពេល​ជិះ​យន្ត​ហោះ" - "មិនអាចផ្ញើសារបានទេ" - "បានទាញយកសារ" - "ទិន្នន័យចល័តត្រូវបានបិទ។ ពិនិត្យការកំណត់របស់អ្នក។" - "មិនអាចទាញយកសារនៅក្នុង របៀបយន្តហោះ ទេ។" - "មិនអាចទាញយកសារបានទេ" - "សូន្យ" - "មួយ" - "ពីរ" - "​បី" - "បួន" - "ប្រាំ" - "ប្រាំមួយ" - "ប្រាំពីរ" - "ប្រាំបី" - "ប្រាំបួន" - "មិនអាចផ្ញើសារដោយប្រើ %1$s នេះបានទេ។ កំហុស %2$d" - "មិនអាចផ្ញើសារដោយប្រើក្រុមហ៊ុនផ្តល់សេវាដែលមិនស្គាល់បានទេ។ កំហុស %1$d" - "បញ្ជូន​បន្ត៖ %s" - "សារ​មិន​បាន​ផ្ញើ៖ សេវាកម្ម​មិន​សកម្ម​លើ​បណ្ដាញ" - "មិន​បាន​ផ្ញើសារ៖ អាសយដ្ឋាន​គោលដៅ​មិន​ត្រឹមត្រូវ" - "មិន​បាន​ផ្ញើ​សារ៖ សារ​មិន​ត្រឹមត្រូវ" - "មិន​បាន​ផ្ញើ​សារ៖ មិន​គាំទ្រ​មាតិកា" - "មិន​បាន​ផ្ញើសារ៖ មិន​គាំទ្រ​សារ" - "មិនបានផ្ញើសារ៖ ធំពេក" - "សារ​ថ្មី" - "មើល" - "រូបភាព" - "មិន​អាច​រក​ឃើញ​កម្មវិធី​ដែល​សមរម្យ" - "លុប​អ្នក​ទទួល" - "សារ​ថ្មី" - "បោះបង់" - "កែ​ចំណុច​ចូល​ដំណើរការ" - "មិនបាន​កំណត់" - "ឈ្មោះ" - "APN" - "MMSC" - "ប្រូកស៊ី MMS" - "ច្រក MMS" - "MCC" - "MNC" - "ប្រភេទ APN" - "លុប APN" - "APN ថ្មី" - "រក្សាទុក" - "បោះបង់" - "វាល​ឈ្មោះ​មិន​អាច​ទទេ។" - "APN មិន​អាច​ទទេ។" - "វាល MCC ត្រូវ​តែ​មាន​ ៣ តួ។" - "វាល MNC ត្រូវ​តែ​មាន​ពីរ ឬ​បី​តួ។" - "ស្ដារ​ការ​កំណត់ APN លំនាំដើម​ឡើងវិញ។" - "កំណត់​ទៅ​លំនាំដើម​ឡើងវិញ" - "បាន​បញ្ចប់​ការ​កំណត់​នៃ​ការកំណត់​ APN លំនាំដើម។" - "គ្មាន​ចំណង​ជើង" - "ឈ្មោះ​ចំណុច​ចូល​ដំណើរការ" - "APNs" - "APN ថ្មី" - "ការ​កំណត់​ឈ្មោះ​ចូល​ដំណើរការ​មិន​អាច​ប្រើ​បាន​សម្រាប់​អ្នក​ប្រើ​​នេះ" - "ចម្លង​ទៅ​ក្ដារ​តម្បៀត​ខ្ទាស់​?" - "ចម្លង" - "ទៅ %s" - "ទូទៅ" - "កម្រិត​ខ្ពស់" - "ការ​កំណត់​ទូទៅ" - "ការ​កំណត់​កម្រិត​ខ្ពស់" - "\"%s\" ស៊ីម" - "ផ្ញើ​សារ SMS នីមួយៗ​ទៅ​អ្នក​ទទួល​ទាំង​អស់។ មាន​តែ​អ្នក​ប៉ុណ្ណោះ​​ទទួល​បាន​ការ​ឆ្លើយតប" - "ផ្ញើ​សារ MMS មួយ​ទៅ​អ្នក​ទទួល" - "មិន​ស្គាល់​លេខ" - "សារថ្មី" - "សារថ្មី។" - "កម្មវិធីជ្រើសស៊ីម" - "បានជ្រើសរើស %1$s កម្មវិធីជ្រើសស៊ីម" - "កែប្រធានបទ" - "ជ្រើសស៊ីម ឬកែប្រធានបទ" - "ប៉ះ និងសង្កត់ដើម្បីថតសម្លេង" - "ចាប់ផ្ដើមការសន្ទនាថ្មី" - "ការផ្ញើសារ" - "បញ្ជីសារ" - "ការផ្ញើសារ" - "សារថ្មី" - "បញ្ជីសន្ទនា" - "កំពុងផ្ទុកការសន្ទនា…" - "សារកំពុងដំណើរការ" - "មើលការសន្ទនាច្រើនទៀត" - "មើលសារច្រើនទៀត" - "បានលុបការសន្ទនា" - "បានលុបការសន្ទនា។ សូមប៉ះដើម្បីបង្ហាញការសន្ទនាផ្សេងទៀតនៅក្នុងការផ្ញើសារ" - "បាន​ទប់ស្កាត់" - "បានឈប់ទប់ស្កាត់" - "ចន្លោះផ្ទុកមានកម្រិតទាប។ ទិន្នន័យមួយចំនួនអាចបាត់បង់។" - "ជ្រើសឯកសារភ្ជាប់" - "បញ្ជាក់​​ការ​ជ្រើស" - "បានជ្រើស %d" - "សូមយកឯកសារភ្ជាប់មួយ ឬច្រើនចេញ ហើយព្យាយាមម្តងទៀត។" - "អ្នកអាចព្យាយាមផ្ញើសារបស់អ្នក ប៉ុន្តែវាប្រហែលជាមិនផ្ញើទៅទេ លុះត្រាតែអ្នកយកឯកសារភ្ជាប់មួយ ឬច្រើនចេញ។" - "អ្នកអាចផ្ញើវីដេអូតែមួយប៉ុណ្ណោះនៅក្នុងសារមួយ។ សូមយកវីដេអូដែលលើសពីនេះចេញ ហើយព្យាយាមម្តងទៀត។" - "ការផ្ញើសារបានបរាជ័យក្នុងដំណើរការផ្ទុកឯកសារភ្ជាប់។" - "អញ្ចឹង​ផ្ញើ​​តែម្ដង" - "មិនអាចចាប់ផ្តើមការសន្ទនាទេ" - "%1$s (%2$s)" - "បានជ្រើស %s" - diff --git a/res/values-kn-rIN/arrays.xml b/res/values-kn-rIN/arrays.xml deleted file mode 100644 index 932fec1..0000000 --- a/res/values-kn-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ಯಾವುದೇ ವಿಷಯವಿಲ್ಲ" - "ಯಾವುದೇ ವಿಷಯವಿಲ್ಲ" - - - "ಹೌದು" - "ಬೇಡ" - "ಸರಿ" - "ಹೆಹೆ" - "ಧನ್ಯವಾದಗಳು" - "ಒಪ್ಪುತ್ತೇನೆ" - "ಉತ್ತಮ" - "ನನ್ನ ಹಾದಿಯಲ್ಲಿದ್ದೇನೆ" - "ಸರಿ, ನಾನು ಮತ್ತೆ ನಿಮಗೆ ತಿಳಿಸುತ್ತೇನೆ" - ":)" - ":(" - - diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml deleted file mode 100644 index eb9539b..0000000 --- a/res/values-kn-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - "ಸಂಭಾಷಣೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಸಂದೇಶ ಕಳುಹಿಸು" - "ಲಗತ್ತನ್ನು ಸೇರಿಸಿ" - "ಸಹಾಯ" - "ಸುಸ್ವಾಗತ" - "ಸ್ಕಿಪ್‌" - "ಮುಂದೆ >" - "ಮುಂದೆ" - "ನಿರ್ಗಮಿಸು" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು >" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಗೆ SMS, ಫೋನ್ ಮತ್ತು ಸಂಪರ್ಕಗಳಿಗೆ ಅನುಮತಿ ಹೊಂದಿರುವ ಅಗತ್ಯವಿದೆ." - "ನೀವು ಅನುಮತಿಗಳನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು > ಅಪ್ಲಿಕೇಶನ್‌ಗಳು > ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ > ಅನುಮತಿಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು." - "ನೀವು ಅನುಮತಿಗಳನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು, ಅಪ್ಲಿಕೇಶನ್‌ಗಳು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ, ಅನುಮತಿಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು." - "ಪುನರಾವರ್ತನೆಗಳು" - "ಎಲ್ಲ ಸಂಪರ್ಕಗಳು" - "%s ಗೆ ಕಳುಹಿಸಿ" - "ಚಿತ್ರಗಳು ಅಥವಾ ವೀಡಿಯೊವನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ" - "ಈ ಸಾಧನದಿಂದ ಚಿತ್ರಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ" - "ಫೋಟೋ ಆಯ್ಕೆಮಾಡಿ" - "ಮಾಧ್ಯಮ ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ." - "ಮಾಧ್ಯಮ ಆಯ್ಕೆ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ." - "%d ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ" - "ಚಿತ್ರ %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ಚಿತ್ರ" - "ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ" - "ಹಂಚಿಕೊಳ್ಳಿ" - "ಈಗ ತಾನೇ" - "ಇದೀಗ" - - %d ನಿಮಿಷಗಳು - %d ನಿಮಿಷಗಳು - - - %d ಗಂಟೆಗಳು - %d ಗಂಟೆಗಳು - - - %d ದಿನಗಳು - %d ದಿನಗಳು - - - %d ವಾರಗಳು - %d ವಾರಗಳು - - - %d ತಿಂಗಳು - %d ತಿಂಗಳು - - - %d ವರ್ಷಗಳು - %d ವರ್ಷಗಳು - - "ವರ್ಗ 0 ಸಂದೇಶ" - "ಉಳಿಸು" - "ಸಾಧನದಲ್ಲಿ ಸ್ಥಳಾವಕಾಶವು ಕಡಿಮೆ ಇದೆ. ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಸ್ಥಳಾವಕಾಶವನ್ನು ತೆರವುಗೊಳಿಸಲು ಹಳೆಯ ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸುತ್ತದೆ." - "ಸಂಗ್ರಹಣೆ ಸ್ಥಳವು ತುಂಬಿದೆ" - "ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶ ಲಭ್ಯವಿರುವವರೆಗೆ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸದಿರಬಹುದು ಅಥವಾ ಸ್ವೀಕರಿಸದಿರಬಹುದು." - "ಕಡಿಮೆ SMS ಸಂಗ್ರಹಣೆ. ನೀವು ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಬೇಕಾಗಬಹುದು." - "ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಖಚಿತಪಡಿಸಿ" - "ಈ ಒಂದು-ಬಾರಿ ಹಂತವು ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ನಿಮ್ಮ ಗುಂಪು ಸಂದೇಶಗಳನ್ನು ಸರಿಯಾಗಿ ಕಳುಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸುತ್ತದೆ." - "ಫೋನ್ ಸಂಖ್ಯೆ" - "ಮೀಡಿಯಾದ ಎಲ್ಲಾ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ" - "%s ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ" - "%s ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂ-ಅಳಿಸಿ" - "ನಿರ್ಲಕ್ಷಿಸು" - "ಮಾಧ್ಯಮದ ಎಲ್ಲಾ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸುವುದೇ?" - "%s ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸುವುದೇ?" - "%s ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ ಹಾಗೂ ಸ್ವಯಂ-ಅಳಿಸು ಆನ್‌ ಮಾಡಿ?" - "%s ಹೇಳಿದ್ದಾರೆ" - "ನೀವು ಹೇಳಿದ್ದೀರಿ" - "%s ಅವರಿಂದ ಸಂದೇಶ" - "ನೀವು ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಿರುವಿರಿ" - "ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ..." - "ಕಳುಹಿಸಲಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ." - "ಕಳುಹಿಸಲಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ…" - "ಮರುಕಳುಹಿಸಿ ಅಥವಾ ಅಳಿಸಿ" - "ತುರ್ತು ಸೇವೆಗಳಿಗೆ ದಯವಿಟ್ಟು ಧ್ವನಿ ಕರೆಗಳನ್ನು ಮಾಡಿ. ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ ಪಠ್ಯ ಸಂದೇಶವನ್ನು ತಲುಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." - "ವಿಫಲವಾಗಿದೆ" - "ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಹೊಸ MMS ಸಂದೇಶ" - "ಹೊಸ MMS ಸಂದೇಶ" - "ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಿಲ್ಲ" - "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ" - "ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ" - "ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಿ ಇಲ್ಲವೇ ಅಳಿಸಿ" - "ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ…" - "ಸಂದೇಶ ಅವಧಿ ಮೀರಿದೆ ಅಥವಾ ಲಭ್ಯವಿಲ್ಲ" - "ಗಾತ್ರ: %1$s, ಮುಕ್ತಾಯ: %2$s" - "ಕಳುಹಿಸಲಾಗುವುದಿಲ್ಲ. ಸ್ವೀಕರಿಸುವವರು ಮಾನ್ಯವಾಗಿಲ್ಲ." - "ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಸೇವೆಯು ಸಕ್ರಿಯಗೊಂಡಿಲ್ಲ" - "ನೆಟ್‌ವರ್ಕ್‌ ಸಮಸ್ಯೆಯಿಂದಾಗಿ ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ" - "ಸಂದೇಶ ಅವಧಿ ಮೀರಿದೆ ಅಥವಾ ಲಭ್ಯವಿಲ್ಲ" - "(ವಿಷಯ ಇಲ್ಲ)" - "ಕಳುಹಿಸಿದವರು ತಿಳಿದಿಲ್ಲ" - "ತಲುಪಿಸಲಾಗಿದೆ" - "%2$s ರಿಂದ %1$s ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ." - "ಕಡಿಮೆ ಮೆಮೊರಿಯ ಕಾರಣ ಡೇಟಾಬೇಸ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ" - "ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿಲ್ಲ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯಲ್ಲಿ ಕೆಲವು ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲಾಗಿಲ್ಲ" - - %d ಸಂವಾದಗಳಲ್ಲಿ %d ಸಂದೇಶಗಳು - %d ಸಂವಾದಗಳಲ್ಲಿ %d ಸಂದೇಶಗಳು - - "ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗಿಲ್ಲ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯಲ್ಲಿ ಕೆಲವು ಸಂದೇಶಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿಲ್ಲ" - - %d ಸಂವಾದಗಳಲ್ಲಿ %d ಸಂದೇಶಗಳು - %d ಸಂವಾದಗಳಲ್ಲಿ %d ಸಂದೇಶಗಳು - - "%1$s ಸಂಖ್ಯೆಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಿಲ್ಲ" - "ತುರ್ತು ಸೇವೆಗಳಿಗೆ ದಯವಿಟ್ಟು ಧ್ವನಿ ಕರೆಗಳನ್ನು ಮಾಡಿ. ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ ಪಠ್ಯ ಸಂದೇಶವನ್ನು %1$s ಸಂಖ್ಯೆಗೆ ತಲುಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." - - %d ಹೊಸ ಸಂದೇಶಗಳು - %d ಹೊಸ ಸಂದೇಶಗಳು - - "ಪ್ರಾರಂಭಿಸು" - "ಕ್ಯಾಮರಾ ಲಭ್ಯವಿಲ್ಲ" - "ಕ್ಯಾಮರಾ ಲಭ್ಯವಿಲ್ಲ" - "ವಿಡಿಯೋ ಸೆರೆಹಿಡಿಯುವಿಕೆ ಲಭ್ಯವಿಲ್ಲ" - "ಮಾಧ್ಯಮ ಉಳಿಸಲಾಗಲಿಲ್ಲ" - "ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಹಿಂದೆ" - "ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ" - "ಅಳಿಸಿ" - "ಆರ್ಕೈವ್ ಮಾಡಿ" - "ಅನ್‌ಆರ್ಕೈವ್ ಮಾಡು" - "ಅಧಿಸೂಚನೆಗಳನ್ನು ಆಫ್ ಮಾಡಿ" - "ಅಧಿಸೂಚನೆಗಳನ್ನು ಆನ್ ಮಾಡು" - "ಸಂಪರ್ಕ ಸೇರಿಸು" - "ಡೌನ್‌ಲೋಡ್‌" - "ಕಳುಹಿಸು" - "ಅಳಿಸಿ" - "ಈ ಸಂದೇಶವನ್ನು ಅಳಿಸುವುದೇ?" - "ಈ ಕ್ರಿಯೆಯನ್ನು ರದ್ದುಪಡಿಸಲಾಗುವುದಿಲ್ಲ" - "ಅಳಿಸಿ" - - ಈ ಸಂವಾದಗಳನ್ನು ಅಳಿಸುವುದೇ? - ಈ ಸಂವಾದಗಳನ್ನು ಅಳಿಸುವುದೇ? - - "ಅಳಿಸಿ" - "ರದ್ದುಮಾಡು" - "ಇವರಿಗೆ" - "ಬಹು ಚಿತ್ರಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಆಯ್ಕೆ ಖಚಿತಪಡಿಸಿ" - "+%d" - "ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಆಡಿಯೋವನ್ನು ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಆಡಿಯೋವನ್ನು ಉಳಿಸಲಾಗಲಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಸ್ಪರ್ಶಿಸಿ & ಹೋಲ್ಡ್‌ ಮಾಡಿ" - ", " - " " - ": " - " " - "ಚಿತ್ರ" - "ಆಡಿಯೊ ಕ್ಲಿಪ್" - "ವೀಡಿಯೊ" - "ಸಂಪರ್ಕದ ಕಾರ್ಡ್" - "ಡೌನ್‌ಲೋಡ್‌" - "SMS ಮೂಲಕ ಪ್ರತ್ಯುತ್ತರಿಸಿ" - "MMS ಮೂಲಕ ಪ್ರತ್ಯುತ್ತರಿಸಿ" - "ಪ್ರತ್ಯುತ್ತರಿಸು" - - %d ಪಾಲ್ಗೊಳ್ಳುವವರು - %d ಪಾಲ್ಗೊಳ್ಳುವವರು - - "ನಾನು" - "ಸಂಪರ್ಕವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ & ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗಿದೆ" - "ಸಂಪರ್ಕವನ್ನು ಅನಿರ್ಬಂಧಿಸಲಾಗಿದೆ & ಆರ್ಕೈವ್ ರದ್ದುಮಾಡಲಾಗಿದೆ" - "%d ಆರ್ಕೈವ್‌‌ ಮಾಡಲಾಗಿದೆ" - "%d ಅನ್‌ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗಿದೆ" - "ಅಧಿಸೂಚನೆಗಳನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ" - "ಅಧಿಸೂಚನೆಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ" - "ಎಲ್ಲವನ್ನೂ ಹೊಂದಿಸಲಾಗಿದೆ. ಮತ್ತೆ ಕಳುಹಿಸಲು ಸ್ಪರ್ಶಿಸಿ." - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಲಾಗಿದೆ." - - ಲಗತ್ತುಗಳನ್ನು ತಿರಸ್ಕರಿಸು - ಲಗತ್ತುಗಳನ್ನು ತಿರಸ್ಕರಿಸು - - "ಆಡಿಯೋ ಲಗತ್ತು" - "ಆಡಿಯೊ ಲಗತ್ತನ್ನು ಪ್ಲೇ ಮಾಡಿ" - "ವಿರಾಮಗೊಳಿಸು" - "%s ಅವರಿಂದ ಸಂದೇಶ: %s." - "%s ಅವರಿಂದ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ: %s. ಸಮಯ: %s." - "%s ಅವರಿಂದ ಸಂದೇಶ: %s. ಸಮಯ: %s." - "%s ಗೆ ಕಳುಹಿಸದಿರುವ ಸಂದೇಶ: %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ: %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ : %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶ: %s. ಸಮಯ: %s." - "%s ಅವರಿಂದ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ: %s. ಸಮಯ: %s. %s." - "%s ಅವರಿಂದ ಸಂದೇಶ: %s. ಸಮಯ: %s. %s." - "%s ಗೆ ಕಳುಹಿಸದಿರುವ ಸಂದೇಶ: %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ: %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ : %s. ಸಮಯ: %s." - "%s ಗೆ ಸಂದೇಶ: %s. ಸಮಯ: %s." - "ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ. ಮರುಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ." - "%s ಅವರೊಂದಿಗೆ ಸಂಭಾಷಣೆ" - "ವಿಷಯವನ್ನು ಅಳಿಸಿ" - "ವೀಡಿಯೊ ಸೆರೆಹಿಡಿಯಿರಿ" - "ಸ್ಥಿರ ಚಿತ್ರವನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ" - "ಫೋಟೋ ತೆಗೆಯಿರಿ" - "ವೀಡಿಯೊ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ" - "ಪೂರ್ಣ ಪರದೆ ಕ್ಯಾಮರಾಗೆ ಬದಲಿಸಿ" - "ಮುಂದಿನ ಮತ್ತು ಹಿಂದಿನ ಕ್ಯಾಮರಾ ನಡುವೆ ಬದಲಾಯಿಸಿ" - "ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ ಮತ್ತು ವೀಡಿಯೊ ಲಗತ್ತಿಸಿ" - "ವೀಡಿಯೊ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ" - "ಫೋಟೋಗಳ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ಫೋಟೋಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ಫೋಟೋಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - - - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ವೀಡಿಯೊಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ವೀಡಿಯೊಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - - - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - \"%s\" ಆಲ್ಬಮ್‌ಗೆ %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - - - \"ಡೌನ್‌ಲೋಡ್‌ಗಳಿಗೆ\" %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - \"ಡೌನ್‌ಲೋಡ್‌ಗಳಿಗೆ\" %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - - - %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ - - - %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ - %d ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ - - "MMS ಲಗತ್ತನ್ನು ಉಳಿಸಲಾಗಿದೆ" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ" - "ಮುಚ್ಚು" - "MMS" - "ಸುಧಾರಿತ" - "ಡೀಬಗ್ ಮಾಡು" - "ಅಧಿಸೂಚನೆಗಳು" - "ಶಬ್ದ" - "ಶಾಂತ" - "ವೈಬ್ರೇಟ್‌‌" - "ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "SMS ವಿತರಣಾ ವರದಿಗಳು" - "ನೀವು ಕಳುಹಿಸುವ ಪ್ರತಿ SMS ಗೂ ವಿತರಣೆ ವರದಿಯನ್ನು ವಿನಂತಿಸಿ" - "ಸ್ವಯಂ-ಹಿಂಪಡೆಯುವಿಕೆ" - "MMS ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಹಿಂಪಡೆದುಕೊಳ್ಳಿ" - "ರೋಮಿಂಗ್‌‌ನಲ್ಲಿರುವಾಗ ಸ್ವಯಂ-ಹಿಂಪಡೆಯುವಿಕೆ" - "ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಸ್ವಯಂಚಾಲಿತವಾಗಿ MMS ಹಿಂಪಡೆಯಿರಿ" - "ಗುಂಪು ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - "ಅನೇಕ ಸ್ವೀಕೃತದಾರರು ಇರುವಾಗ ಒಂದೇ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು MMS ಬಳಸಿ" - "ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್" - "ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್" - - - "ನಿಮ್ಮ ಫೋನ್‌ ಸಂಖ್ಯೆ" - "ಅಪರಿಚಿತ" - "ಹೊರಹೋಗುವ ಸಂದೇಶಕ್ಕೆ ಧ್ವನಿಗಳು" - "ಡಂಪ್ SMS" - "ಬಾಹ್ಯ ಸಂಗ್ರಹಣೆ ಫೈಲ್‌ಗೆ SMS ಕಚ್ಛಾ ಡೇಟಾವನ್ನು ಡಂಪ್ ಸ್ವೀಕರಿಸಿದೆ" - "ಡಂಪ್ MMS" - "ಬಾಹ್ಯ ಸಂಗ್ರಹಣೆ ಫೈಲ್‌ಗೆ MMS ಕಚ್ಛಾ ಡೇಟಾವನ್ನು ಡಂಪ್ ಸ್ವೀಕರಿಸಿದೆ" - "ವೈರ್‌ಲೆಸ್ ಎಚ್ಚರಿಕೆಗಳು" - "ಸಂದೇಶ ಆಯ್ಕೆಗಳು" - "ಪಠ್ಯ ನಕಲಿಸು" - "ವಿವರಗಳನ್ನು ವೀಕ್ಷಿಸಿ" - "ಅಳಿಸಿ" - "ಫಾರ್ವರ್ಡ್ ಮಾಡು" - "ಸಂದೇಶ ವಿವರಗಳು" - "ಪ್ರಕಾರ: " - "ಪಠ್ಯ ಸಂದೇಶ" - "ಮಲ್ಟಿಮೀಡಿಯಾ ಸಂದೇಶ" - "ಇಂದ: " - "ಗೆ: " - "ಕಳುಹಿಸಿದ್ದು: " - "ಸ್ವೀಕರಿಸಿದ್ದು: " - "ವಿಷಯ: " - "ಗಾತ್ರ: " - "ಆದ್ಯತೆ: " - "SIM: " - "ಅಧಿಕ" - "ಸಾಮಾನ್ಯ" - "ಕಡಿಮೆ" - "SIM %s" - "ಮರೆಮಾಡಲಾಗಿರುವ ಕಳುಹಿಸುವವರ ವಿಳಾಸ" - "ಲಗತ್ತುಗಳನ್ನು ಲೋಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." - "ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ನೆಟ್‌ವರ್ಕ್ ಸಿದ್ಧವಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಪಠ್ಯ ಅಳಿಸಿ" - "ಪಠ್ಯ ಮತ್ತು ಸಂಖ್ಯೆಗಳ ನಡುವೆ ನಮೂದಿಸುವುದನ್ನು ಬದಲಾಯಿಸಿ" - "ಇನ್ನಷ್ಟು ಪಾಲ್ಗೊಳ್ಳುವವರನ್ನು ಸೇರಿಸಿ" - "ಭಾಗವಹಿಸುವವರನ್ನು ಖಚಿತಪಡಿಸಿ" - "ಹೊಸ ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಿ" - "ಈ ಐಟಂ ಆಯ್ಕೆಮಾಡಿ" - "ವೀಡಿಯೊ ಪ್ಲೇ ಮಾಡಿ" - "ಜನರು & ಆಯ್ಕೆಗಳು" - "ಡೀಬಗ್ ಮಾಡು" - "ಜನರು & ಆಯ್ಕೆಗಳು" - "ಸಾಮಾನ್ಯ" - "ಈ ಸಂವಾದದಲ್ಲಿರುವ ಜನರು" - "ಕರೆ ಮಾಡಿ" - "ಸಂದೇಶ ಕಳುಹಿಸಿ" - "<br/><small>%s</small> ನಿಂದ ಸಂದೇಶ ಕಳುಹಿಸಿ" - - ಫೋಟೋಗಳನ್ನು ಕಳುಹಿಸಿ - ಫೋಟೋಗಳನ್ನು ಕಳುಹಿಸಿ - - - ಆಡಿಯೋಗಳನ್ನು ಕಳುಹಿಸಿ - ಆಡಿಯೋಗಳನ್ನು ಕಳುಹಿಸಿ - - - ವೀಡಿಯೊಗಳನ್ನು ಕಳುಹಿಸಿ - ವೀಡಿಯೊಗಳನ್ನು ಕಳುಹಿಸಿ - - - ಸಂಪರ್ಕ ಕಾರ್ಡ್‍ಗಳನ್ನು ಕಳುಹಿಸಿ - ಸಂಪರ್ಕ ಕಾರ್ಡ್‍ಗಳನ್ನು ಕಳುಹಿಸಿ - - - ಲಗತ್ತುಗಳನ್ನು ಕಳುಹಿಸಿ - ಲಗತ್ತುಗಳನ್ನು ಕಳುಹಿಸಿ - - - %d ಕಳುಹಿಸಲು ಲಗತ್ತುಗಳು ಸಿದ್ಧವಾಗಿವೆ - %d ಕಳುಹಿಸಲು ಲಗತ್ತುಗಳು ಸಿದ್ಧವಾಗಿವೆ - - "ಪ್ರತಿಕ್ರಿಯೆ ಕಳುಹಿಸಿ" - "Google Play Store ನಲ್ಲಿ ವೀಕ್ಷಿಸಿ" - "ಆವೃತ್ತಿ ಮಾಹಿತಿ" - "ಆವೃತ್ತಿ %1$s" - "ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು" - "ಅಧಿಸೂಚನೆಗಳು" - "ಲಗತ್ತು ಮಿತಿಯನ್ನು ತಲುಪಿದೆ" - "ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ." - "ಸಂಪರ್ಕಗಳಿಗೆ ಸೇರಿಸುವುದೇ?" - "ಸಂಪರ್ಕ ಸೇರಿಸು" - "ವಿಷಯ" - "ವಿಷಯ: " - "%s%s" - "ಸಂಪರ್ಕ ಕಾರ್ಡ್‌ ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ" - "ಸಂಪರ್ಕ ಕಾರ್ಡ್ ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಸಂಪರ್ಕ ಕಾರ್ಡ್ ವೀಕ್ಷಿಸಿ" - - %d ಸಂಪರ್ಕಗಳು - %d ಸಂಪರ್ಕಗಳು - - "ಸಂಪರ್ಕ ಕಾರ್ಡ್‌ಗಳು" - "ಜನ್ಮದಿನ" - "ಟಿಪ್ಪಣಿಗಳು" - "ಸಂದೇಶವನ್ನು ಫಾರ್ವರ್ಡ್ ಮಾಡು" - "ಪ್ರತ್ಯುತ್ತರ ನೀಡು" - "+1" - "+%d" - "SMS ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" - "ಕಳುಹಿಸಲು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ" - "ಬದಲಾಯಿಸು" - "ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ" - "SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಯಾವುದೇ ಪ್ರಾಶಸ್ತ್ಯದ ಸಿಮ್ ಆಯ್ಕೆಮಾಡಲಾಗಿಲ್ಲ" - "ಸಾಧನದ ಮಾಲೀಕರಿಂದ ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸಲಾಗಿಲ್ಲ" - "ಸರಿ" - "ಸಂವಾದದಲ್ಲಿ ಹಲವಾರು ಸಹಭಾಗಿಗಳಿದ್ದಾರೆ" - - ಅಮಾನ್ಯ ಸಂಪರ್ಕಗಳು - ಅಮಾನ್ಯ ಸಂಪರ್ಕಗಳು - - "ಕ್ಯಾಮರಾ ಚಿತ್ರವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗಲಿಲ್ಲ" - "ನೀವು: " - "%s: " - "ಡ್ರಾಫ್ಟ್‌" - "ನೀವು ಒಮ್ಮೆ ಹೊಸ ಸಂವಾದ ಪ್ರಾರಂಭಿಸಿದರೆ, ಅದು ಇಲ್ಲಿ ಪಟ್ಟಿ ಮಾಡಲಾಗಿರುವುದನ್ನು ನೋಡುತ್ತೀರಿ" - "ಆರ್ಕೈವ್‌ ಮಾಡಲಾದ ಸಂವಾದಗಳು ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ" - "ಸಂವಾದಗಳನ್ನು ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ..." - "ಚಿತ್ರ" - "ಆಡಿಯೊ ಕ್ಲಿಪ್" - "ವೀಡಿಯೊ" - "ಸಂಪರ್ಕದ ಕಾರ್ಡ್" - "MMS" - "ರದ್ದುಮಾಡು" - "ಮರುಪ್ರಯತ್ನಿಸು" - "ಹೊಸ ಸಂದೇಶವನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಂಪರ್ಕ ಹೆಸರು ಅಥವಾ ಫೊನ್ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ" - "ನಿರ್ಬಂಧಿಸು" - "%s ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "%s ಅನಿರ್ಬಂಧಿಸು" - "%s ನಿರ್ಬಂಧಿಸುವುದೇ?" - "ಈ ಸಂಖ್ಯೆಯಿಂದ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಮುಂದುವರೆಸುತ್ತೀರಿ ಆದರೆ ಇನ್ನು ಮುಂದೆ ಸೂಚನೆ ನೀಡುವುದಿಲ್ಲ. ಈ ಸಂವಾದವನ್ನು ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗುವುದು." - "ನಿರ್ಬಂಧಿಸಲಾದ ಸಂಪರ್ಕಗಳು" - "ಅನಿರ್ಬಂಧಿಸು" - "ನಿರ್ಬಂಧಿಸಲಾದ ಸಂಪರ್ಕಗಳು" - "ಡಾಕ್ಯುಮೆಂಟ್ ಲೈಬ್ರರಿಯಿಂದ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಸಂದೇಶ ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ" - "ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿದೆ" - "ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾ ಆಫ್ ಮಾಡಲಾಗಿದೆ. ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರೀಕ್ಷಿಸಿ." - "ಸಂದೇಶಗಳನ್ನು ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್‌ನಲ್ಲಿ ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಸಂದೇಶ ಕಳುಸಲು ಆಗುತ್ತಿಲ್ಲ" - "ಸಂದೇಶ ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ" - "ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾ ಆಫ್ ಮಾಡಲಾಗಿದೆ. ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರೀಕ್ಷಿಸಿ." - "ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್‌ನಲ್ಲಿ ಸಂದೇಶಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" - "ಶೂನ್ಯ" - "ಒಂದು" - "ಎರಡು" - "ಮೂರು" - "ನಾಲ್ಕು" - "ಐದು" - "ಆರು" - "ಏಳು" - "ಎಂಟು" - "ಒಂದತ್ತು" - "%1$s ಮೂಲಕ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ, ದೋಷ %2$d" - "ಅಜ್ಞಾತ ವಾಹಕದ ಮೂಲಕ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ದೋಷ %1$d" - "ಫಾರ್ವರ್ಡ್‌: %s" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಸೇವೆಯು ಸಕ್ರಿಯಗೊಂಡಿಲ್ಲ" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಅಮಾನ್ಯವಾದ ಗಮ್ಯಸ್ಥಾನ ವಿಳಾಸ" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಅಮಾನ್ಯವಾದ ಸಂದೇಶ" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಬೆಂಬಲವಿಲ್ಲದ ವಿಷಯ" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಬೆಂಬಲವಿಲ್ಲದ ಸಂದೇಶ" - "ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಿಲ್ಲ: ತುಂಬಾ ದೊಡ್ಡದಾಗಿದೆ" - "ಹೊಸ ಸಂದೇಶ" - "ವೀಕ್ಷಿಸಿ" - "ಚಿತ್ರ" - "ಸೂಕ್ತವಾದ ಅಪ್ಲಿಕೇಶನ್ ಕಂಡುಬಂದಿಲ್ಲ" - "ಸ್ವೀಕೃತಿದಾರರನ್ನು ತೆಗೆದುಹಾಕಿ" - "ಹೊಸ ಸಂದೇಶ" - "ರದ್ದುಮಾಡು" - "ಪ್ರವೇಶ ಬಿಂದು ಸಂಪಾದನೆ" - "ಹೊಂದಿಸಿಲ್ಲ" - "ಹೆಸರು" - "APN" - "MMSC" - "MMS ಪ್ರಾಕ್ಸಿ" - "MMS ಪೋರ್ಟ್" - "MCC" - "MNC" - "APN ಪ್ರಕಾರ" - "APN ಅಳಿಸು" - "ಹೊಸ APN" - "ಉಳಿಸು" - "ತಿರಸ್ಕರಿಸು" - "ಹೆಸರಿನ ಕ್ಷೇತ್ರವು ಖಾಲಿ ಇರುವಂತಿಲ್ಲ." - "APN ಖಾಲಿ ಇರುವಂತಿಲ್ಲ." - "MCC ಕ್ಷೇತ್ರವು 3 ಅಂಕಿಗಳಾಗಿರಬೇಕು." - "MNC ಕ್ಷೇತ್ರವು 2 ಅಥವಾ 3 ಅಂಕಿಗಳಾಗಿರಬೇಕು." - "ಡೀಫಾಲ್ಟ್ APN ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮರುಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ." - "ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸು" - "ಡೀಫಾಲ್ಟ್ APN ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮರುಹೊಂದಿಕೆಯು ಪೂರ್ಣಗೊಂಡಿದೆ." - "ಶೀರ್ಷಿಕೆರಹಿತ" - "ಪ್ರವೇಶಿಸುವಿಕೆ ಕೇಂದ್ರದ ಹೆಸರುಗಳು" - "APN ಗಳು" - "ಹೊಸ APN" - "ಪ್ರವೇಶ ಬಿಂದು ಹೆಸರಿನ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಈ ಬಳಕೆದಾರರಿಗೆ ಲಭ್ಯವಿಲ್ಲ" - "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸುವುದೇ?" - "ನಕಲಿಸು" - "%s ಗೆ" - "ಸಾಮಾನ್ಯ" - "ಸುಧಾರಿತ" - "ಸಾಮಾನ್ಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಸುಧಾರಿತ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "\"%s\" ಸಿಮ್‌" - "ಎಲ್ಲಾ ಸ್ವೀಕೃತದಾರರಿಗೆ ಪ್ರತ್ಯೇಕ SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಿ. ಯಾವುದೇ ಪ್ರತ್ಯುತ್ತರಗಳು ನಿಮಗೆ ಮಾತ್ರ ಬರುತ್ತವೆ" - "ಎಲ್ಲಾ ಸ್ವೀಕೃತಿದಾರರಿಗೆ ಒಂದೇ MMS ಕಳುಹಿಸಿ" - "ಅಜ್ಞಾತ ಸಂಖ್ಯೆ" - "ಹೊಸ ಸಂದೇಶ" - "ಹೊಸ ಸಂದೇಶ." - "ಸಿಮ್ ಆಯ್ಕೆದಾರ" - "%1$s ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ, ಸಿಮ್ ಆಯ್ಕೆ" - "ವಿಷಯವನ್ನು ಸಂಪಾದಿಸಿ" - "ಸಿಮ್ ಆಯ್ಕೆ ಮಾಡಿ ಅಥವಾ ವಿಷಯ ಸಂಪಾದಿಸಿ" - "ಆಡಿಯೋ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ" - "ಹೊಸ ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಿ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ ಪಟ್ಟಿ" - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ" - "ಹೊಸ ಸಂದೇಶ" - "ಸಂವಾದ ಪಟ್ಟಿ" - "ಸಂವಾದಗಳನ್ನು ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ" - "ಸಂದೇಶಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ" - "ಇನ್ನಷ್ಟು ಸಂವಾದಗಳನ್ನು ವೀಕ್ಷಿಸಿ" - "ಹೆಚ್ಚಿನ ಸಂದೇಶಗಳನ್ನು ವೀಕ್ಷಿಸಿ" - "ಸಂವಾದವನ್ನು ಅಳಿಸಲಾಗಿದೆ" - "ಸಂಭಾಷಣೆಯನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಬೇರೆ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ ಸಂಭಾಷಣೆಯನ್ನು ತೋರಿಸಲು ಸ್ಪರ್ಶಿಸಿ" - "ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಅನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಸಂಗ್ರಹಣೆ ಸ್ಥಳಾವಕಾಶವು ಕಡಿಮೆ ಇದೆ. ಕೆಲವು ಡೇಟಾ ನಷ್ಟವಾಗಬಹುದು." - "ಲಗತ್ತುಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಆಯ್ಕೆ ಖಚಿತಪಡಿಸಿ" - "%d ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ" - "ದಯವಿಟ್ಟು ಒಂದು ಅಥವಾ ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕಿ ಮತ್ತು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ನಿಮ್ಮ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ನೀವು ಪ್ರಯತ್ನಿಸಬಹುದು, ಆದರೆ ನೀವು ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕದ ಹೊರತು ಇದು ತಲುಪದಿರಬಹುದು." - "ನೀವು ಪ್ರತಿ ಸಂದೇಶಕ್ಕೆ ಒಂದು ವೀಡಿಯೊ ಮಾತ್ರ ಕಳುಹಿಸಬಹುದು. ದಯವಿಟ್ಟು ಹೆಚ್ಚುವರಿ ವೀಡಿಯೊಗಳನ್ನು ತೆಗೆದುಹಾಕಿ ಹಾಗೂ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ." - "ಹೇಗಾದರೂ ಕಳುಹಿಸು" - "ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" - "%1$s (%2$s)" - "%s ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ" - diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml deleted file mode 100644 index 876776b..0000000 --- a/res/values-ko/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "제목 없음" - "nosubject" - - - "예" - "아니요" - "알겠습니다" - "하하" - "감사합니다." - "동의합니다" - "좋습니다." - "가는 중입니다" - "나중에 연락드리겠습니다" - ":)" - ":(" - - diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml deleted file mode 100644 index 79949ea..0000000 --- a/res/values-ko/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "메시지" - "메시지" - "대화 선택" - "설정" - "메시지 보내기" - "첨부파일 추가" - "도움말" - "환영합니다." - "건너뛰기" - "다음 >" - "다음" - "종료" - "설정 >" - "설정" - "메시지를 사용하려면 SMS, 전화, 주소록에 대한 권한이 필요합니다." - "설정 > 앱 > 메시지 > 권한에서 권한을 변경할 수 있습니다." - "설정, 앱, 메시지, 권한에서 권한을 변경할 수 있습니다." - "자주 연락하는 사람" - "모든 연락처" - "받는사람: %s" - "사진 또는 동영상 캡처" - "이 기기에서 이미지 선택" - "오디오 녹음" - "사진 선택" - "미디어가 선택되었습니다." - "미디어가 선택 취소되었습니다." - "%d개 선택됨" - "이미지 - %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "이미지" - "오디오 녹음" - "공유" - "방금 전" - "지금" - - %d - %d - - - %d시간 - %d시간 - - - %d - %d - - - %d - 1주 - - - %d개월 - 1개월 - - - %d - 1년 - - "클래스 0 메시지" - "저장" - "기기의 저장공간이 부족합니다. 메시지에서 공간을 확보하기 위해 오래된 메시지를 삭제합니다." - "저장 공간이 부족함" - "기기 공간이 더 확보되어야 메시지 앱에서 메시지를 보내고 받을 수 있습니다." - "SMS 저장공간이 부족합니다. 메시지를 삭제해야 합니다." - "전화번호 확인" - "이 단계를 한 번만 실행하면 메시지 앱에서 그룹 메시지를 정확히 전달할 수 있습니다." - "전화번호" - "미디어가 포함된 모든 메시지 삭제" - "%s보다 오래된 메시지 삭제" - "%s 이상 된 메시지 자동 삭제" - "무시" - "미디어가 포함된 모든 메시지를 삭제하시겠습니까?" - "%s 이상 된 메시지를 삭제하시겠습니까?" - "%s 이상 된 메시지를 삭제하고 자동 삭제를 사용하시겠습니까?" - "%s님이 보낸 내용" - "보낸 내용" - "%s님이 보낸 메시지" - "메시지 전송됨" - "보내는 중..." - "전송되지 않았습니다. 다시 시도하려면 터치하세요." - "전송되지 않았습니다. 다시 시도하는 중…" - "다시 보내기 또는 삭제" - "음성 통화로 응급 서비스에 연락하시기 바랍니다. 현재 SMS를 보낼 수 없습니다." - "실패" - "다운로드할 새 MMS 메시지" - "새 MMS 메시지" - "다운로드하지 못했습니다." - "다시 시도하려면 터치" - "터치하여 다운로드" - "다운로드 또는 삭제" - "다운로드 중..." - "메시지가 만료되었거나 사용할 수 없음" - "크기: %1$s, 만료: %2$s" - "보낼 수 없습니다. 수신자가 올바르지 않습니다." - "네트워크에 서비스가 활성화되어 있지 않습니다." - "네트워크 문제로 인해 전송하지 못했습니다." - "메시지가 만료되었거나 사용할 수 없음" - "(제목 없음)" - "알 수 없는 발신자" - "전송 완료" - "%2$s님이 보낸 메시지(%1$s)를 다운로드할 수 없습니다." - "메모리가 부족하여 데이터베이스 작업을 완료할 수 없습니다." - "메시지를 보내지 못함" - "일부 메시지가 전송되지 않았습니다." - - 대화 %d개에 메시지 %d - 대화 1개에 메시지 %d - - "메시지를 다운로드하지 못했습니다." - "일부 메시지가 다운로드되지 않았습니다." - - 대화 %d개에 메시지 %d - 대화 1개에 메시지 %d - - "%1$s에 메시지를 보내지 못했습니다." - "음성 통화로 응급 서비스에 연락하시기 바랍니다. 현재 %1$s번으로 SMS를 보낼 수 없습니다." - - 새 메시지 %d - 새 메시지 1개 - - "시작" - "카메라를 사용할 수 없습니다." - "카메라를 사용할 수 없습니다." - "동영상을 캡처할 수 없습니다." - "미디어를 저장할 수 없습니다." - "사진을 찍을 수 없음" - "뒤로" - "보관처리됨" - "삭제" - "보관처리" - "보관 취소" - "알림 사용 중지" - "알림 사용" - "연락처 추가" - "다운로드" - "전송" - "삭제" - "이 메일을 삭제하시겠습니까?" - "이 작업은 취소할 수 없습니다." - "삭제" - - 대화를 삭제하시겠습니까? - 대화를 삭제하시겠습니까? - - "삭제" - "취소" - "받는사람" - "여러 이미지 선택" - "선택항목 확인" - "+%d" - "오디오를 녹음할 수 없습니다. 다시 시도해 주세요." - "오디오를 재생할 수 없습니다. 다시 시도해 주세요." - "오디오를 저장할 수 없습니다. 다시 시도해 주세요." - "길게 터치" - ", " - " " - ": " - " " - "사진" - "오디오 클립" - "동영상" - "연락처 카드" - "다운로드" - "SMS를 통해 답장" - "MMS로 답장" - "답장" - - 참여자 %d - 참여자 %d - - "나" - "연락처가 차단 및 보관되었습니다." - "연락처 차단 해제 및 보관 취소됨" - "%d개 보관됨" - "%d개 보관 취소됨" - "알림 사용 중지됨" - "알림 사용 설정됨" - "준비가 완료되었습니다. 전송을 다시 터치해 주세요." - "메시지가 기본 SMS 앱으로 설정되었습니다." - - 첨부파일 삭제 - 첨부파일 삭제 - - "오디오 첨부파일" - "오디오 첨부파일 재생" - "일시중지" - "%s님의 메시지입니다. %s." - "받지 못한 %s님의 메시지: %s. 시간: %s." - "%s님의 메시지: %s. 시간: %s." - "%s님에게 전송되지 않은 메시지: %s. 시간: %s." - "%s님에게 전송 중인 메시지: %s. 시간: %s." - "%s님에게 전송하지 못한 메시지: %s. 시간: %s." - "%s님에게 보내는 메시지: %s. 시간: %s." - "받지 못한 %s님의 메시지: %s. 시간: %s. %s." - "%s님의 메시지: %s. 시간: %s. %s." - "%s 그룹에 전송되지 않은 메시지: %s. 시간: %s." - "%s 그룹에 전송 중인 메시지: %s. 시간: %s." - "%s 그룹에 전송하지 못한 메시지: %s. 시간: %s." - "%s 그룹에 보내는 메시지: %s. 시간: %s." - "메시지를 전달하지 못했습니다. 다시 시도하려면 터치하세요." - "%s님과 대화" - "제목 삭제" - "동영상 촬영" - "정지 이미지 캡처" - "사진 촬영" - "동영상 녹화 시작" - "전체화면 카메라로 전환" - "전면 및 후면 카메라 전환" - "녹화 중지 및 비디오 첨부" - "동영상 녹화 중지" - "메시지 사진" - - 사진 %d장이 \"%s\" 앨범에 저장됨 - 사진 %d장이 \"%s\" 앨범에 저장됨 - - - 동영상 %d개가 \"%s\" 앨범에 저장됨 - 동영상 %d개가 \"%s\" 앨범에 저장됨 - - - 첨부파일 %d개가 \"%s\" 앨범에 저장됨 - 첨부파일 %d개가 \"%s\" 앨범에 저장됨 - - - 첨부파일 %d개가 \"다운로드\"에 저장됨 - 첨부파일 %d개가 \"다운로드\"에 저장됨 - - - 첨부파일 %d개가 첨부되었습니다. - 첨부파일 %d개가 저장되었습니다. - - - 첨부파일 %d개를 저장하지 못했습니다. - 첨부파일 %d개를 저장하지 못했습니다. - - "저장된 MMS 첨부파일" - "설정" - "보관처리됨" - "닫기" - "MMS" - "고급" - "디버그" - "알림" - "소리" - "무음" - "진동" - "차단됨" - "SMS 전송 보고" - "모든 SMS에 대해 전송 확인 요청" - "자동 검색" - "MMS 메시지를 자동으로 수신" - "로밍 중 자동 수신" - "로밍 시 MMS 자동 검색" - "그룹 메시지" - "수신자가 여러 명일 때 MMS를 사용하여 한 개의 메시지를 전송합니다." - "기본 SMS 앱" - "기본 SMS 앱" - - - "전화번호" - "알 수 없음" - "발신 메시지 소리" - "SMS 덤프" - "수신된 SMS 원시 데이터를 외부 저장소 파일에 덤프합니다." - "MMS 덤프" - "수신된 MSS 원시 데이터를 외부 저장소 파일에 덤프합니다." - "긴급 무선 경보" - "메시지 옵션" - "텍스트 복사" - "세부정보 보기" - "삭제" - "전달" - "메시지 세부정보" - "유형: " - "문자 메시지" - "멀티미디어 메시지" - "보낸사람: " - "받는사람: " - "보낸 시간: " - "받은 시간: " - "제목: " - "크기: " - "중요도: " - "SIM: " - "높음" - "보통" - "낮음" - "SIM %s" - "숨긴 발신자 주소" - "파일을 로드하는 동안 메시지를 보낼 수 없습니다." - "첨부파일을 로드할 수 없습니다. 다시 시도해 주세요." - "네트워크가 준비되지 않았습니다. 다시 시도해 주세요." - "텍스트 삭제" - "텍스트 및 숫자 입력 간 전환" - "더 많은 참가자 추가" - "참가자 확인" - "새로운 대화 시작" - "이 항목을 선택합니다." - "Play 동영상" - "사용자 및 옵션" - "디버그" - "사용자 및 옵션" - "일반" - "이 대화에 참여하는 사용자" - "전화 걸기" - "메시지 보내기" - "<br/><small>%s</small>에서 메시지 보내기" - - 사진 전송 - 사진 전송 - - - 오디오 전송 - 오디오 전송 - - - 동영상 전송 - 동영상 전송 - - - 연락처 카드 전송 - 연락처 카드 전송 - - - 첨부파일 전송 - 첨부파일 전송 - - - 첨부파일 %d개 전송 준비 완료 - 첨부파일 1개 전송 준비 완료 - - "의견 보내기" - "Google Play 스토어에서 보기" - "버전 정보" - "버전: %1$s" - "오픈소스 라이선스" - "알림" - "첨부파일 한도 도달" - "첨부파일을 로드하지 못했습니다." - "주소록에 추가하시겠습니까?" - "연락처 추가" - "제목" - "제목: " - "%s%s" - "연락처 카드 로드 중" - "연락처 카드를 로드할 수 없습니다." - "연락처 카드 보기" - - 연락처 %d - 연락처 %d - - "연락처 카드" - "생일" - "메모" - "메시지 전달" - "답장" - "외 1명" - "외 %d명" - "SMS 사용 중지됨" - "메시지를 전송하려면 메시지를 기본 SMS 앱으로 설정하세요." - "메시지를 기본 SMS 앱으로 설정하세요." - "변경" - "메시지를 수신하려면 메시지를 기본 SMS 앱으로 설정하세요." - "SMS 메시지 보내기에 사용할 기본 앱이 선택되어 있지 않습니다." - "기기 소유자가 허용하지 않은 앱입니다." - "확인" - "대화 참가자가 너무 많습니다." - - 잘못된 연락처 - 잘못된 연락처 - - "카메라 이미지를 로드할 수 없습니다." - "나: " - "%s: " - "임시보관" - "새 대화를 시작하면 여기에 표시됩니다." - "보관처리된 대화는 여기에 표시됩니다." - "대화 로드 중..." - "사진" - "오디오 클립" - "동영상" - "연락처 카드" - "MMS" - "실행취소" - "다시 시도" - "새로운 메시지를 시작하려면 연락처 이름 또는 전화번호를 입력하세요." - "차단" - "%s 차단" - "%s 차단 해제" - "%s을(를) 차단하시겠습니까?" - "앞으로 이 번호에서 보낸 메시지를 계속 받지만 알림은 더 이상 받지 않습니다. 이 대화는 보관처리됩니다." - "차단된 연락처" - "차단 해제" - "차단된 연락처" - "문서 라이브러리에서 이미지 선택" - "메시지를 전송하는 중입니다." - "메시지가 전송되었습니다." - "이동통신 데이터 사용이 중지되어 있습니다. 설정을 확인하세요." - "비행기 모드에서는 메시지를 보낼 수 없습니다." - "메시지를 보내지 못했습니다." - "메시지가 다운로드되었습니다." - "이동통신 데이터 사용이 중지되어 있습니다. 설정을 확인하세요." - "비행기 모드에서 메시지를 다운로드할 수 없습니다." - "메시지를 다운로드할 수 없습니다." - "0" - "1" - "2" - "3" - "4" - "5" - "6" - "7" - "8" - "9" - "%1$s을(를) 사용하여 메시지를 보낼 수 없습니다. 오류: %2$d" - "알 수 없는 이동통신사를 사용하여 메시지를 보낼 수 없습니다. 오류: %1$d" - "전달: %s" - "서비스가 네트워크에서 활성화되지 않아 메시지가 전송되지 않았습니다." - "메시지가 전송되지 않음: 잘못된 수신 주소" - "메시지가 전송되지 않음: 잘못된 메시지" - "메시지가 전송되지 않음: 지원되지 않는 콘텐츠" - "메시지가 전송되지 않음: 지원되지 않는 메시지" - "메시지가 전송되지 않음: 용량 초과" - "새 메시지" - "보기" - "이미지" - "관련 애플리케이션을 찾을 수 없습니다." - "수신자 삭제" - "새 메시지" - "취소" - "액세스포인트 네임(APN) 수정" - "설정되지 않음" - "이름" - "APN" - "MMSC" - "MMS 프록시" - "MMS 포트" - "MCC" - "MNC" - "APN 유형" - "APN 삭제" - "새 APN" - "저장" - "취소" - "이름 입력란은 비워둘 수 없습니다." - "APN을 비워둘 수 없습니다." - "MCC 입력란에는 3자리 숫자를 입력해야 합니다." - "MNC 입력란에는 2~3자리 숫자를 입력해야 합니다." - "기본 APN 설정 복원 중" - "기본값으로 재설정" - "기본 APN 설정을 초기화했습니다." - "제목 없음" - "APN" - "APN" - "새 APN" - "이 사용자는 액세스포인트 네임(APN) 설정을 수정할 수 없습니다." - "클립보드에 복사할까요?" - "복사" - "전송 대상: %s" - "일반" - "고급" - "일반 설정" - "고급 설정" - "\'%s\' SIM" - "모든 수신자에게 개별 SMS 보내기(나만 답장을 받을 수 있음)" - "모든 수신자에게 하나의 MMS 보내기" - "알 수 없는 번호" - "새 메시지" - "새 메시지" - "SIM 선택기" - "%1$s 선택됨. SIM 선택기" - "제목 수정" - "SIM 선택 또는 제목 수정" - "길게 눌러 오디오 녹음" - "새로운 대화 시작" - "메시지" - "메시지 목록" - "메시지" - "새 메시지" - "대화 목록" - "대화 로드 중" - "메시지 로드 중" - "대화 더보기" - "메시지 더보기" - "대화를 삭제함" - "대화가 삭제되었습니다. 다른 메시지 대화를 표시하려면 터치하세요." - "차단됨" - "차단 해제됨" - "저장공간이 충분하지 않아 일부 데이터가 저장되지 않을 수 있습니다." - "첨부파일 선택" - "선택항목 확인" - "%d개 선택됨" - "하나 이상의 첨부파일을 삭제하고 다시 시도하세요." - "메시지 보내기를 시도할 수는 있지만 하나 이상의 첨부파일을 삭제하지 않으면 전송되지 않을 수 있습니다." - "메시지당 하나의 동영상만 보낼 수 있습니다. 추가 동영상을 삭제하고 다시 시도하세요." - "메시지에서 첨부파일이 로드되지 않았습니다." - "무시하고 보내기" - "대화를 시작할 수 없습니다." - "%1$s(%2$s)" - "%s 선택됨" - diff --git a/res/values-ky-rKG/arrays.xml b/res/values-ky-rKG/arrays.xml deleted file mode 100644 index 0227144..0000000 --- a/res/values-ky-rKG/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "темасы жок" - "темасыжок" - - - "Ооба" - "Жок" - "OK" - "Хе-хе" - "Рахмат" - "Туура" - "Сонун" - "Жолдомун" - "Макул, бир аздан кийин кайрылайынчы" - ":)" - ":(" - - diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml deleted file mode 100644 index b53ca0c..0000000 --- a/res/values-ky-rKG/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "SMS/MMS" - "SMS/MMS" - "Маек тандаңыз" - "Жөндөөлөр" - "Билдирүү жөнөтүү" - "Тиркеме кошуңуз" - "Жардам" - "Кош келиңиз" - "Өткөрүп жиберүү" - "Кийинки >" - "Кийинки" - "Чыгуу" - "Жөндөөлөр >" - "Жөндөөлөр" - "SMS/MMS\'ке SMS, телефон жана байланыштар уруксаты керек." - "Уруксаттарды өзгөртүү үчүн Жөндөөлөр > Колдонмолор > SMS/MMS > уруксаттарды басыңыз." - "Уруксаттарды өзгөртүү үчүн Жөндөөлөр, Колдонмолор, SMS/MMS, уруксаттарды басыңыз." - "Көп байланышкандар" - "Бардык байланыштар" - "Төмөнкүгө жөнөтүү %s" - "Сүрөттөрдү же видео тартып алуу" - "Бул түзмөктөн сүрөттөрдү тандаңыз" - "Аудио жаздыруу" - "Сүрөт тандаңыз" - "Медиа файл тандалды." - "Медиа файл тандоодон чыгарылды." - "%d тандалды" - "сүрөт %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "сүрөт" - "Аудио жаздыруу" - "Бөлүшүү" - "Жаңы эле" - "Азыр" - - %d мүн - %d мүн - - - %d саат - %d саат - - - %d күн - %d күн - - - %d апта - бир апта - - - %d ай - бир ай - - - %d жыл - бир жыл - - "0 классындагы билдирүү" - "Сактоо" - "Түзмөктө бош орун жетишпейт. Орун бошотуу үчүн, SMS/MMS эски SMS билдирүүлөрүн автоматтык түрдө жок кылат." - "Сактагычта орун калбай баратат" - "Түзмөгүңүздө көбүрөөк бош орун болмоюнча, SMS/MMS билдирүүлөрдү жөнөтүп же ала албашы мүмкүн." - "SMS үчүн орун жетишпейт. Билдирүүлөрдү жок кылышыңыз керек окшойт." - "Телефон номериңизди ырастаңыз" - "Номер топтошкон SMS/MMS билдирүүлөрүн жөнөтүү үчүн талап кылынат. Аны бир гана жолу киргизүү керек." - "Телефон номери" - "Медиа файл камтылган бардык билдирүүлөрдү жок кылуу" - "%s ашкан билдирүүлөрдү жок кылуу" - "%s мурунку билдирүүлөр автоматтык түрдө жок кылынсын" - "Көңүл бурбоо" - "Медиа менен бардык файлдар жок кылынсынбы?" - "%s мурунку файлдар жок кылынсынбы?" - "%s мурунку билдирүүлөр жок кылынып, авто жок кылуу күйгүзүлсүнбү?" - "%s айтты" - "Сиз айттыңыз" - "%s дегенден билдирүү" - "Билдирүү жөнөттүңүз" - "Жөнөтүлүүдө…" - "Жөнөтүлгөн жок. Дагы аракет кылуу үчүн тийип коюңуз." - "Жөнөтүлгөн жок. Кайра аракет жасалууда…" - "Кайра жөнөтүү же жок кылуу" - "Өзгөчө кырдаал кызматтарына үн чалуу жасаңыз. Азыркы учурда текст билдирүүңүз жеткирилбей койду." - "Болбой калды" - "Жүктөлүп алынуучу жаңы MMS билдирүү" - "Жаңы MMS билдирүү" - "Жүктөлүп алынган жок" - "Дагы аракет кылуу үчүн тийип коюңуз" - "Жүктөп алуу үчүн тийип коюңуз" - "Жүктөп алуу же жок кылуу" - "Жүктөлүүдө…" - "Билдирүү мөөнөтү аяктады же жеткиликтүү эмес" - "өлчөмү: %1$s, мөөнөтү бүтөт: %2$s" - "Жөнөтүлбөй жатат. Алуучу жарактуу эмес." - "Тармакта кызмат жандырылган эмес" - "Тармактагы көйгөйдөн улам жөнөтүлгөн жок" - "Билдирүү мөөнөтү аяктады же жеткиликтүү эмес" - "(Темасы жок)" - "Белгисиз жөнөтүүчү" - "Жеткирилди" - "%2$s келген %1$s билдирүүсү жүктөлүп алынган жок." - "Сактагычта орун аз болгондуктан, дайындар базасы аяктабай калды" - "Билдирүү жөнөтүлгөн жок" - "Айрым SMS/MMS билдирүүлөрү жөнөтүлгөн жок" - - %d баарлашууда %d билдирүү - Бир баарлашууда %d билдирүү - - "Билдирүү жүктөлүп алынган жок" - "Айрым SMS/MMS билдирүүлөрү жүктөлүп алынган жок" - - %d баарлашууда %d билдирүү - Бир баарлашууда %d билдирүү - - "%1$s дегенге билдирүү жөнөтүлгөн жок" - "Өзгөчө кырдаал кызматтарына үн чалуу жасаңыз. Азыркы учурда %1$s номерине текст билдирүүңүз жеткирилбей койду." - - %d жаңы билдирүү - Жаңы билдирүү - - "Баштоо" - "Камера жеткиликтүү эмес" - "Камера жеткиликтүү эмес" - "Видеону тартып алуу жеткиликтүү эмес" - "Медиа сакталбай жатат" - "Сүрөткө тарта албайт" - "Артка" - "Архивделди" - "Жок кылуу" - "Архивдөө" - "Архивден чыгаруу" - "Эскертмелерди өчүрүү" - "Эскертмелерди күйгүзүү" - "Байланыш кошуу" - "Жүктөп алуу" - "Жөнөтүү" - "Жок кылуу" - "Бул билдирүү жок кылынсынбы?" - "Бул аракетти артка кайтаруу мүмкүн эмес." - "Жок кылуу" - - Бул баарлашуулөр жок кылынсынбы? - Бул баарлашуу жок кылынсынбы? - - "Жок кылуу" - "Жокко чыгаруу" - "Кимге" - "Бир нече сүрөт тандаңыз" - "Тандалгандарды ырастаңыз" - "+%d" - "Аудио жазылбай жатат. Дагы аракет кылыңыз." - "Аудио ойнотулбай жатат. Дагы аракет кылыңыз." - "Аудио сакталган жок. Дагы аракет кылыңыз." - "Коё бербей amp; тийип туруу" - ", " - " " - ": " - " " - "Сүрөт" - "Аудио клип" - "Видео" - "Байланыш картасы" - "Жүктөп алуу" - "SMS аркылуу жооп берүү" - "MMS аркылуу жооп берүү" - "Жооп берүү" - - %d катышуучу - %d катышуучу - - "Мен" - "Байланыш бөгөттөлдү & архивделди" - "Байланыш бөгөттөн жана архивден чыгарылды" - "%d архивделди" - "%d архивден чыгарылды" - "Эскертмелер өчүрүлгөн" - "Эскертмелер күйгүзүлгөн" - "Баары коюлду. Жөнөтүүнү кайра басыңыз." - "SMS/MMS демейки SMS колдонмосу катары коюлду." - - Тиркемелерди жарактан чыгаруу - Тиркемени жарактан чыгаруу - - "Аудио тиркеме" - "Аудио тиркемени ойнотуу" - "Бир азга токтотуу" - "%s жөнөткөн билдирүү: %s." - "%s: %s билдирүүсү бузулду. Убакыт: %s" - "%s: %s билдирүүсү. Убакыт: %s" - "Төмөнкүгө жиберилбеген билдирү %s: %s. Убакыт: %s" - "Төмөнкүгө билдирүү жөнөтүлүүдө: %s: %s. Убакыт: %s" - "%s: %s билдирүүсү бузулду. Убакыт: %s" - "%s: %s билдирүүсү. Убакыт: %s" - "%s: %s билдирүүсү бузулду. Убакыт: %s. %s." - "%s: %s билдирүүсү. Убакыт: %s. %s." - "Төмөнкүгө жиберилбеген билдирүү: %s: %s. Убакыт: %s" - "Төмөнкүгө билдирүү жөнөтүлүүдө: %s: %s. Убакыт: %s" - "%s: %s билдирүүсү бузулду. Убакыт: %s" - "%s: %s билдирүүсү. Убакыт: %s" - "Билдирүү жөнөтүлбөй койду. Кайра аракет кылуу үчүн тийип коюңуз." - "%s менен сүйлөшүү" - "Темасын жок кылуу" - "Видео жаздыруу" - "Кыймылдабаган сүрөттү тартып алуу" - "Сүрөт тартуу" - "Видео тартып баштоо" - "Камераны толук экранга которуу" - "Алдыңкы жана арткы камераны которуштуруу" - "Тартылып жаткан видеону токтотуп, тиркеңиз" - "Видео жаздырууну токтотуу" - "SMS/MMS сүрөттөрү" - - %d сүрөт \"%s\" альбомуна сакталды - %d сүрөт \"%s\" альбомуна сакталды - - - %d видео \"%s\" альбомуна сакталды - %d видео \"%s\" альбомуна сакталды - - - %d тиркеме \"%s\" альбомуна сакталды - %d тиркеме \"%s\" альбомуна сакталды - - - %d тиркеме \"Жүктөлүп алынгандарга\" сакталды - %d тиркеме \"Жүктөлүп алынгандарга\" сакталды - - - %d тиркеме сакталды - %d тиркеме сакталды - - - %d тиркеме сакталбай калды - %d тиркеме сакталбай калды - - "Сакталган MMS тиркемеси" - "Жөндөөлөр" - "Архивделди" - "Жабуу" - "MMS" - "Өркүндөтүлгөн" - "Мүчүлүштүктөрдү таап оңдоо" - "Эскертмелер" - "Үн" - "Үнсүз" - "Дирилдөө" - "Бөгөттөлгөн" - "SMS жеткирүү кабарлары" - "Ар бир жөнөтүлгөн SMS билдирүүсүнүн алынгандыгын кабарлап коюну өтүнүү" - "Авто-түшүрүү" - "MMS билдирүүлөрүн автоматтык түрдө түшүрүп алуу" - "Роумингде авто-түшүрүп алуу" - "Роуминг учурунда MMS\'ти автоматтык түрдө чыгарып алуу" - "Топтошуп билдирүү жазышуу" - "Бир нече алуучуга бир билдирүү жөнөтүү үчүн MMS\'ти колдонуңуз" - "Демейки SMS колдонмо" - "Демейки SMS колдонмо" - - - "Телефон номериңиз" - "Белгисиз" - "Билдирүү кеткенде добуш чыксын" - "SMS\'ти ыргытуу" - "Алынган SMS\'тин чийки дайындарын тышкы сактагычтагы файлга ыргытыңыз" - "MMS\'ти ыргытуу" - "Алынган MMS\'тин чийки дайындарын тышкы сактагычтагы файлга ыргытыңыз" - "Зымсыз айгай кабарлар" - "Билдирүүнүн параметрлери" - "Текстти көчүрүү" - "Дайындарды карап көрүү" - "Жок кылуу" - "Багыттоо" - "Билдирүүнүн чоо-жайы" - "Түрү: " - "SMS билдирүүсү" - "Мультимедиа билдирүүсү" - "Кимден: " - "Кимге: " - "Жөнөтүлдү: " - "Алынды: " - "Темасы: " - "Өлчөмү: " - "Шашылыштыгы: " - "SIM: " - "Жогору" - "Орточо" - "Төмөн" - "SIM %s" - "Дареги жашырылган жөнөтүүчү" - "Тиркемелер жүктөлүп жатканда билдирүү жөнөтүлбөйт." - "Тиркеме жүктөлбөй жатат. Дагы аракет кылыңыз." - "Тармак даяр эмес. Дагы аракет кылыңыз." - "Текстти жок кылуу" - "Текст жана сандарды киргизүү ыкмаларын которуштуруу" - "Дагы катышуучуларды кошуу" - "Катышуучуларды ырастоо" - "Жаңы сүйлөшүү баштоо" - "Бул нерсени тандоо" - "Видео ойнотуу" - "Адамдар & параметрлер" - "Мүчүлүштүктөрдү таап оңдоо" - "Адамдар & параметрлер" - "Жалпы" - "Бул маектеги адамдар" - "Чалуу" - "Билдирүү жөнөтүү" - "<br/><small> %s</small> аркылуу билдирүү жөнөтүү" - - Сүрөттөрдү жөнөтүү - Сүрөт жөнөтүү - - - Аудиолорду жөнөтүү - Аудио жөнөтүү - - - Видеолорду жөнөтүү - Видео жөнөтүү - - - Байланыш карталарын жөнөтүү - Байланыш картасын жөнөтүү - - - Тиркемелерди жөнөтүү - Тиркемени жөнөтүү - - - %d тиркеме жөнөтүүгө даяр - Бир тиркеме жөнөтүүгө даяр - - "Жооп пикир жөнөтүү" - "Google Play Дүкөнүндө карап көрүү" - "Версия маалыматы" - "%1$s версиясы" - "Ачык булак лицензиялары" - "Эскертмелер" - "Тиркеме чегине жетти" - "Тиркеме жүктөлбөй койду." - "Байланыштарга кошулсунбу?" - "Байланыш кошуу" - "Тема" - "Темасы: " - "%s%s" - "Байланыш картасы жүктөлүүдө" - "Байланыштын картасы жүктөлгөн жок" - "Байланыш картасын карап көрүү" - - %d байланыш - %d байланыш - - "Байланыш карталары" - "Туулган күн" - "Эскертүүлөр" - "Билдирүүнү багыттоо" - "Жооп берүү" - "+1" - "+%d" - "SMS өчүк" - "Билдирүүлөрдү жөнөтүү үчүн, SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз" - "SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз" - "Өзгөртүү" - "Билдирүүлөрдү алуу үчүн, SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз" - "SMS билдирүүлөрдү жөнөтүү үчүн артыкчылыктуу SIM тандалган эмес" - "Бул колдонмого түзмөк ээсинен уруксат жок." - "OK" - "Маекте катышуучулар өтө эле көп" - - Жараксыз байланыштар - Жараксыз байланыш - - "Камера сүрөтү жүктөлгөн жок" - "Сиз: " - "%s: " - "Сомо" - "Жаңы маек башталганда, аны бул жердеги тизмеден көрөсүз" - "Архивделген маектер бул жерден көрүнөт" - "Маектер жүктөлүүдө…" - "Сүрөт" - "Аудио клип" - "Видео" - "Байланыш картасы" - "MMS" - "Кайтаруу" - "Дагы аракет кылуу" - "Жаңы билдирүүнү жазып баштоо үчүн байланыштын атын же телефон номерин киргизиңиз" - "Бөгөттөө" - "%s дегенди бөгөттөө" - "%s дегенди бөгөттөн чыгаруу" - "%s бөгөттөлсүнбү?" - "Бул номерден билдирүүлөрдү ала бересиз, бирок мындан ары эскертмелерди албай каласыз. Бул маек архивделет." - "Бөгөттөлгөн байланыштар" - "БӨГӨТТӨН ЧЫГАРУУ" - "Бөгөттөлгөн байланыштар" - "Документтер китепканасынан сүрөт тандаңыз" - "Билдирүү жөнөтүлүүдө…" - "Билдирүү жөнөтүлдү" - "Уюлдук дайындар өчүрүлгөн. Жөндөөлөрүңүздү текшериңиз." - "Учак режиминде билдирүүлөр жөнөтүлбөйт" - "Билдирүү жөнөтүлбөй жатат" - "Билдирүү жүктөлүп алынды" - "Уюлдук дайындар өчүрүлгөн. Жөндөөлөрүңүздү текшериңиз." - "Учак режиминде билдирүүлөрдү жүктөп ала албайт" - "Билдирүү жүктөлүп алынбай койду" - "Ноль" - "Бир" - "Эки" - "Үч" - "Төрт" - "Беш" - "Алты" - "Жети" - "Сегиз" - "Тогуз" - "Билдирүүнү %1$s оператору аркылуу жөнөтүү мүмкүн эмес, ката %2$d" - "Билдирүүнү белгисиз түзмөк аркылуу жөнөтүү мүмкүн эмес, ката %1$d" - "Багыттоо: %s" - "Билдирүү жөнөтүлгөн жок: кызмат тармакта жандырылган эмес" - "Билдирүү жөнөтүлгөн жок: жеткирүү дареги жараксыз" - "Билдирүү жөнөтүлгөн жок: билдирүү жараксыз" - "Билдирүү жөнөтүлгөн жок: мазмун колдоого алынбайт" - "Билдирүү жөнөтүлгөн жок: билдирүү колдоого алынбайт" - "Билдирүү жөнөтүлгөн жок: өтө чоң" - "Жаңы билдирүү" - "Карап көрүү" - "Сүрөт" - "Ылайыктуу колдонмо табылган жок" - "Алуучуну чыгарып салуу" - "Жаңы билдирүү" - "Жокко чыгаруу" - "Мүмкүнчүлүк алуу түйүнүн түзөтүү" - "Коюлган эмес" - "Ысымы" - "APN" - "MMSC" - "MMS прокси" - "MMS порту" - "MCC" - "MNC" - "APN түрү" - "APN жок кылуу" - "Жаңы APN" - "Сактоо" - "Жарактан чыгаруу" - "Аталыш талаасы бош болбошу керек." - "APN бош болбошу керек." - "MCC талаасы 3 сандан турушу керек." - "MNC талаасы 2 же 3 сандан турушу керек." - "Демейки APN жөндөөлөрү калыбына келтирилүүдө." - "Баштапкы абалга келтирүү" - "Демейки APN жөндөөлөрү баштапкы абалга келтирилди." - "Аталышы жок" - "Мүмкүнчүлүк алуу түйүнүнүн аталыштары" - "APN\'дер" - "Жаңы APN" - "Бул колдонуучу мүмкүнчүлүк алуу түйүнүнүн аталышынын жөндөөлөрүн колдоно албайт" - "Буферге көчүрүлсүнбү?" - "Көчүрүү" - "%s SIMге" - "Жалпы" - "Өркүндөтүлгөн" - "Жалпы жөндөөлөр" - "Өркүндөтүлгөн жөндөөлөр" - "\"%s\" SIM" - "Бардык алуучуларга жеке SMS билдирүүлөрүн жөнөтүү. Бардык жоопторду сиз гана аласыз" - "Бардык алуучуларга бир MMS жөнөтүү" - "Белгисиз номер" - "Жаңы билдирүү" - "Жаңы билдирүү." - "SIM тандагыч" - "%1$s тандалган, SIM тандагыч" - "Теманы түзөтүү" - "SIM тандоо же теманы түзөтүү" - "Аудио жаздырыш үчүн кол тийгизип туруңуз" - "Жаңы сүйлөшүү баштоо" - "SMS/MMS" - "SMS/MMS тизмеси" - "SMS/MMS" - "Жаңы билдирүү" - "Маек тизмеси" - "Сүйлөшүүлөр жүктөлүүдө…" - "Билдирүүлөр жүктөлүүдө" - "Көбүрөөк сүйлөшүүлөрдү көрүү" - "Дагы билдирүүлөрдү көрүү" - "Сүйлөшүү жок кылынды" - "Сүйлөшүү жок кылынды. Башка SMS/MMS сүйлөшүүсүн көрсөтүү үчүн тийиңиз" - "Бөгөттөлдү" - "Бөгөттөн чыгарылды" - "Сактагычта бош орун аз. Айрым дайындар жоголушу мүмкүн." - "Тиркемелерди тандаңыз" - "Тандалгандарды ырастаңыз" - "%d тандалды" - "Тиркемелердин бирин же көбүрөөгүн алып салып, кайра аракет кылыңыз." - "Билдирүүңүздү жөнөтүп көрсөңүз болот, бирок ал бир же көбүрөөк тиркемени алып салмайыңызча жеткирилбеши мүмкүн." - "Бир билдирүү менен бир эле видео жөнөтө аласыз. Кошумча видеолорду алып салып, кайра аракет кылыңыз." - "Тиркелген файлдар жүктөлбөй калды." - "Баары бир жөнөтүлсүн" - "Сүйлөшүүнү баштай алган жок" - "%1$s (%2$s)" - "%s тандалды" - diff --git a/res/values-ldrtl-v21/styles.xml b/res/values-ldrtl-v21/styles.xml deleted file mode 100644 index 4775979..0000000 --- a/res/values-ldrtl-v21/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/res/values-ldrtl/styles.xml b/res/values-ldrtl/styles.xml deleted file mode 100644 index bd270c7..0000000 --- a/res/values-ldrtl/styles.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values-lo-rLA/arrays.xml b/res/values-lo-rLA/arrays.xml deleted file mode 100644 index cae3797..0000000 --- a/res/values-lo-rLA/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ບໍ່ມີຫົວຂໍ້" - "​ບໍ່​ມີ​ຫົວ​ຂໍ້" - - - "​ແມ່ນ​ແລ້ວ" - "ບໍ່​" - "ຕົກລົງ" - "​ຮິ​ຮິ" - "​ຂອບ​ໃຈ" - "​ຂ້ອຍ​​ຍອມ​ຮັບ" - "​ຄັກຫຼາຍ" - "​ກຳ​ລັງ​ໄປ" - "​ຕົກ​ລົງ, ດຽວ​ຂ້ອຍ​ຈະ​ຕິດ​ຕໍ່​ກັບ​ໄປ" - ":)" - ":(" - - diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml deleted file mode 100644 index f433031..0000000 --- a/res/values-lo-rLA/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "ຂໍ້ຄວາມ" - "ຂໍ້ຄວາມ" - "​ເລືອກ​ການ​ແປງ​ຂໍ້​ມູນ" - "​ການ​ຕັ້ງ​ຄ່າ" - "​ສົ່ງ​ຂໍ້​ຄວາມ" - "ເພີ່ມໄຟລ໌ແນບ" - "​ຊ່ວຍ​ເຫຼືອ" - "ຍິນດີຕ້ອນຮັບ" - "ຂ້າມ" - "ຕໍ່ໄປ >" - "ຕໍ່​ໄປ" - "ອອກ" - "ການ​ຕັ້ງ​ຄ່າ >" - "ການ​ຕັ້ງ​ຄ່າ" - "ການສົ່ງຂໍ້​ຄວາມຕ້ອງ​ການ​ການ​ອະ​ນຸ​ຍາດ​ຫາ SMS, ໂທ​ລະ​ສັບ ແລະ ​ລາຍ​ຊື່." - "ທ່ານ​ສາ​ມາດ​ປ່ຽນ​ແປງ​ການ​ການ​ອະ​ນຸ​ຍາດ​ໄດ້​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ > ແອັບ > ຂໍ້​ຄວາມ > ການ​ອະ​ນຸ​ຍາດ." - "ທ່ານ​ສາ​ມາດ​ປ່ຽນ​ແປງ​ການ​ອະ​ນຸ​ຍາດ​ໄດ້​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ, ແອັບ, ຂໍ້​ຄວາມ, ການ​ອະ​ນຸ​ຍາດ." - "​ຄວາມ​ຖີ່" - "ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່​ທັງ​ໝົດ" - "ສົ່ງໄປ %s" - "ບັນ​ທຶກ​ຮູບ ຫຼື​ວິ​ດີ​ໂອ" - "ເລືອກ​ຮູບ​ສຳ​ລັບ​ອຸ​ປະ​ກອນ​ນີ້" - "ບັນທຶກສຽງ" - "ເລືອກ​ຮູບ" - "ມີ​ດີ​ນີ້​ຖືກ​ເລືອກ​ແລ້ວ." - "ມີ​ເດຍ​ຖືກ​ຖອນ​ເລືອກ​ແລ້ວ." - "ເລືອກແລ້ວ %d ລາຍການ" - "ຮູບ %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ຮູບ" - "ບັນທຶກສຽງ" - "ແບ່ງ​ປັນ" - "ຕອນນີ້" - "ຕອນນີ້" - - %d ນ​ທ - %d ນ​ທ - - - %d ຊົ່ວ​ໂມງ - %d ຊົ່ວ​ໂມງ - - - %d ວັນ - %d ວັນ - - - %d ອາ​ທິດ - ໜຶ່ງອາ​ທິດ - - - %d ເດືອນ - ໜຶ່ງ​ເດືອນ - - - %d ປີ - ໜຶ່ງ​ປີ - - "ຂໍ້ຄວາມຄລາສ 0" - "ບັນທຶກ" - "ອຸ​ປະ​ກອນ​ມີ​ພື້ນ​ທີ່​​ເກັບ​ຂໍ້​ມູນ​ໜ້ອຍ. ກາ​ນສົ່ງຂໍ້​ຄວາມ​ຈະ​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດ ເພື່ອ​ເຮັດ​ໃຫ້​ມີ​ພື້ນ​ທີ່​ຫວ່າງ​ເພີ່ມ​ຂຶ້ນ." - "ພື້ນທີ່​ຈັດ​ເກັບ​ຂໍ້ມູນ​ກຳລັງ​ຈະ​ເຕັມ" - "ການສົ່ງຂໍ້​ຄວາມ​ອາດ​ຈະ​ບໍ່​ສົ່ງ ຫຼື ຮັບ​ຂໍ້​ຄວາມ​ຈົນ​ກ່​ວາ​ມີ​ພື້ນ​ທີ່​ເພີ່ມ​ເຕີມ​ຢູ່​ໃນ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ." - "ພື້ນ​ທີ່​ເກັບ​ຂໍ້ຄວາມ​ໜ້ອຍ​ລົງ. ທ່ານ​ອາດ​ຈະ​ຕ້ອງ​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ອອກ​ກ່ອນ." - "ຢືນຢັນເບີໂທລະສັບຂອງທ່ານ" - "ຂັ້ນ​ຕອນ​ໃຊ້​ເທື່ອ​ດຽວ​ນີ້​ຈະ​ຮັບ​ປະ​ກັນ​ໃຫ້ຂໍ້​ຄວາມສົ່ງ​ຂໍ້​ຄວາມ​ກຸ່ມ​ຂອງ​ທ່ານ​ໄດ້​ຢ່າງ​ຖືກ​ຕ້ອງ." - "ເບີໂທລະສັບ" - "ລຶບ​ຂໍ້ຄວາມ​ທັງໝົດ​ພ້ອມ​ທັງ​ມີເດຍ​ຕ່າງໆ" - "ລຶບ​ຂໍ້​ຄວາມ​ທີ່​ເກົ່າ​ກວ່າ %s" - "ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ກວ່າ %s ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດ" - "ບໍ່ສົນໃຈ" - "ລຶບ​ຂໍ້ຄວາມ​ທັງ​ໝົດ​ພ້ອມ​ທັງ​ມີເດຍ​ຕ່າງໆຫຼື​ບໍ່?" - "ລຶບ​ຂໍ້ຄວາມ​ທີ່ເກົ່າກວ່າ %s ຫຼື​ບໍ່?" - "​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ກວ່າ %s ​ແລະ ເປີດ​ການ​ລຶບ​ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດຫຼື​ບໍ່?" - "%s ໄດ້ເວົ້າ" - "ທ່ານໄດ້​ເວົ້າ" - "ຂໍ້ຄວາມຈາກ %s" - "ທ່ານ​ໄດ້ສົ່ງ​ຂໍ້​ຄວາມ" - "ກຳລັງສົ່ງ..." - "ສົ່ງ​ບໍ່​ໄດ້. ສຳ​ຜັດ​ເພື່ອ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ." - "ສົ່ງ​ບໍ່​ໄດ້. ກຳ​ລັງ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ…" - "Resend or delete" - "ກະ​ລຸ​ນາ​ໂທ​ດ້ວຍ​ສຽງ​ໄປ​ຫາ​ບໍ​ລິ​ການ​ສຸກ​ເສີນ. ຂໍ້​ຄວາມ​ຂອງ​ທ່ານ​ບໍ່​ສາ​ມາດ​ນຳ​ສົ່ງ​ໄດ້​ໃນ​ເວ​ລາ​ນີ້." - "​ລົ້ມ​ເຫລວ" - "ຂໍ້ຄວາມ MMS ​ໃຫມ່​ທີ່ຈະ​ດາວ​ໂຫລດ" - "ຂໍ້ຄວາມ MMS ​ໃຫມ່" - "ບໍ່​ສາ​ມາດ​ດາວ​ໂຫລດ​ໄດ້" - "ສຳ​ຜັດ​ເພື່ອ​ລອງ​ອີກ​ຄັ້ງ" - "ແຕະເພື່ອ​ດາວ​ໂຫຼດ" - "ດາວ​ໂຫລດ​ ຫລື​ລຶບ" - "​ກຳ​ລັງ​ດາວ​ໂຫລດ..." - "ຂໍ້​ຄວາມ​ໝົດ​ອາຍຸ ຫຼື ໃຊ້​ບໍ່​ໄດ້​ແລ້ວ" - "size: %1$s, expiration: %2$s" - "​ບໍ່​ສາ​ມາດ​ສົ່ງ​ໄດ້. ຂໍ້​ມູນ​ຜູ່​ຮັບ​ບໍ່​ຖືກ​ຕ້ອງ." - "ບໍ່​ໄດ້​ເປີດ​ໃຊ້​ບໍລິການ​ໃນ​ເຄືອຂ່າຍ" - "ບໍ່ສາມາດ​ສົ່ງໄດ້​ເນື່ອງຈາກ​ມີບັນຫາ​ກັບເຄືອຂ່າຍ" - "ຂໍ້​ຄວາມ​ໝົດ​ກຳ​ນົດ ຫຼື ໃຊ້​ບໍ່​ໄດ້​ແລ້ວ" - "(ບໍ່ມີຫົວຂໍ້)" - "ບໍ່ຮູ້ຜູ່ສົ່ງ" - "ສົ່ງຮອດແລ້ວ" - "ບໍ່​ສາມາດ​ດາວໂຫລດ​ຂໍ້​ຄວາມ %1$s ຈາກ %2$s ໄດ້." - "ບໍ່​ສາມາດ​ປະຕິບັດ​ການ​ທາງ​ດ້ານ​ຖານ​ຂໍ້ມູນ​ເນື່ອງ​ຈາກ​ໜ່ວຍ​ຄວາມ​ຈຳ​ຕໍ່າ" - "ຂໍ້ຄວາມ​ຍັງ​ບໍ່ໄດ້​ຖືກສົ່ງ" - "ບາງ​ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຢູ່​ໃນການສົ່ງ​ຂໍ້​ຄວາມ" - - %d ຂໍ້​ຄວາມ​ໃນ %d ບົດ​ສົນ​ທະ​ນາ - %d ຂໍ້​ຄວາມ​ໃນ​ຫນຶ່ງ​ບົດ​ສົນ​ທະ​ນາ - - "ບໍ່ສາມາດດາວໂຫລດຂໍ້ຄວາມໄດ້" - "ບາງ​ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ຮັບ​ການ​ດາວ​ໂຫຼດ​ຢູ່​ໃນການສົ່ງ​ຂໍ້​ຄວາມ" - - %d ຂໍ້​ຄວາມ​ໃນ %d ບົດ​ສົນ​ທະ​ນາ - %d ຂໍ້​ຄວາມ​ໃນ​ຫນຶ່ງ​ບົດ​ສົນ​ທະ​ນາ - - "ຂໍ້​ຄວາມ​ຫາ %1$s ບໍ່​ໄດ້​ສົ່ງ" - "ກະ​ລຸ​ນາ​ໂທ​ດ້ວຍ​ສຽງ​ໄປ​ຫາ​ບໍ​ລິ​ການ​ສຸກ​ເສີນ. ຂໍ້​ຄວາມ​ຂອງ​ທ່ານຫາ %1$s ບໍ່​ສາ​ມາດ​ນຳ​ສົ່ງ​ໄດ້​ໃນ​ເວ​ລາ​ນີ້." - - %d ຂໍ້ຄວາມ​ໃໝ່. - ຂໍ້ຄວາມ​ໃໝ່. - - "ເລີ່ມ" - "ບໍ່​ສາມາດ​ໃຊ້​ກ້ອງໄດ້" - "ບໍ່​ສາມາດ​ໃຊ້​ກ້ອງໄດ້" - "​ບໍ່​ສາ​ມາດ​ໃຊ້​ການບັນ​ທຶກວິ​ດີ​ໂອ​ໄດ້" - "​ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ​ມີ​ເດຍ​ໄດ້" - "ບໍ່​ສາ​ມາດ​ຖ່າຍ​ຮູບ​ໄດ້" - "ກັບຄືນ" - "ຈັດເກັບແລ້ວ" - "​ລຶບ" - "ຈັດເກັບ" - "ຖອດອອກຈາກແຟ້ມ" - "ປິດ​ການ​ແຈ້ງເຕືອນ" - "ເປີດ​ການ​ແຈ້ງເຕືອນ" - "ເພີ່ມ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່" - "ດາວໂຫລດ" - "ສົ່ງ" - "​ລຶບ" - "ລຶບ​ຂໍ້ຄວາມ​ນີ້ຫຼືບໍ່?" - "​ການ​ກະ​ທຳ​ນີ້ຈະ​ບໍ່​ສາ​ມາດ​ກູ້​ຄືນ​ໄດ້." - "​ລຶບ" - - ລຶບການສົນທະນານີ້ບໍ? - ລຶບການສົນທະນານີ້ບໍ? - - "​ລຶບ" - "​ຍົກ​ເລີກ" - "ໄປ" - "​ເລືອກ​ຫຼາຍ​ຮູບ" - "​ຢືນ​ຢັນ​ການ​ເລືອກ" - "+%d" - "ບໍ່​ສາ​ມາດ​ອັດ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ." - "ບໍ່​ສາ​ມາດຫຼິ້ນ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ." - "​ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ." - "ແຕະຄ້າງ​ໄວ້" - ", " - " " - ": " - " " - "ຮູບພາບ" - "ຄລິບສຽງ" - "ວິດີໂອ" - "ບັດ​ລາຍຊື່​ຜູ່ຕິດຕໍ່" - "ດາວໂຫຼດ" - "ຕອບ​ກັບ​ຜ່ານ SMS" - "ຕອບ​ກັບ​ຜ່ານ MMS" - "ຕອບກັບ" - - %d ຜູ້ເຂົ້າ​ຮ່ວມ - %d ຜູ້ເຂົ້າ​ຮ່ວມ - - "ຂ້ອຍ" - "ບລັອກ​ & ຈັດ​ເກັບ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ເຂົ້າ​ແຟ້ມ​ແລ້ວ" - "ປົດ​ບ​ລັອກ​ລາຍ​ຊື່​ແລ້ວ & ບໍ່​ໄດ້​ເກັບ​ໄວ້" - "%d ຖືກຈັດເກັບແລ້ວ" - "%d ຖືກ​ຖອດ​ອອກ​ຈາກ​ບ່ອນ​ຈັດ​ເກັບ​ແລ້ວ" - "ການ​ແຈ້ງ​ເຕືອນ​ປິດ​ແລ້ວ" - "ການ​ແຈ້ງ​ເຕືອນ​ເປີດ​ແລ້ວ" - "​ຕັ້ງ​ຮຽບ​ຮ້ອຍ​ແລ້ວ. ສຳ​ຜັດ​ເພື່ອ​ສົ່ງ​ໃໝ່​ອີກ​ຄັ້ງ." - "ຕັ້ງ​ຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ​ສຳ​ເລັດ​ແລ້ວ." - - ເອົາ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖິ້ມ - ເອົາ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖິ້ມ - - "ໄຟ​ລ໌​ຄັດ​ຕິດ​ສຽງ" - "ຫຼິ້ນ​ສິ່ງຕິດຄັດທີ່ເປັນ​ສຽງ" - "ຢຸດຊົ່ວຄາວ" - "ຂໍ້​ຄວາມ​ຈາກ%s: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ​ຈາກ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ຈາກ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ກຳ​ລັງ​ສົ່ງ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດຈາກ %s: %s. ເວ​ລາ: %s. %s." - "ຂໍ້​ຄວາມ​ຈາກ %s: %s. ເວ​ລາ: %s. %s." - "ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມກຳ​ລັງ​ສົ່ງ​​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ສຳເລັດ​ຫາ %s: %s. Time: %s." - "ຂໍ້​ຄວາມ​ຫາ %s: %s. ເວ​ລາ: %s." - "ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ. ສຳ​ຜັດ​ເພື່ອ​ລອງ​ໃໝ່​ອີກ." - "ການ​ສົນ​ທະ​ນາ​ກັບ %s" - "​ລຶບ​ຫົວ​ຂໍ້" - "ບັນ​ທຶກ​ວິດີໂອ" - "ບັນ​ທຶກ​ຮູບ" - "ຖ່າຍຮູບ" - "ເລີ່ມ​ການ​ບັນທຶກ​ວິດີໂອ" - "ສະລັບ​ໄປ​ໃຊ້​ກ້ອງ​ແບບ​ເຕັມຈໍ" - "​ສະ​ລັບ​ລະ​ຫວ່າງ​ກ້ອງ​ໜ້າ ແລະ ຫຼັງ" - "​ຢຸດ​ການ​ບັນ​ທຶກ ແລະ ແນບ​ວິ​ດີ​ໂອ" - "​ຢຸດ​ການ​ບັນ​ທຶກວິ​ດີ​ໂອ" - "ຮູບຖ່າຍານສົ່ງ​ຂໍ້​ຄວາມ" - - %d ຮູບ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"%s\" - %d ຮູບ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"%s\" - - - %d ວິ​ດີ​ໂອ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"%s\" - %d ວິ​ດີ​ໂອ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"%s\" - - - %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶ​ກ​ໄປ​ໃສ່ອາ​ລະບັ້ມ \"%s\" - %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶ​ກ​ໄປ​ໃສ່ອາ​ລະ​ບັ້ມ \"%s\" - - - %dໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່ \"ດາວ​ໂຫຼດ\" - %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່ \"ດາວ​ໂຫຼດ\" - - - %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ແລ້ວ - %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ແລ້ວ - - - ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ໄດ້ - ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ %d ໄຟ​ລ໌​ຄັດ​ຕິດ​ໄດ້ - - "ບັນ​ທຶກ​ໄຟລ໌​ແນບ MMS ​ແລ້ວ" - "ການ​ຕັ້ງ​ຄ່າ" - "ຈັດເກັບແລ້ວ" - "ປິດ" - "MMS" - "​ຂັ້ນ​ສູງ" - "ດີບັ໊ກ" - "ການແຈ້ງເຕືອນ" - "ສຽງ" - "ປິດສຽງ" - "ສັ່ນເຕືອນ" - "ບລັອກ​ແລ້ວ" - "​ລາຍ​ງານ​ຜົນ​ການສົ່ງ SMS" - "​ຂໍ​ລາຍ​ງານຜົນ​ການ​ສົ່ງ​ສຳ​ລັບ​ແຕ່​ລະ​ SMS ທີ່​ທ່ານ​ສົ່ງ" - "ດຶງ​ຂໍ້ມູນ​ອັດຕະໂນມັດ" - "ດຶງຂໍ້ຄວາມ MMS ອັດຕະໂນມັດ" - "ດຶງ​ຂໍ້ຄວາມ​ອັດຕະໂນມັດ​ເມື່ອ​ໂຣມມິງ" - "ກູ້​ຄືນ MMS ອັດ​ຕະ​ໂນ​ມັດ​ເມື່ອ​ໂຣມ​ມິງ" - "ການສົ່ງ​ຂໍ້ຄວາມກຸ່ມ" - "ໃຊ້ MMS ເພື່ອສົ່ງ​ຂໍ້ຄວາມ​ໃດນຶ່ງ​ເມື່ອມີຜູ່ຮັບ​ຫຼາຍຄົນ" - "ແອັບຯ SMS ມາດ​ຕະ​ຖານ" - "ແອັບຯ SMS ມາດ​ຕະ​ຖານ" - - - "​ເບີ​ໂທ​ລະ​ສັບ​ຂອງ​ທ່ານ" - "​ບໍ່​ຮູ້​ຈັກ" - "ສຽງ​ຂໍ້​ຄວາມ​ຂາ​ອອກ" - "ດຶງ SMS" - "ດຶງ​ຂໍ້​ມູນ SMS ​ດິບທີ່​ໄດ້​ຮັບ​ໃນ​ຮູບ​ແບບ​ໄຟ​ລ໌​ເກັບ​ຂໍ້​ມູນ​ພາຍນອກ" - "ດຶງ MMS" - "ດຶງ​ຂໍ້​ມູນ MMS ​ດິບທີ່​ໄດ້​ຮັບ​ໃນ​ຮູບ​ແບບ​ໄຟ​ລ໌​ເກັບ​ຂໍ້​ມູນ​ພາຍນອກ" - "ແຈ້ງ​ເຕືອນ​ລະບົບ​ໄຮ້ສາຍ" - "​ໂຕ​ເລືອກ​ຂໍ້​ຄວາມ" - "​ສຳ​ເນົາ​ຂໍ້​ຄວາມ" - "​ເບິ່ງ​ລາຍ​ລະ​ອຽດ" - "​ລຶບ" - "ສົ່ງຕໍ່" - "​ລາຍ​ລະ​ອຽດ​ຂໍ້​ຄວາມ" - "ປະ​ເພດ: " - "​ຂໍ້​ຄວາມ" - "ຂໍ້​ຄວາມ​ມັນ​ຕິ​ມີ​ເດຍ" - "ຈາກ: " - "ເຖິງ: " - "ສົ່ງ​ເມື່ອ: " - "ໄດ້​ຮັບເມື່ອ: " - "ຫົວ​ຂໍ້: " - "​ຂະ​ໜາດ: " - "ຄວາມ​ສຳ​ຄັນ: " - "​ຊິມ: " - "​ສູງ" - "​ປານ​ກາງ" - "​ຕ່ຳ" - "​ຊິມ %s" - "​​ເຊື່ອງ​ທີ່​ຢູ່​ຜູ່​ສົ່ງ" - "​ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ໄດ້​ໃນ​ຂະ​ນະ​ທີ່​ໂຫຼດ​ໄຟລ໌​ແນບ." - "ບໍ່​ສາ​ມາດ​ໂຫລດ​ໄຟລ໌​ແນບ​ໄດ້. ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ." - "ເຄືອ​ຂ່າຍ​ບໍ່​ພ້ອມ. ລອງ​ໃໝ່​ອີກ." - "ລຶບ​ຂໍ້​ຄວາມ" - "ສະ​ລັບ​ລະ​ຫວ່າງ​ການ​ພິມ​ໂຕ​ອັກ​ສອນ ແລະ​ໂຕ​ເລກ" - "ເພີ່ມ​ຜູ່​ເຂົ້າ​ຮ່ວມ​ອື່ນ" - "ຢືນ​ຢັນ​ຜູ່​ເຂົ້າ​ຮ່ວມ" - "​ເລີ່ມ​ບົດ​ສົນ​ທະ​ນາ​ໃໝ່" - "​ເລືອກ​ລາຍ​ການ​ນີ້" - "ຫຼິ້ນວິດີໂອ" - "​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ & ໂຕ​ເລືອກ" - "ດີບັ໊ກ" - "​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ & ໂຕ​ເລືອກ" - "ທົ່ວໄປ" - "ຄ​ົນ​ໃນ​ບົດ​ສົນ​ທະ​ນາ​ນີ້" - "​ໂທ​ອອກ" - "ສົ່ງຂໍ້ຄວາມ" - "​ສົ່ງ​ຂໍ້​ຄວາມ<br/><small>​ຈາກ %s</small>" - - ​ສົ່ງ​ຮູບ​ພາບ - ​ສົ່ງ​ຮູບ​ພາບ - - - ​ສົ່ງ​ສຽງ - ​ສົ່ງ​ສຽງ - - - ສົ່ງ​ວິດີໂອ - ສົ່ງ​ວິດີໂອ - - - ​ສົ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່ - ​ສົ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່ - - - ​ສົ່ງ​ໄຟລ໌​ຄັດ​ຕິດ - ​ສົ່ງ​ໄຟລ໌​ຄັດ​ຕິດ - - - %d ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ພ້ອມ​ສົ່ງ​ໄດ້​ແລ້ວ - ໜຶ່ງ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ພ້ອມ​ສົ່ງ​ໄດ້​ແລ້ວ - - "​ສົ່ງ​ຄຳ​ຕິ​ຊົມ" - "ເບິ່ງໃນ Google Play Store" - "​​ຂໍ​້​ມູນ​ເວີ​ຊັນ" - "ເວີ​ຊັນ %1$s" - "​ລິ​ຂະ​ສິດ​ໂອ​ເພນ​ຊອດ" - "ການແຈ້ງເຕືອນ" - "ຮອດ​ຂີດ​ຈຳ​ກັດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ແລ້ວ" - "ໂຫຼດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ບໍ່​ສຳ​ເລັດ." - "ເພີ່ມ​ໃສ່​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ບໍ່?" - "ເພີ່ມ​​ໃສ່​ລາ​ຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່" - "ຫົວຂໍ້" - "ຫົວຂໍ້: " - "%s%s" - "​ກຳ​ລັງ​ໂຫລດ​ບັດ​ລາຍ​ຊື່​ຜ​ູ່​ຕິດ​ຕໍ່" - "​ບໍ່​ສາ​ມາດ​ໂຫລດ​ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ໄດ້" - "​ເບິ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່" - - %d ລາຍ​ຊື່ - %d ລາຍ​ຊື່ - - "ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່" - "ວັນເດືອນປີເກີດ" - "ບັນທຶກ" - "​ສົ່ງ​ຕໍ່​ຂໍ້​ຄວາມ" - "ຕອບຄືນ" - "+1" - "+%d" - "SMS ​ຖືກ​ປິດ​ແລ້ວ" - "ເພື່ອ​ສົ່ງ, ຕັ້ງ​ການສົ່ງຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ" - "ຕັ້ງການສົ່ງ​ຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ" - "ປ່ຽນແປງ" - "​ເພື່ອ​ຮັບ​ຂໍ້​ຄວາມ, ຕັ້ງການສົ່ງຂໍ້​ຄວາມໃຫ້​ເປັນ​ແອັບ SMS ​ເລີ່ມ​ຕົ້ນ​ຂອງ​ທ່ານ" - "ບໍ່​ມີ SIM ທີ່​ມັກ​ຖືກ​ເລືອກ​ໄວ້​ ເພື່ອ​ສົ່ງ​ຂໍ້​ຄວາມ SMS" - "​​ເຈົ້າ​ຂອງ​ອຸ​ປະ​ກອນບໍ່​ອະ​ນຸ​ຍາດ​ໃຫ້​ໃຊ້​ແອັບຯ​ນີ້." - "ຕົກລົງ" - "​ມີ​ຜູ່​ເຂົ້າ​ຮ່ວມ​ໃນ​ການ​ສົນ​ທະ​ນາຫຼາຍ​ເກີນ​ໄປ" - - ລາຍ​ຊື່​ຕິດ​ຕໍ່​ບໍ່​ຖືກ​ຕ້ອງ - ລາຍ​ຊື່​ຕິດ​ຕໍ່​ບໍ່​ຖືກ​ຕ້ອງ - - "​ບໍ່​ສາ​ມາດ​ໂຫລດ​ຮູບ​ຈາກ​ກ້ອງ​ໄດ້" - "ທ່ານ: " - "%s: " - "ສະບັບຮ່າງ" - "ໃນເວລາ​ທີ່ທານ​ເລີ່ມການ​ສົນທະນາ​ໃຫມ່, ທ່ານຈະເຫັນ​ມັນສະແດງ​ຢູ່ບ່ອນ​ນີ້" - "​ບົດ​ສົນ​ທະ​ນາ​ທີ່​ຖືກ​ຈັດ​ເກັບ​ຈະ​ສະ​ແດງ​ຢູ່​ນີ້" - "ກຳລັງ​ໂຫລດ​ບົດ​ສົນທະນາ..." - "ຮູບພາບ" - "ຄລິບສຽງ" - "ວິດີໂອ" - "ບັດ​ລາຍຊື່​ຕິດຕໍ່" - "MMS" - "ຍົກເລີກ" - "ລອງໃໝ່" - "​ປ້ອນ​ຊື່​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ ຫຼື ເບີ​ໂທ​ລະ​ສັບ​ເພື່ອ​ເລີ່ມ​ຂໍ້​ຄວາມ​ໃໝ່" - "ບລັອກ" - "ບລັອກ %s" - "​ປົດ​ບລັອກ %s" - "ບລັອກ %s ບໍ?" - "​ທ່ານ​ຈະ​ຍັງ​ສືບ​ຕໍ່​ໄດ້​ຮັບ​ຂໍ້​ຄວາມ​ຈາກ​ເບີ​ນີ້ ແຕ່​ຈະ​ບໍ່​ຖືກ​ແຈ້ງ​ເຕືອນ​ອີກ​ຕໍ່​ໄປ. ບົດ​ສົນ​ທະ​ນາ​ນີ້​ຈະ​ຖືກ​ຈັດ​ເກັບ." - "​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ທີ່​ຖືກບລັອກ" - "​ປົດ​ບລັອກ" - "ບລັອກ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ແລ້ວ" - "​ເລືອກ​ຮູບ​ຈາກ​ຫ້ອງ​ສະ​ໝຸດ​ເອ​ກະ​ສານ" - "ກຳລັງສົ່ງຂໍ້ຄວາມ" - "ສົ່ງຂໍ້ຄວາມແລ້ວ" - "ຂໍ້​ມູນ​ເຄືອ​ຂ່າຍ​ໂທ​ລະ​ສັບ​ຖືກ​ປິດ​ໄວ້. ກະ​ລຸ​ນາ​ກວດ​ສອບ​ການ​ຕັ້ງ​ຄ່າ​ຂອງ​ທ່ານ." - "​ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ໃນ​ໂໝດ​ໃນ​ຍົນ​ໄດ້" - "ບໍ່ສາມາດສົ່ງຂໍ້ຄວາມໄປໄດ້" - "ດາວ​ໂຫຼດ​ຂໍ້​ຄວາມ​ແລ້ວ" - "ຂໍ້​ມູນເຊວ​ລູ​ລາ​ຖືກ​ປິດ​ໄວ້. ສອບ​ການ​ຕັ້ງ​ຄ່າ​ຂອງ​ທ່ານ." - "ບໍ່​ສາ​ມາດ​ກາວ​ໂຫຼດ​ຂໍ້​ຄວາມ​ຢູ່​ໃນ​ໂໝດ​ເຮືອ​ບິນ​ໄດ້" - "ບໍ່​ສາ​ມາດ​ດາວ​ໂຫຼດ​ໄດ້" - "​ສູນ" - "​ນຶ່ງ" - "​ສອງ" - "​ສາມ" - "​ສີ່" - "​ຫ້າ" - "​ຫົກ" - "​ເຈັດ" - "ແປດ" - "​ເກົ້າ" - "ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ດ້ວຍ %1$s ໄດ້, ຂໍ້​ຜິດ​ພາດ %2$d" - "ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ດ້ວຍບໍ​ລິ​ສັດ​ເຄືອ​ຂ່າຍ​ມື​ຖື​ບໍ່​ຮູ້​ຈັກ​ໄດ້, ຂໍ້​ຜິດ​ພາດ %1$d" - "Fwd: %s" - "ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ບໍ​ລິ​ການ​ຍັງ​ບໍ່​ໄດ້​ເປີດ​ນຳ​ໃຊ້​ໃນ​ເຄືອ​ຂ່າຍ​ເທື່ອ" - "ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ທີ່​ຢູ່​ປາຍ​ທາງບໍ່​ຖືກ​ຕ້ອງ" - "ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ຂໍ້​ຄວາມ​ບໍ່​ຖືກ​ຕ້ອງ" - "ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ​ບໍ່​ຮອງ​ຮັບ​ເນື້ອ​ຫາ" - "ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ບໍ່​ຮອງ​ຮັບ​ຂໍ້​ຄວາມ" - "ຂໍ້ຄວາມ​ຍັງ​ບໍ່ໄດ້​ຖືກສົ່ງ: ໃຫຍ່​ເກີນ​ໄປ" - "ຂໍ້ຄວາມໃໝ່" - "​ເບິ່ງ" - "ຮູບພາບ" - "ບໍ່​ສາ​ມາດ​ຊອກ​ຫາ​ແອັບ​ພລິ​ເຄ​ຊັນ​ທີ່​ເໝາະ​ສົມ​ໄດ້" - "ລຶບ​ຜູ່​ຮັບ" - "ຂໍ້ຄວາມໃໝ່" - "​ຍົກ​ເລີກ" - "ແກ້ໄຂ​ຈຸດ​ການ​ເຊື່ອມຕໍ່" - "ຍັງບໍ່ໄດ້ຕັ້ງ" - "ຊື່" - "APN" - "MMSC" - "MMS proxy" - "ພອດ MMS" - "MCC" - "MNC" - "ປະເພດ APN" - "ລຶບ APN" - "APN ໃໝ່" - "ບັນທຶກ" - "ຍົກເລີກ" - "ຊ່ອງຂໍ້ມູນ​ຊື່​ບໍ່ສາມາດ​ປ່ອຍ​ຫວ່າງໄດ້." - "APN ບໍ່​ສາມາດ​ປ່ອຍ​ໃຫ້​ຫວ່າງ​ໄດ້." - "ຊ່ອງຂໍ້ມູນ MMC ຕ້ອງມີ 3 ໂຕເລກ." - "ຊ່ອງຂໍ້ມູນ MNC ຕ້ອງມີ 2 ຫຼື 3 ໂຕເລກ." - "ກຳລັງ​ກັບ​ໄປ​ໃຊ້ການຕັ້ງຄ່າ APN ເລີ່ມຕົ້ນ." - "ກັບຄືນໄປໃຊ້ຄ່າເລີ່ມຕົ້ນ" - "​ສຳ​ເລັດ​ການ​ກັບ​ໄປໃຊ້ການ​ຕັ້ງ​ຄ່າ APN ເລີ່ມຕົ້ນແລ້ວ." - "ບໍ່ໄດ້ຕັ້ງຊື່" - "ຊື່ຂອງ​ຈຸດການ​ເຂົ້າເຖິງ (APN)" - "APNs" - "APN ໃໝ່" - "ຜູ່​ໃຊ້​ນີ້ບໍ່​ສາ​ມາດ​ຕັ້ງ​ຄ່າ​ຊື່​ຈຸດ​ການ​ເຂົ້າ​ເຖິງ​ໄດ້" - "​ສຳ​ເນົາ​ໄວ້​ໃນຄລິບບອດຫຼື​ບໍ່?" - "ສຳເນົາ" - "ເຖິງ %s" - "ທົ່ວໄປ" - "​ຂັ້ນ​ສູງ" - "ການຕັ້ງຄ່າ​ທົ່ວໄປ" - "ການຕັ້ງຄ່າ​ຂັ້ນສູງ" - "SIM \"%s\"" - "​ສົ່ງ​ຂໍ້​ຄວາມ SMS ​ສະ​ເພາະ​ບຸກ​ຄົນໄປ​ຫາ​ທຸກໆ​ຜູ່​ຮັບ. ​ສະ​ເພາະ​ທ່ານ​ເທົ່າ​ນັ້ນ​ທີ່​ຈະ​ໄດ້​ຮັບ​ແຕ່​ລະ​ການ​ຕອບ​ກັບ" - "​ສົ່ງ MMS ​ສະ​ບັບ​ນຶ່ງ​ໄປ​ຫາ​ທຸກໆ​ຜູ່​ຮັບ" - "​ເບີ​ໂທບໍ່​ຮູ້​ຈັກ" - "ຂໍ້ຄວາມ​ໃໝ່" - "ຂໍ້ຄວາມ​ໃໝ່." - "ຕົວ​ເລືອກ SIM" - "%1$s ຖືກ​ເລືອກ​ແລ້ວ, ຕົວ​ເລືອກ SIM" - "ແກ້ໄຂຫົວຂໍ້" - "ເລືອກ SIM ຫຼື​ແກ້​ໄຂ​ຫົວ​ຂໍ້" - "ສຳ​ພັດ​ຄ້າງ​ໄວ້ ເພື່ອ​ບັນ​ທຶກ​ສຽງ" - "​ເລີ່ມ​ບົດ​ສົນ​ທະ​ນາ​ໃໝ່" - "ຂໍ້ຄວາມ" - "ລາຍ​ການການສົ່ງ​ຂໍ້​ຄວາມ" - "ຂໍ້ຄວາມ" - "ຂໍ້ຄວາມໃໝ່" - "ລາຍການການສົນທະນາ" - "ກຳລັງ​ໂຫລດ​ບົດ​ສົນທະນາ..." - "ກຳລັງໂຫລດຂໍ້ຄວາມ" - "ເບິ່ງບົດສົນທະນາເພີ່ມເຕີມ" - "ເບິ່ງ​ຂໍ້​ຄວາມ​ເພີ່ມ​ເຕີມ" - "ການ​ສົນທະນາ​ຖືກລຶບແລ້ວ" - "ການ​ສົນ​ທະ​ນາ​ຖືກ​ລຶບ​ແລ້ວ. ສຳ​ຜັດ​ເພື່ອ​ສະ​ແດງ, ການ​ສົນ​ທະ​ນາ​ຂໍ້​ຄວາມ​ທີ່​ແຕກ​ຕ່າງ" - "ບລັອກ​ແລ້ວ" - "ປົດ​ບ​ລັອກ​ແລ້ວ" - "ພື້ນ​ທີ່​ຈັດ​ເກັບ​ຕ່ຳ. ບາງ​ຂໍ້​ມູນ​ອາດ​ຈະ​ເສຍ​ໄປ." - "ເລືອກ​ໄຟ​ລ໌​ຄັດ​ຕິດ" - "​ຢືນ​ຢັນ​ການ​ເລືອກ" - "%d ເລືອກ​ແລ້ວ" - "ກະ​ລຸ​ນາ​ເອົາ​ໜຶ່ງ ຫຼື​ຫຼາຍ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ອອກ​ໄປ ແລະ​ລອງ​ໃໝ່​ອີກ." - "ທ່ານ​ສາ​ມາດ​ລອງ​ສົ່ງ​ຂໍ້​ຄວາມ​ຂອງ​ທ່ານ​ໄດ້, ແຕ່​ມັນ​ອາດ​ຈະ​ບໍ່​ຖືກ​ສົ່ງ​ໄປ ນອກ​ຈາກວ່າ​ທ່ານ​ເອົາ​ໜຶ່ງ ຫຼື​ຫຼາຍ​ໄຟ​ລ໌​ຄັດ​ຕິດ." - "ທ່ານ​ສາ​ມາດ​ສົ່ງ​ໄດ້​ແຕ່​ໜຶ່ງວິ​ດີ​ໂອ​ຕໍ່​ຂໍ້​ຄວາມ​ເທົ່າ​ນັ້ນ. ກະ​ລຸ​ນາ​ເອົາ​ວິ​ດີ​ໂອ​ເພີ່ມ​ເຕີມ​ອອກ ແລະ​ລອງ​ໃໝ່​ອີກ." - "ການສົ່ງຂໍ້​ຄວາມ​ໂຫຼດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ບໍ່ສຳເລັດ." - "ແນວ​ໃດ​ກໍ່​ສົ່ງ​" - "ບໍ່​ສາ​ມາດ​ເລີ່ມ​ຕົ້ນ​ການ​ສົນ​ທະ​ນາ​ໄດ້" - "%1$s (%2$s)" - "%s ເລືອກ​ແລ້ວ" - diff --git a/res/values-lt/arrays.xml b/res/values-lt/arrays.xml deleted file mode 100644 index 3c1c116..0000000 --- a/res/values-lt/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "nėra temos" - "nėra temos" - - - "Taip" - "Ne" - "Gerai" - "Hehe" - "Ačiū" - "Sutinku" - "Puiku" - "Pakeliui" - "Gerai, informuosiu vėliau" - ":)" - ":(" - - diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml deleted file mode 100644 index 167fe9f..0000000 --- a/res/values-lt/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "Susirašinėjimas pranešimais" - "Susirašinėjimas pranešimais" - "Pasirinkti pokalbį" - "Nustatymai" - "Siųsti pranešimą" - "Pridėti priedą" - "Pagalba" - "Sveiki!" - "Praleisti" - "Kitas >" - "Kitas" - "Išeiti" - "Nustatymai >" - "Nustatymai" - "Susirašinėjimo programai reikia leidimų pasiekti SMS pranešimus, telefoną ir kontaktus." - "Leidimus galite pakeisti skiltyje „Nustatymai > Programos > Susirašinėjimo programa > Leidimai“." - "Leidimus galite pakeisti skiltyje „Nustatymai, Programos, Susirašinėjimo programa, Leidimai“." - "Dažniausiai naudojami" - "Visi kontaktai" - "Siųsti: %s" - "Fotografuokite arba filmuokite" - "Pasirinkite vaizdus iš šio įrenginio" - "Įrašyti garsą" - "Pasirinkti nuotrauką" - "Medija pasirinkta." - "Medija nepasirinkta." - "Pasirinkta: %d" - "vaizdas: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "vaizdas" - "Įrašyti garsą" - "Bendrinimas" - "Ką tik" - "Dabar" - - %d min. - %d min. - %d min. - %d min. - - - %d val. - %d val. - %d val. - %d val. - - - %d diena - %d dienos - %d dienos - %d dienų - - - %d savaitė - %d savaitės - %d savaitės - %d savaičių - - - %d mėnesiai - %d mėnesiai - %d mėnesio - %d mėnesių - - - %d metai - %d metai - %d metų - %d metų - - "0 klasės pranešimas" - "Išsaugoti" - "Mažai vietos įrenginyje. Susirašinėjimo programa automatiškai ištrins senesnius pranešimus, kad atlaisvintų vietos." - "Mažėja laisvos saugyklos vietos saugykloje" - "Galbūt Susirašinėjimo programa negalės siųsti ar gauti pranešimų, kol įrenginyje nebus daugiau laisvos vietos." - "Mažai laisvos SMS saugyklos vietos. Gali reikėti ištrinti pranešimus." - "Patvirtinkite telefono numerį" - "Atlikę šį vienkartinį veiksmą užtikrinsite, kad Susirašinėjimo programa tinkamai pateiks grupės pranešimus." - "Telefono numeris" - "Ištrinti visus pranešimus su medija" - "Ištrinti senesnius nei %s pranešimus" - "Automatiškai ištrinti pranešimus, kurie senesni nei %s" - "Nepaisyti" - "Ištrinti visus pranešimus su medija?" - "Ištrinti pranešimus, kurie senesni nei %s?" - "Ištrinti pranešimus, kurie senesni nei %s, ir įjungti automatinį ištrynimą?" - "%s sakė" - "Jūs sakėte" - "Pranešimas nuo %s:" - "Išsiuntėte pranešimą" - "Siunčiama…" - "Neišsiųsta. Palieskite, kad bandytumėte dar kartą." - "Neišsiųsta. Bandoma dar kartą…" - "Persiųsti arba ištrinti" - "Atlikite balso skambutį pagalbos tarnyboms. Šiuo metu negalime pateikti teksto pranešimo." - "Nepavyko" - "Naujas MMS pranešimas, kurį galima atsisiųsti" - "Naujas MMS pranešimas" - "Nepavyko atsisiųsti" - "Palieskite ir bandykite dar kartą" - "Palieskite, kad atsisiųstumėte" - "Atsisiųskite arba ištrinkite" - "Atsisiunčiama..." - "Pranešimo galiojimo laikas pasibaigė arba jis negalimas" - "dydis: %1$s, galiojimo pabaiga: %2$s" - "Nepavyko išsiųsti. Netinkamas gavėjas." - "Paslauga nesuaktyvinta tinkle" - "Nepavyko išsiųsti dėl tinklo problemos" - "Pranešimo galiojimo laikas pasibaigė arba jis negalimas" - "(Nėra temos)" - "Nežinomas siuntėjas" - "Pristatyta" - "Nepavyko atsisiųsti pranešimo „%1$s“ nuo %2$s." - "Nepavyko atlikti duomenų bazės operacijos dėl atminties trūkumo" - "Pranešimas neišsiųstas" - "Kai kurie pranešimai neišsiųsti naudojant Susirašinėjimo programą" - - Pranešimų: %d (%d pokalbis) - Pranešimų: %d (%d pokalbiai) - Pranešimų: %d (%d pokalbio) - Pranešimų: %d (%d pokalbių) - - "Pranešimas neatsisiųstas" - "Kai kurie pranešimai neatsisiųsti naudojant Susirašinėjimo programą" - - Pranešimų: %d (%d pokalbis) - Pranešimų: %d (%d pokalbiai) - Pranešimų: %d (%d pokalbio) - Pranešimų: %d (%d pokalbių) - - "Pranešimas numeriu %1$s nebuvo išsiųstas" - "Atlikite balso skambutį pagalbos tarnyboms. Šiuo metu negalime pateikti teksto pranešimo numeriu %1$s." - - %d naujas pranešimas - %d nauji pranešimai - %d naujo pranešimo - %d naujų pranešimų - - "Pradėti" - "Fotoaparatas negalimas" - "Fotoaparatas negalimas" - "Vaizdo įrašo fiksavimas negalimas" - "Nepavyko išsaugoti medijos" - "Negalima fotografuoti" - "Atgal" - "Suarchyvuota" - "Ištrinti" - "Archyvuoti" - "Išarchyvuoti" - "Išjungti pranešimus" - "Įjungti pranešimus" - "Pridėti kontaktą" - "Atsisiųsti" - "Siųsti" - "Ištrinti" - "Ištrinti šį pranešimą?" - "Šio veiksmo anuliuoti negalima." - "Ištrinti" - - Ištrinti šiuos pokalbius? - Ištrinti šiuos pokalbius? - Ištrinti šiuos pokalbius? - Ištrinti šiuos pokalbius? - - "Ištrinti" - "Atšaukti" - "Kam" - "Pasirinkti kelis vaizdus" - "Patvirtinti pasirinkimą" - "Dar %d" - "Nepavyko įrašyti garso. Bandykite dar kartą." - "Nepavyko paleisti garso įrašo. Bandykite dar kartą." - "Nepavyko išsaugoti garso įrašo. Bandykite dar kartą." - "Palieskite ir palaikykite" - ", " - " " - ": " - " " - "Nuotrauka" - "Garso įrašo klipas" - "Vaizdo įrašas" - "Kontakto kortelė" - "Atsisiųsti" - "Atsakyti SMS žinute" - "Atsakyti MMS" - "Atsakyti" - - %d dalyvis - %d dalyviai - %d dalyvio - %d dalyvių - - "Aš" - "Kontaktas užblokuotas ir suarchyvuotas" - "Kontaktas atblokuotas ir išarchyvuotas" - "Suarchyvuota: %d" - "Išarchyvuota: %d" - "Pranešimai išjungti" - "Pranešimai įjungti" - "Viskas nustatyta. Dar kartą palieskite „Siųsti“." - "Susirašinėjimo programa sėkmingai nustatyta kaip numatytoji SMS programa." - - Išmesti priedus - Išmesti priedus - Išmesti priedus - Išmesti priedus - - "Garso priedas" - "Leisti garso priedą" - "Pristabdyti" - "Pranešimas nuo %s: %s." - "Nepavyko atsiųsti pranešimo nuo %s: „%s“. Laikas: %s." - "Pranešimas nuo %s: „%s“. Laikas: %s." - "Neišsiųstas pranešimas grupei „%s“: „%s“. Laikas: %s." - "Siunčiamas pranešimas grupei „%s“: „%s“. Laikas: %s." - "Nepavyko išsiųsti pranešimo grupei „%s“: „%s“. Laikas: %s." - "Pranešimas grupei „%s“: „%s“. Laikas: %s." - "Nepavyko atsiųsti pranešimo nuo %s: „%s“. Laikas: %s. %s." - "Pranešimas nuo %s: „%s“. Laikas: %s. %s." - "Neišsiųstas pranešimas grupei „%s“: „%s“. Laikas: %s." - "Siunčiamas pranešimas grupei „%s“: „%s“. Laikas: %s." - "Nepavyko išsiųsti pranešimo grupei „%s“: „%s“. Laikas: %s." - "Pranešimas grupei „%s“: „%s“. Laikas: %s." - "Įvyko pranešimo klaida. Palieskite ir bandykite dar kartą." - "Pokalbis su %s" - "Pašalinti temą" - "Filmuoti" - "Fiksuoti nejudantį vaizdą" - "Fotografuoti" - "Pradėti filmuoti" - "Perjungti į viso ekrano režimu veikiantį fotoaparatą" - "Perjungti priekinį ir užpakalinį fotoaparatus" - "Sustabdyti įrašymą ir pridėti vaizdo įrašą" - "Sustabdyti vaizdo įrašymą" - "Susirašinėjimo programos nuotraukos" - - %d nuotrauka išsaugota albume „%s - %d nuotraukos išsaugotos albume „%s - %d nuotraukos išsaugota albume „%s - %d nuotraukų išsaugota albume „%s - - - %d vaizdo įrašas išsaugotas albume „%s - %d vaizdo įrašai išsaugoti albume „%s - %d vaizdo įrašo išsaugota albume „%s - %d vaizdo įrašų išsaugota albume „%s - - - %d priedas išsaugotas albume „%s - %d priedai išsaugoti albume „%s - %d priedo išsaugota albume „%s - %d priedų išsaugota albume „%s - - - %d priedas išsaugotas aplanke „Atsisiuntimai“ - %d priedai išsaugoti aplanke „Atsisiuntimai“ - %d priedo išsaugota aplanke „Atsisiuntimai“ - %d priedų išsaugota aplanke „Atsisiuntimai“ - - - Išsaugotas %d priedas - Išsaugoti %d priedai - Išsaugota %d priedo - Išsaugota %d priedų - - - Nepavyko išsaugoti %d priedo - Nepavyko išsaugoti %d priedų - Nepavyko išsaugoti %d priedo - Nepavyko išsaugoti %d priedų - - "Išsaugotas MMS priedas" - "Nustatymai" - "Suarchyvuota" - "Uždaryti" - "MMS" - "Išplėstiniai" - "Derinti" - "Pranešimai" - "Garsas" - "Tylus" - "Vibravimas" - "Užblokuota" - "SMS pristatymo ataskaitos" - "Pateikti kiekvieno išsiųsto SMS pristatymo ataskaitos užklausą" - "Automatiškai nuskaityti" - "Automatiškai nuskaityti MMS pranešimus" - "Aut. nuskait. naud. tarptinklinį ryšį" - "Automatiškai nuskaityti MMS, kai veikia tarptinklinis ryšys" - "Grupinis susirašinėjimas" - "Naudokite MMS, kad siųstumėte vieną pranešimą, kai yra keli gavėjai" - "Numatytoji SMS programa" - "Numatytoji SMS programa" - - - "Jūsų telefono numeris" - "Nežinomas" - "Siunčiamų pranešimų garsai" - "Pateikti SMS" - "Pateikti gautų SMS neapdorotus duomenis išorinės atminties faile" - "Pateikti MMS" - "Pateikti gautų MMS neapdorotus duomenis išorinės atminties faile" - "Belaidžiu ryšiu siunčiami įspėjimai" - "Pranešimo parinktys" - "Kopijuoti tekstą" - "Peržiūrėti išsamią informaciją" - "Ištrinti" - "Persiųsti" - "Išsami pranešimo informacija" - "Tipas: " - "teksto pranešimas" - "Įvairialypės informacijos pranešimas" - "Nuo: " - "Kam: " - "Išsiųsta: " - "Gauta: " - "Tema: " - "Dydis: " - "Pirmenybė: " - "SIM: " - "Aukšta" - "Įprasta" - "Žema" - "SIM %s" - "Paslėptas siuntėjo adresas" - "Negalima siųsti žinutės, kol įkeliami priedai." - "Nepavyko įkelti priedo. Bandykite dar kartą." - "Tinklas neparuoštas. Bandykite dar kartą." - "Pašalinti tekstą" - "Perjungti iš teksto įvedimo į skaičių įvedimą ir atvirkščiai" - "Pridėti daugiau dalyvių" - "Patvirtinti dalyvius" - "Pradėti naują pokalbį" - "Pasirinkti šį elementą" - "Leisti vaizdo įrašą" - "Žmonės ir parinktys" - "Derinti" - "Žmonės ir parinktys" - "Bendrieji" - "Žmonės šiame pokalbyje" - "Skambinti" - "Siųsti pranešimą" - "Siųsti pranešimą<br/><small>iš %s</small>" - - Siųsti nuotraukas - Siųsti nuotraukas - Siųsti nuotraukas - Siųsti nuotraukas - - - Siųsti garso įrašus - Siųsti garso įrašus - Siųsti garso įrašus - Siųsti garso įrašus - - - Siųsti vaizdo įrašus - Siųsti vaizdo įrašus - Siųsti vaizdo įrašus - Siųsti vaizdo įrašus - - - Siųsti kontaktų korteles - Siųsti kontaktų korteles - Siųsti kontaktų korteles - Siųsti kontaktų korteles - - - Siųsti priedus - Siųsti priedus - Siųsti priedus - Siųsti priedus - - - %d priedas paruoštas siųsti - %d priedai paruošti siųsti - %d priedo paruošta siųsti - %d priedų paruošta siųsti - - "Siųsti atsiliepimą" - "Žr. „Google Play“ parduotuvėje" - "Versijos informacija" - "%1$s versija" - "Atvirojo šaltinio licencijos" - "Pranešimai" - "Pasiektas priedų apribojimas" - "Nepavyko įkelti priedo." - "Pridėti prie kontaktų?" - "Pridėti kontaktą" - "Tema" - "Tema: " - "%s%s" - "Įkeliama kontakto kortelė" - "Nepavyko įkelti kontakto kortelės" - "Peržiūrėti kontakto kortelę" - - %d kontaktas - %d kontaktai - %d kontakto - %d kontaktų - - "Kontaktų kortelės" - "Gimimo data" - "Užrašai" - "Persiųsti pranešimą" - "Atsakyti" - "+1" - "+%d" - "SMS išjungta" - "Kad galėtumėte išsiųsti, nustatykite Susirašinėjimo programą kaip numatytąją SMS programą" - "Nustatykite Susirašinėjimo programą kaip numatytąją SMS programą" - "Pakeisti" - "Kad galėtumėte gauti pranešimus, nustatykite Susirašinėjimo programą kaip numatytąją SMS programą" - "Nepasirinkta jokių pageidaujamų SIM kortelių, kurias būtų galima naudoti SMS pranešimams siųsti" - "Įrenginio savininkas neleidžia naudoti šios programos." - "Gerai" - "Per daug pokalbio dalyvių" - - Netinkami kontaktai - Netinkami kontaktai - Netinkami kontaktai - Netinkami kontaktai - - "Nepavyko įkelti fotoaparato vaizdo" - "Jūs: " - "%s: " - "Juodraštis" - "Kai pradėsite naują pokalbį, jis bus pateiktas čia" - "Archyvuoti pokalbiai rodomi čia" - "Įkeliami pokalbiai..." - "Nuotrauka" - "Garso įrašo klipas" - "Vaizdo įrašas" - "Kontakto kortelė" - "MMS" - "Anuliuoti" - "Bandyti dar kartą" - "Įveskite kontakto vardą arba telefono numerį, kad pradėtumėte naują pranešimą" - "Blokuoti" - "Blokuoti %s" - "Panaikinti %s blokavimą" - "Blokuoti %s?" - "Ir toliau gausite pranešimus iš šio numerio, bet nebūsite apie juos įspėti. Šis pokalbis bus archyvuotas." - "Užblokuoti kontaktai" - "PANAIKINTI BLOKAVIMĄ" - "Užblokuoti kontaktai" - "Pasirinkite vaizdą iš dokumentų bibliotekos" - "Siunčiamas pranešimas" - "Pranešimas išsiųstas" - "Mobiliojo ryšio duomenys išjungti. Patikrinkite nustatymus." - "Negalima siųsti pranešimų lėktuvo režimu" - "Nepavyko išsiųsti pranešimo" - "Pranešimas atsisiųstas" - "Mobiliojo ryšio duomenys išjungti. Patikrinkite nustatymus." - "Nepavyksta atsisiųsti pranešimų lėktuvo režimu" - "Pranešimo nepavyko atsisiųsti" - "Nulis" - "Vienas" - "Du" - "Trys" - "Keturi" - "Penki" - "Šeši" - "Septyni" - "Aštuoni" - "Devyni" - "Nepavyko išsiųsti pranešimo; operatorius „%1$s“, %2$d klaida" - "Nepavyko išsiųsti pranešimo; operatorius nežinomas, %1$d klaida" - "Pers.: %s" - "Pranešimas neišsiųstas: paslauga nesuaktyvinta tinkle" - "Pranešimas neišsiųstas: netinkamas paskirties adresas" - "Pranešimas neišsiųstas: netinkamas pranešimas" - "Pranešimas neišsiųstas: nepalaikomas turinys" - "Pranešimas neišsiųstas: nepalaikomas pranešimas" - "Pranešimas neišsiųstas: per didelis" - "Naujas pranešimas" - "Peržiūrėti" - "Vaizdas" - "Nepavyko rasti tinkamos programos" - "Pašalinti gavėją" - "Naujas pranešimas" - "Atšaukti" - "Redaguoti prieigos tašką" - "Nenustatyta" - "Pavadinimas" - "APN" - "MMSC" - "MMS tarpinis serveris" - "MMS prievadas" - "MCC" - "MNC" - "APN tipas" - "Ištrinti APN" - "Naujas APN" - "Išsaugoti" - "Atmesti" - "Pavadinimo laukas negali būti tuščias." - "APN laukas negali būti tuščias." - "MCC lauke turi būti įvesti 3 skaitmenys." - "MNC lauke turi būti įvesti 2 ar 3 skaitmenys." - "Atkuriami numatytieji APN nustatymai." - "Iš naujo nustatyti numatytuosius nustatymus" - "Numatytųjų APN nustatymų nustatymas iš naujo baigtas." - "Be pavadinimo" - "Prieigos taškų pavadinimai" - "APN" - "Naujas APN" - "Prieigos taško pavadinimo nustatymai šiam naudotojui nepasiekiami" - "Kopijuoti į iškarpinę?" - "Kopijuoti" - %s" - "Bendrieji" - "Išplėstiniai" - "Bendrieji nustatymai" - "Išplėstiniai nustatymai" - "„%s“ SIM" - "Siųsti individualius SMS pranešimus visiems gavėjams. Atsakymus gausite tik jūs" - "Siųsti vieną MMS visiems gavėjams" - "Nežinomas numeris" - "Naujas pranešimas" - "Naujas pranešimas." - "SIM pasirinkimas" - "Pasirinkta: %1$s, SIM pasirinkimas" - "Redaguoti temą" - "Pasirinkite SIM arba redaguokite temą" - "Palaikykite palietę, kad įrašytumėte garso įrašą" - "Pradėti naują pokalbį" - "Susirašinėjimo programa" - "Susirašinėjimo programos sąrašas" - "Susirašinėjimo programa" - "Naujas pranešimas" - "Pokalbių sąrašas" - "Įkeliami pokalbiai" - "Įkeliami pranešimai" - "Peržiūrėti daugiau pokalbių" - "Peržiūrėti daugiau pranešimų" - "Pokalbis ištrintas" - "Pokalbis ištrintas. Palieskite, kad būtų rodomas kitas Susirašinėjimo programos pokalbis." - "Užblokuota" - "Atblokuota" - "Mažai laisvos saugyklos vietos. Kai kurie duomenys gali būti prarasti." - "Pasirinkti priedus" - "Patvirtinti pasirinkimą" - "Pasirinkta: %d" - "Pašalinkite bent vieną priedą ir bandykite dar kartą." - "Galite pabandyti siųsti pranešimą, bet gali nepavykti jo pristatyti, nebent pašalinsite bent vieną priedą." - "Viename pranešime galite siųsti tik vieną vaizdo įrašą. Pašalinkite papildomus vaizdo įrašus ir bandykite dar kartą." - "Susirašinėjimo programai nepavyko įkelti priedo." - "Vis tiek siųsti" - "Nepavyko pradėti pokalbio" - "%1$s (%2$s)" - "Pasirinkta %s" - diff --git a/res/values-lv/arrays.xml b/res/values-lv/arrays.xml deleted file mode 100644 index 0e130b7..0000000 --- a/res/values-lv/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "bez temata" - "bez temata" - - - "Jā" - "Nē" - "Labi" - "He-he" - "Paldies!" - "Piekrītu" - "Jauki" - "Esmu ceļā" - "Labi, vēlāk ar tevi sazināšos." - ":)" - ":(" - - diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml deleted file mode 100644 index a3195b3..0000000 --- a/res/values-lv/strings.xml +++ /dev/null @@ -1,545 +0,0 @@ - - - - - "Ziņojumapmaiņa" - "Ziņojumapmaiņa" - "Atlasiet sarunu" - "Iestatījumi" - "Nosūtīt ziņojumu" - "Pievienot pielikumu" - "Palīdzība" - "Laipni lūdzam!" - "Izlaist" - "Tālāk >" - "Tālāk" - "Iziet" - "Iestatījumi >" - "Iestatījumi" - "Lietotnei Ziņojumapmaiņa ir nepieciešama piekļuves atļauja īsziņām, kā arī lietotnēm Tālrunis un Kontaktpersonas." - "Atļaujas varat mainīt sadaļā Iestatījumi > Lietotnes > Ziņojumapmaiņa > Atļaujas." - "Atļaujas varat mainīt sadaļā Iestatījumi > Lietotnes > Ziņojumapmaiņa > Atļaujas." - "Biežāk izmantotās" - "Visas kontaktpersonas" - "Nosūtīt uz: %s" - "Uzņemt attēlus vai video" - "Izvēlieties attēlus no šīs ierīces." - "Ierakstīt audio" - "Fotoattēla izvēle" - "Saturs ir atlasīts." - "Satura atlase ir noņemta." - "Atlasīts: %d" - "attēls, %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "attēls" - "Ierakstīt audio" - "Kopīgot" - "Tikko" - "Tikko" - - %d min - %d min - %d min - - - %d stundas - %d stunda - %d stundas - - - %d dienas - %d diena - %d dienas - - - %d nedēļas - %d nedēļa - %d nedēļas - - - %d mēneši - %d mēnesis - %d mēneši - - - %d gadi - %d gads - %d gadi - - "0. klases ziņojums" - "Saglabāt" - "Ierīcē ir maz vietas. Lai atbrīvotu vietu, lietotnē Ziņojumapmaiņa tiks automātiski dzēsti vecāki ziņojumi." - "Paliek maz brīvas vietas" - "Kamēr ierīcē nebūs vairāk vietas, lietotnē Ziņojumapmaiņa, iespējams, nevarēs sūtīt vai saņemt ziņojumus." - "Maz vietas īsziņām. Iespējams, jums būs jādzēš īsziņas." - "Tālruņa numura apstiprināšana" - "Šī vienreizējā darbība ir nepieciešama, lai lietotnē Ziņojumapmaiņa tiktu pareizi piegādāti jūsu grupas ziņojumi." - "Tālruņa numurs" - "Dzēst visus ziņojumus, kas ietver multivides saturu" - "Dzēst ziņojumus, kas vecāki par %s" - "Automātiski dzēst ziņojumus, kas vecāki par: %s" - "Ignorēt" - "Vai dzēst visas ziņas, kurās ietverts multivides saturs?" - "Vai dzēst ziņojumus, kas vecāki par: %s?" - "Vai dzēst ziņojumus, kas vecāki par: %s, un ieslēgt automātisko dzēšanu?" - "%s rakstīja" - "Jūs rakstījāt" - "Ziņojums no %s" - "Jūs nosūtījāt ziņojumu" - "Notiek sūtīšana…" - "Neizdevās nosūtīt. Pieskarieties, lai mēģinātu vēlreiz." - "Neizdevās nosūtīt. Notiek atkārtots mēģinājums…" - "Sūtiet vēlreiz vai dzēsiet vienumu" - "Veiciet balss zvanu uz avārijas dienesta tālruņa numuru. Pašlaik nevar nosūtīt īsziņu." - "Neizdevās" - "Jauna lejupielādējama multiziņa" - "Jauna multiziņa" - "Nevarēja lejupielādēt" - "Pieskarieties, lai mēģinātu vēlreiz" - "Pieskarieties, lai lejupielādētu" - "Lejupielādējiet vai dzēsiet" - "Notiek lejupielāde…" - "Ziņojums nav pieejams, vai ir beidzies tā derīguma termiņš" - "lielums: %1$s; derīgs līdz: %2$s" - "Nevar nosūtīt ziņojumu. Adresāts nav derīgs." - "Pakalpojums tīklā nav aktivizēts." - "Nevarēja nosūtīt tīkla problēmas dēļ." - "Ziņa nav pieejama, vai ir beidzies tās derīguma termiņš." - "(Bez temata)" - "Nezināms sūtītājs" - "Piegādāts" - "Nevarēja lejupielādēt no sūtītāja %2$s saņemto ziņojumu “%1$s”." - "Nevarēja pabeigt datu bāzes darbību, jo nav vietas atmiņā." - "Ziņojums netika nosūtīts." - "Lietotnē Ziņojumapmaiņa netika nosūtīti daži ziņojumi." - - %d ziņojumi %d sarunās - %d ziņojumi %d sarunā - %d ziņojumi %d sarunās - - "Ziņojums nav lejupielādēts." - "Lietotnē Ziņojumapmaiņa nav lejupielādēti daži ziņojumi." - - %d ziņojumi %d sarunās - %d ziņojumi %d sarunā - %d ziņojumi %d sarunās - - "Ziņojums netika nosūtīts uz tālruņa numuru %1$s." - "Veiciet balss zvanu uz avārijas dienesta tālruņa numuru. Pašlaik nevar nosūtīt īsziņu uz tālruņa numuru %1$s." - - %d jauni ziņojumi - %d jauns ziņojums - %d jauni ziņojumi - - "Ieslēgt" - "Kamera nav pieejama." - "Kamera nav pieejama." - "Video tveršana nav pieejama." - "Nevar saglabāt multivides failu." - "Nevar uzņemt attēlu" - "Atpakaļ" - "Arhīvs" - "Dzēst" - "Arhivēt" - "Izņemt no arhīva" - "Izslēgt paziņojumus" - "Ieslēgt paziņojumus" - "Pievienot kontaktpersonu" - "Lejupielādēt" - "Sūtīt" - "Dzēst" - "Vai dzēst šo ziņojumu?" - "Šo darbību nevar atsaukt." - "Dzēst" - - Vai dzēst šīs sarunas? - Vai dzēst šīs sarunas? - Vai dzēst šīs sarunas? - - "Dzēst" - "Atcelt" - "Kam:" - "Atlasīt vairākus attēlus" - "Apstiprināt atlasi" - "Vēl %d" - "Nevar ierakstīt audio. Mēģiniet vēlreiz." - "Nevar atskaņot audio. Mēģiniet vēlreiz." - "Nevarēja saglabāt audio. Mēģiniet vēlreiz." - "Pieskarieties un turiet." - ", " - " " - ": " - " " - "Attēls" - "Audioklips" - "Videoklips" - "Kontaktpersonas kartīte" - "Lejupielādēt" - "Atbildēt ar īsziņu" - "Atbildēt ar MMS" - "Atbildēt" - - %d dalībnieki - %d dalībnieks - %d dalībnieki - - "Es" - "Kontaktpersona ir bloķēta un arhivēta" - "Kontaktpersona ir atbloķēta un atarhivēta." - "Arhivētas: %d" - "Atcelta arhivēšana: %d" - "Paziņojumi ir izslēgti." - "Paziņojumi ir ieslēgti." - "Iestatīšana ir pabeigta. Vēlreiz pieskarieties vienumam Sūtīt." - "Lietotne Ziņojumapmaiņa ir sekmīgi iestatīta kā noklusējuma īsziņu lietotne." - - Atmest pielikumus - Atmest pielikumu - Atmest pielikumus - - "Audio pielikums" - "Atskaņot audio pielikumu" - "Pārtraukt" - "Ziņojums no lietotāja %s: %s." - "Neizdevās saņemt ziņojumu no kontaktpersonas %s: %s. Laiks: %s." - "Ziņojums no kontaktpersonas %s: %s. Laiks: %s." - "Nenosūtīts ziņojums kontaktpersonai %s: %s. Laiks: %s." - "Notiek ziņojuma sūtīšana kontaktpersonai %s: %s. Laiks: %s." - "Neizdevās nosūtīt ziņojumu kontaktpersonai %s: %s. Laiks: %s." - "Ziņojums kontaktpersonai %s: %s. Laiks: %s." - "Neizdevās saņemt ziņojumu no kontaktpersonas %s: %s. Laiks: %s. %s." - "Ziņojums no kontaktpersonas %s: %s. Laiks: %s. %s." - "Nenosūtīts ziņojums grupai %s: %s. Laiks: %s." - "Notiek ziņojuma sūtīšana grupai %s: %s. Laiks: %s." - "Neizdevās nosūtīt ziņojumu grupai %s: %s. Laiks: %s." - "Ziņojums grupai %s: %s. Laiks: %s." - "Radās problēma ar ziņojumu. Pieskarieties, lai mēģinātu vēlreiz." - "Saruna ar grupu: %s" - "Dzēst tēmu" - "Uzņemt video" - "Uzņemt fotoattēlu" - "Uzņemt attēlu" - "Sākt video ierakstīšanu" - "Pārslēgties uz pilnekrāna kameru" - "Pārslēgties no priekšējās kameras uz aizmugurējo kameru un otrādi" - "Pārtraukt ierakstīšanu un pievienot videoklipu" - "Pārtraukt videoklipa ierakstīšanu" - "Fotoattēli lietotnē Ziņojumapmaiņa" - - %d fotoattēli tika saglabāti albumā “%s - %d fotoattēls tika saglabāts albumā “%s - %d fotoattēli tika saglabāti albumā “%s - - - %d videoklipi tika saglabāti albumā “%s - %d videoklips tika saglabāts albumā “%s - %d videoklipi tika saglabāti albumā “%s - - - %d pielikumi tika saglabāti albumā “%s - %d pielikums tika saglabāts albumā “%s - %d pielikumi tika saglabāti albumā “%s - - - %d pielikumi tika saglabāti mapē “Lejupielādes” - %d pielikums tika saglabāts mapē “Lejupielādes” - %d pielikumi tika saglabāti mapē “Lejupielādes” - - - Tika saglabāti %d pielikumi. - Tika saglabāts %d pielikums. - Tika saglabāti %d pielikumi. - - - Nevarēja saglabāt %d pielikumus. - Nevarēja saglabāt %d pielikumu. - Nevarēja saglabāt %d pielikumus. - - "Multiziņas pielikums tika saglabāts." - "Iestatījumi" - "Arhīvs" - "Aizvērt" - "Multiziņa" - "Papildu" - "Atkļūdot" - "Paziņojumi" - "Signāls" - "Klusums" - "Vibrozvans" - "Bloķēts" - "Īsziņu piegādes atskaites" - "Pieprasiet piegādes atskaiti par katru sūtīto īsziņu." - "Automātiski izgūt" - "Automātiski izgūstiet multiziņas." - "Autom. izgūšana viesabon. laikā" - "Automātiska multiziņu izgūšana viesabonēšanas laikā" - "Grupas ziņojumapmaiņa" - "Izmantojiet multiziņu, lai vairākiem adresātiem nosūtītu vienu ziņu." - "Noklusējuma īsziņu lietotne" - "Noklusējuma īsziņu lietotne" - - - "Jūsu tālruņa numurs" - "Nezināms" - "Izejošo ziņojumu signāli" - "Īsziņu izrakstīšana" - "Izrakstiet saņemto īsziņu jēldatus ārējā krātuves failā." - "Multiziņu izrakstīšana" - "Izrakstiet saņemto multiziņu jēldatus ārējā krātuves failā." - "Bezvadu brīdinājumi" - "Ziņojuma opcijas" - "Kopēt tekstu" - "Skatīt detalizētu informāciju" - "Dzēst" - "Pārsūtīt" - "Detalizēta informācija par ziņojumu" - "Veids: " - "Īsziņa" - "Multiziņa" - "No: " - "Kam: " - "Nosūtīts: " - "Saņemts: " - "Temats: " - "Lielums: " - "Prioritāte: " - "SIM: " - "Augsta" - "Parasta" - "Zema" - "SIM %s" - "Sūtītāja adrese ir slēpta" - "Ziņojumu nevar nosūtīt, kamēr tiek ielādēti pielikumi." - "Nevar ielādēt pielikumu. Mēģiniet vēlreiz." - "Tīkls nav gatavs lietošanai. Mēģiniet vēlreiz." - "Dzēst tekstu" - "Pārslēgties starp teksta un ciparu ievadīšanu" - "Pievienot vēl dalībniekus" - "Apstiprināt dalībniekus" - "Sākt jaunu sarunu" - "Atlasīt šo vienumu" - "Atskaņot videoklipu" - "Personas un opcijas" - "Atkļūdot" - "Personas un opcijas" - "Vispārīgi" - "Sarunas dalībnieki" - "Zvanīt" - "Sūtīt ziņojumu" - "Sūtiet īsziņu,<br/><small>izmantojot šo SIM: %s</small>" - - Sūtīt fotoattēlus - Sūtīt fotoattēlus - Sūtīt fotoattēlus - - - Sūtīt audio failus - Sūtīt audio failus - Sūtīt audio failus - - - Sūtīt videoklipus - Sūtīt videoklipus - Sūtīt videoklipus - - - Sūtīt kontaktpersonu kartītes - Sūtīt kontaktpersonu kartītes - Sūtīt kontaktpersonu kartītes - - - Sūtīt pielikumus - Sūtīt pielikumus - Sūtīt pielikumus - - - Nav atlasīts neviens pielikums. - %d pielikums ir gatavs nosūtīšanai. - %d pielikumi ir gatavi nosūtīšanai. - - "Sūtīt atsauksmes" - "Skatīt Google Play veikalā" - "Informācija par versiju" - "Versija %1$s" - "Atklātā pirmkoda licences" - "Paziņojumi" - "Sasniegts pielikumu ierobežojums." - "Neizdevās ielādēt pielikumu." - "Vai pievienot kontaktpersonām?" - "Pievienot kontaktpersonu" - "Temats" - "Temats: " - "%s%s" - "Notiek kontaktpersonas kartītes ielāde…" - "Nevarēja ielādēt kontaktpersonas kartīti." - "Skatiet kontaktpersonas kartīti" - - %d kontaktpersonas - %d kontaktpersona - %d kontaktpersonas - - "Kontaktpersonu kartītes" - "Dzimšanas diena" - "Piezīmes" - "Ziņojuma pārsūtīšana" - "Atbildēt" - "+1" - "+%d" - "Īsziņas ir atspējotas." - "Lai sūtītu īsziņas, iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni." - "Iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni." - "Mainīt" - "Lai saņemtu īsziņas, iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni." - "Īsziņu sūtīšanai nav atlasīta vēlamā SIM karte" - "Ierīces īpašnieks nav atļāvis šīs lietotnes darbību." - "Labi" - "Sarunā ir pārāk daudz dalībnieku." - - Nederīgas kontaktpersonas - Nederīga kontaktpersona - Nederīgas kontaktpersonas - - "Nevarēja ielādēt kameras attēlu." - "Jūs: " - "%s: " - "Melnraksts" - "Jaunas sarunas tiks rādītas šeit." - "Šeit tiek rādītas arhivētās sarunas." - "Notiek sarunu ielāde..." - "Attēls" - "Audioklips" - "Videoklips" - "Kontaktpersonas kartīte" - "Multiziņa" - "Atsaukt" - "Mēģināt vēlreiz" - "Ievadiet kontaktpersonas vārdu vai tālruņa numuru, lai sāktu rakstīt jaunu ziņojumu." - "Bloķēt" - "Bloķēt: %s" - "Atbloķēt: %s" - "Vai bloķēt %s?" - "Jūs turpināsiet saņemt ziņojumus no šī numura, taču vairs nesaņemsiet par tiem paziņojumus. Šī saruna tiks arhivēta." - "Bloķētās kontaktpersonas" - "ATBLOĶĒT" - "Bloķētās kontaktpersonas" - "Izvēlēties attēlu no dokumentu bibliotēkas" - "Notiek ziņojuma sūtīšana" - "Ziņojums nosūtīts" - "Mobilo datu savienojums ir izslēgts. Pārbaudiet iestatījumus." - "Lidojuma režīmā nevar nosūtīt ziņojumus." - "Ziņojumu nevarēja nosūtīt." - "Ziņojums lejupielādēts" - "Mobilo datu savienojums ir izslēgts. Pārbaudiet iestatījumus." - "Ziņojumus nevar lejupielādēt lidojuma režīmā" - "Ziņojumu nevarēja lejupielādēt" - "Nulle" - "Viens" - "Divi" - "Trīs" - "Četri" - "Pieci" - "Seši" - "Septiņi" - "Astoņi" - "Deviņi" - "Nevar nosūtīt ziņojumu: operatora %1$s kļūda %2$d" - "Nevar nosūtīt ziņojumu: nezināma operatora kļūda %1$d" - "Pārs.: %s" - "Ziņojums netika nosūtīts: pakalpojums nav aktivizēts tīklā." - "Ziņojums nav nosūtīts: nederīga galamērķa adrese" - "Ziņojums nav nosūtīts: nederīgs ziņojums" - "Ziņojums nav nosūtīts: neatbalstīts saturs" - "Ziņojums nav nosūtīts: neatbalstīts ziņojums" - "Ziņojums nav nosūtīts: pārāk liels" - "Jauns ziņojums" - "Skatīt" - "Attēls" - "Neizdevās atrast atbilstošu lietojumprogrammu." - "Noņemt adresātu" - "Jauns ziņojums" - "Atcelt" - "Piekļuves punkta rediģēšana" - "Nav iestatīts" - "Nosaukums" - "APN" - "MMSC" - "Multiziņu starpniekserveris" - "Multiziņu ports" - "MCC" - "MNC" - "APN veids" - "Dzēst APN" - "Jauns APN" - "Saglabāt" - "Atmest" - "Nosaukuma lauks nedrīkst būt tukšs." - "APN lauks nedrīkst būt tukšs." - "MCC laukā ir jābūt 3 cipariem." - "MNC laukā ir jābūt 2 vai 3 cipariem." - "Notiek APN noklusējuma iestatījumu atjaunošana." - "Atiestatīt noklusējuma vērtības" - "Noklusējuma APN iestatījumu atiestatīšana ir pabeigta." - "Bez nosaukuma" - "Piekļuves punktu nosaukumi" - "APN" - "Jauns APN" - "Piekļuves punkta nosaukuma iestatījumi šim lietotājam nav pieejami." - "Vai kopēt starpliktuvē?" - "Kopēt" - "šajā SIM: %s" - "Vispārīgi" - "Papildu" - "Vispārīgi iestatījumi" - "Papildu iestatījumi" - "SIM kartes “%s” iestatījumi" - "Sūtīt atsevišķas īsziņas visiem adresātiem (atbildes saņemsiet tikai jūs)" - "Sūtīt vienu multiziņu visiem adresātiem" - "Nezināms numurs" - "Jauns ziņojums" - "Jauns ziņojums" - "SIM kartes atlasītājs" - "Atlasīta %1$s, SIM kartes atlasītājs" - "Rediģēt tematu" - "Atlasīt SIM karti vai rediģēt tematu" - "Pieskarieties un turiet, lai ierakstītu skaņu." - "Sākt jaunu sarunu" - "Ziņojumapmaiņa" - "Sarunu saraksts lietotnē Ziņojumapmaiņa" - "Ziņojumapmaiņa" - "Jauns ziņojums" - "Sarunu saraksts" - "Notiek sarunu ielāde" - "Notiek ziņojumu ielāde." - "Skatīt citas sarunas" - "Skatīt vairāk ziņojumu" - "Saruna ir izdzēsta" - "Saruna dzēsta. Pieskarieties, lai skatītu citu sarunu lietotnē Ziņojumapmaiņa." - "Bloķēts" - "Atbloķēts" - "Krātuvē ir maz vietas. Daļa datu var tikt zaudēta." - "Pielikumu atlasīšana" - "Apstiprināt atlasi" - "Atlasīti: %d" - "Lūdzu, noņemiet vienu vai vairākus pielikumus un mēģiniet vēlreiz." - "Varat mēģināt sūtīt ziņojumu, taču tas, iespējams, netiks piegādāts, ja vien nenoņemsiet vienu vai vairākus pielikumus." - "Ziņojumam var pievienot tikai vienu videoklipu. Lūdzu, noņemiet liekos videoklipus un mēģiniet vēlreiz." - "Lietotnē Ziņojumapmaiņa neizdevās ielādēt pielikumu." - "Tik un tā sūtīt" - "Nevarēja sākt sarunu" - "%1$s (%2$s)" - "Atlasīts: %s" - diff --git a/res/values-mk-rMK/arrays.xml b/res/values-mk-rMK/arrays.xml deleted file mode 100644 index eba8db7..0000000 --- a/res/values-mk-rMK/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "без наслов" - "без тема" - - - "Да" - "Не" - "Во ред" - "Хехе" - "Фала" - "Се согласувам" - "Убаво" - "На пат сум" - "Во ред, ќе ти јавам подоцна" - ":)" - ":(" - - diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml deleted file mode 100644 index 98f871e..0000000 --- a/res/values-mk-rMK/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Пораки" - "Пораки" - "Изберете разговор" - "Поставки" - "Испрати порака" - "Додај прилог" - "Помош" - "Добре дојдовте" - "Прескокни" - "Следно >" - "Следно" - "Излези" - "Поставки >" - "Поставки" - "На Messaging му треба дозвола за СМС, телефон и контакти." - "Дозволите може да ги промените во Поставки > Апликации > Messaging > Дозволи." - "Дозволите може да ги промените во Поставки, Апликации, Messaging, Дозволи." - "Чести" - "Сите контакти" - "Испрати на %s" - "Снимај слики или видео" - "Изберете слики од уредот" - "Снимај аудио" - "Избери фотографија" - "Медиумот е избран." - "Медиумот е неизбран." - "Избрани: %d" - "слика %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "слика" - "Снимај аудио" - "Сподели" - "Пред малку" - "Сега" - - %d мин. - %d мин. - - - %d час - %d часа - - - %d ден - %d дена - - - %d седмица - %d седмици - - - %d месец - %d месеци - - - %d година - %d години - - "Порака 0 класа" - "Зачувај" - "Уредот нема доволно меморија. Messaging автоматски ќе ги брише постарите текстуални пораки за да ослободи простор." - "Меморијата е речиси полна" - "Messaging може да не испраќа или прима пораки сѐ додека не се ослободи повеќе простор на уредот." - "Малку простор за складирање СМС. Можеби ќе треба да избришете пораки." - "Потврдете го својот телефонски број" - "Со овој еднократен чекор ќе се погрижите Messaging правилно да ви ги испорачува групните пораки." - "Телефонски број" - "Избриши ги сите пораки со медиуми" - "Избриши ги пораките постари од %s" - "Автоматско бришење пораки постари од %s" - "Игнорирај" - "Да се избришат сите пораки со медиуми?" - "Да се избришат пораките постари од %s?" - "Да се избришат пораките постари од %s и да се вклучи автоматското бришење?" - "%s рече" - "Рековте" - "Порака од %s" - "Испративте порака" - "Се испраќа..." - "Не се испрати. Допрете за да се обидете повторно." - "Не се испрати. Се обидуваме повторно…" - "Испратете повторно или избришете" - "Направете гласовен повик до службите за итни случаи. Вашата текстуална порака не може да се испорача во ова време." - "Неуспешно" - "Нова ММС-порака за преземање" - "Нова ММС-порака" - "Не можеше да се преземе" - "Допрете да се обидете повторно" - "Допри за преземање" - "Преземете или избришете" - "Се презема..." - "Пораката е истечена или не е достапна" - "големина: %1$s, истекување: %2$s" - "Не може да се испрати. Примачот не е важечки." - "Услугата не е активирана на мрежата" - "Не можеше да се испрати поради проблем со мрежата" - "Пораката е истечена или не е достапна" - "(Без наслов)" - "Непознат испраќач" - "Доставено" - "Пораката %1$s од %2$s не може да се преземе." - "Не може да се заврши дејството во базата со податоци поради мала меморија" - "Пораката не се испрати" - "Некои пораки не се испратени во Messaging" - - %d пораки во %d разговор - %d пораки во %d разговори - - "Пораката не е преземена" - "Некои пораки не се преземени во Messaging" - - %d пораки во %d разговор - %d пораки во %d разговори - - "Пораката до %1$s не се испрати" - "Направете гласовен повик до службите за итни случаи. Вашата текстуална порака до %1$s не може да се испорача во ова време." - - %d нова порака - %d нови пораки - - "Започни" - "Нема фотоапарат на располагање" - "Нема фотоапарат на располагање" - "Снимањето видео не е достапно" - "Медиумите не може да се зачуваат" - "Не може да фотографира" - "Назад" - "Архивирано" - "Бришење" - "Архивирање" - "Извади од архива" - "Исклучи известувања" - "Вклучи известувања" - "Додај контакт" - "Преземи" - "Испрати" - "Избриши" - "Да се избрише пораката?" - "Дејството не може да се врати." - "Избриши" - - Да се избрише разговоров? - Да се избришат разговориве? - - "Избриши" - "Откажи" - "До" - "Избери повеќе слики" - "Потврдете го изборот" - "+%d" - "Аудиото не може да се сними. Обидете се повторно." - "Аудиото не може да се пушти. Обидете се повторно." - "Аудиото не можеше да се зачува. Обидете се повторно." - "Допри и задржи" - ", " - " " - ": " - " " - "Слика" - "Аудиоклип" - "Видео" - "Картичка за контакти" - "Преземи" - "Одговори преку СМС" - "Одговори преку ММС" - "Одговори" - - %d учесник - %d учесници - - "Јас" - "Контактот е блокиран и архивиран" - "Одблокиран и одархивиран контакт" - "%d архивирани" - "%d извадени од архива" - "Известувањата се исклучени" - "Известувањата се вклучени" - "Сè е подготвено. Допрете Испрати повторно." - "Messaging е успешно поставен како стандардна апликација за СМС." - - Отфрли го прилогот - Отфрли ги прилозите - - "Аудио прилог" - "Репродуцирај аудио прилог" - "Пауза" - "Порака од %s: %s." - "Неуспешна порака од %s: %s. Време: %s." - "Порака од %s: %s. Време: %s." - "Неиспратена порака до %s: %s. Време: %s." - "Се испраќа порака до %s: %s. Време: %s." - "Неуспешна порака до %s: %s. Време: %s." - "Порака до %s: %s. Време: %s." - "Неуспешна порака од %s: %s. Време: %s. %s." - "Порака од %s: %s. Време: %s. %s." - "Неиспратена порака до %s: %s. Време: %s." - "Се испраќа порака до %s: %s. Време: %s." - "Неуспешна порака до %s: %s. Време: %s." - "Порака до %s: %s. Време: %s." - "Неуспешна порака. Допрете за да се обидете повторно." - "Разговор со %s" - "Избриши тема" - "Снимај видео" - "Сними фотографија" - "Фотографирај" - "Започни со снимање видео" - "Префрлете на камера на цел екран" - "Префрли меѓу предна и задна камера" - "Престани со снимање и прикачи видео" - "Прекини со снимање видео" - "Фотографии во Messaging" - - %d фотографија се зачува во албумот „%s - %d фотографии се зачуваа во албумот „%s - - - %d видео се зачува во албумот „%s - %d видеа се зачуваа во албумот „%s - - - %d прилог се зачува во албумот „%s - %d прилози се зачуваа во албумот „%s - - - %d прилог се зачува во „Преземања“ - %d прилози се зачуваа во „Преземања“ - - - Зачуван е %d прилог - Зачувани се %d прилози - - - Не можеше да се зачува %d прилог - Не можеше да се зачуваат %d прилози - - "Зачуван ММС-прилог" - "Поставки" - "Архивирано" - "Затвори" - "ММС" - "Напредни" - "Отстрани грешка" - "Известувања" - "Звук" - "На тивко" - "Вибрации" - "Блокирано" - "Извештаи за испорака на СМС" - "Побарајте извештај за испорака за секоја СМС што ја испраќате" - "Автоматско преземање" - "Преземај ММС-пораки автоматски" - "Автоматско преземање во роаминг" - "Автоматски преземај MMS додека си во роаминг" - "Групно испраќање пораки" - "Користи ММС за испраќање една порака кога има повеќе примачи" - "Стандардна СМС апликација" - "Стандардна СМС апликација" - - - "Вашиот телефонски број" - "Непознато" - "Звуци на појдовни пораки" - "Извади СМС" - "Извади ги примените необработени податоци за СМС на надворешна датотека за складирање" - "Извади ММС" - "Извади ги примените необработени податоци за ММС на надворешна датотека за складирање" - "Безжични предупредувања" - "Опции на порака" - "Копирај текст" - "Прикажи детали" - "Избриши" - "Проследи" - "Детали на порака" - "Тип: " - "Текстуална порака" - "Мултимедијална порака" - "Од: " - "До: " - "Испратено: " - "Примени: " - "Предмет: " - "Големина: " - "Приоритет: " - "СИМ: " - "Високо" - "Нормално" - "Ниско" - "СИМ %s" - "Скриена адреса на испраќачот" - "Не може да се испрати порака додека се вчитуваат прилозите." - "Прилогот не може да се вчита. Обидете се повторно." - "Мрежата не е подготвена. Обидете се повторно." - "Избриши текст" - "Префрлајте меѓу внесување текст и броеви" - "Додај повеќе учесници" - "Потврди учесници" - "Започнете нов разговор" - "Изберете ја ставкава" - "Пушти видео" - "Луѓе и опции" - "Отстрани грешка" - "Луѓе и опции" - "Општи" - "Луѓе во овој разговор" - "Повикај" - "Испрати порака" - "Испратете порака <br/><small>од %s</small>" - - Испрати ја фотографијата - Испрати ги фотографиите - - - Испрати го аудиото - Испрати ги аудиата - - - Испрати го видеото - Испрати ги видеата - - - Испрати ја картичката за контакт - Испрати ги картичките за контакт - - - Испрати го прилогот - Испрати ги прилозите - - - %d прилог е подготвен за испраќање - %d прилози се подготвени за испраќање - - "Испрати повратни информации" - "Прикажи во продавницата на Google Play" - "Информации за верзијата" - "Верзија %1$s" - "Лиценци со отворен код" - "Известувања" - "Достигнато е ограничувањето за прилози" - "Не успеа да се вчита прилогот." - "Да додадеме во контакти?" - "Додај контакт" - "Предмет" - "Предмет: " - "%s%s" - "Се вчитува картичката за контакт" - "Картичката за контакт не можеше да се вчита" - "Прикажи картичка за контакти" - - %d контакт - %d контакти - - "Картички за контакт" - "Роденден" - "Белешки" - "Проследи порака" - "Одговори" - "+1" - "+%d" - "СМС-пораките се оневозможени" - "За испраќање, поставете го Messaging како стандардна апликација за СМС" - "Поставете го Messaging како стандардна апликација за СМС" - "Промени" - "За примање пораки, поставете го Messaging како стандардна апликација за СМС" - "Не е избрана претпочитана СИМ за испраќање СМС-пораки" - "Оваа апликација не е дозволена од сопственикот на уредот." - "Во ред" - "Премногу учесници во разговорот" - - Неважечки контакт - Неважечки контакти - - "Слика од камерата не можеше да се вчита" - "Вие: " - "%s: " - "Нацрт" - "Штом започнете нов разговор, ќе го видите наведен тука" - "Архивираните разговори се појавуваат тука" - "Се вчитуваат разговори…" - "Слика" - "Аудиоклип" - "Видео" - "Картичка за контакти" - "ММС" - "Врати" - "Обидете се повторно" - "Внесете име на контактот или телефонски број за да започнете нова порака" - "Блокирај" - "Блокирај %s" - "Одблокирај %s" - "Да се блокира %s?" - "Ќе продолжите да добивате пораки од овој број, но веќе нема да добивате известувања. Разговорот ќе се архивира." - "Блокирани контакти" - "ОДБЛОКИРАЈ" - "Блокирани контакти" - "Изберете слика од библиотеката со документи" - "Пораката се испраќа" - "Пораката е испратена" - "Мобилните податоци се исклучени. Проверете ги поставките." - "Не може да се испраќаат пораки во авионски режим" - "Пораката не може да се испрати." - "Пораката е преземeна" - "Мобилните податоци се исклучени. Проверете ги поставките." - "Не може да се преземаат пораки во авионски режим" - "Пораката не можеше да се преземе" - "Нула" - "Еден" - "Два" - "Три" - "Четири" - "Пет" - "Шест" - "Седум" - "Осум" - "Девет" - "Пораката не може да се испрати преку %1$s, грешка %2$d" - "Пораката не може да се испрати преку непознат давател на услуга, грешка %1$d" - "Препрати: %s" - "Пораката не се испрати: услугата не е активирана на мрежата" - "Пораката не е испратена: неважечка адреса на одредиштето" - "Пораката не е испратена: неважечка порака" - "Пораката не е испратена: неподдржана содржина" - "Пораката не е испратена: неподдржана порака" - "Пораката не е испратена: преголема" - "Нова порака" - "Прикажи" - "Слика" - "Не може да се најде соодветна апликација" - "Отстрани го примачот" - "Нова порака" - "Откажи" - "Уреди пристапна точка" - "Не е поставено" - "Име" - "Име на пристапна точка (APN)" - "ММСЦ" - "Прокси на ММС" - "Порта за ММС" - "MCC" - "MNC" - "Тип APN" - "Избриши APN" - "Ново APN" - "Зачувај" - "Отфрли" - "Полето за име не може да биде празно." - "APN не може да биде празна." - "Полето MNC мора да содржи 3 цифри." - "Полето MNC мора да содржи 2 или 3 цифри." - "Враќање стандардни поставки на APN." - "Ресетирај на стандардни вредности" - "Ресетирањето стандардни поставки на APN е завршено." - "Без наслов" - "Имиња на пристапни точки" - "Поставување APN" - "Ново APN" - "Поставките за името на пристапната точка не се достапни за овој корисник" - "Копирајте во таблата со исечоци?" - "Копирај" - "до %s" - "Општи" - "Напредни" - "Општи поставки" - "Напредни поставки" - "СИМ „%s“" - "Испратете поединечни СМС-пораки до сите примачи. Само вие ќе добивате одговори" - "Испратете единствена ММС-порака до сите примачи" - "Непознат број" - "Нова порака" - "Нова порака." - "Бирач на СИМ" - "%1$s е избран, бирач на СИМ" - "Уреди предмет" - "Избери СИМ или уреди предмет" - "Допри и задржи за снимање аудио" - "Започнете нов разговор" - "Messaging" - "Список на Messaging" - "Messaging" - "Нова порака" - "Список со разговори" - "Разговорите се вчитуваат" - "Се вчитуваат пораки" - "Прикажи повеќе разговори" - "Прикажи повеќе пораки" - "Конверзацијата е избришана" - "Разговорот е избришан. Допрете за да се прикаже друг разговор на Messaging" - "Блокирано" - "Одблокирано" - "Просторот за складирање е премногу мал. Некои податоци може да се изгубат." - "Изберете прилози" - "Потврдете го изборот" - "Избрани %d" - "Отстранете еден или повеќе прилози и обидете се повторно." - "Може да се обидете да ја испратите пораката, но можеби нема да се испорача, освен ако не отстраните еден или повеќе прилози." - "Може да испраќате само едно видео по порака. Отстранете ги дополнителните видеа и обидете се повторно." - "Messaging не успеа да го вчита прилогот." - "Сепак испрати" - "Не може да се започне разговор" - "%1$s (%2$s)" - "Избрана е %s" - diff --git a/res/values-ml-rIN/arrays.xml b/res/values-ml-rIN/arrays.xml deleted file mode 100644 index 7454e1f..0000000 --- a/res/values-ml-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "വിഷയമില്ല" - "വിഷയമൊന്നുമില്ല" - - - "ഉവ്വ്" - "ഇല്ല" - "ശരി" - "ഹിഹി" - "നന്ദി" - "ഞാൻ അംഗീകരിക്കുന്നു" - "കൊള്ളാം" - "മറുപടി നൽകാൻ തയ്യാറാണ്" - "ശരി, പിന്നീട് നിങ്ങളുമായി ബന്ധപ്പെടാൻ എന്നെ അനുവദിക്കുക" - ":)" - ":(" - - diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml deleted file mode 100644 index 554cac3..0000000 --- a/res/values-ml-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "സന്ദേശമയയ്‌ക്കൽ" - "സന്ദേശമയയ്‌ക്കൽ" - "സംഭാഷണം തിരഞ്ഞെടുക്കുക" - "ക്രമീകരണങ്ങൾ" - "സന്ദേശം അയയ്ക്കൂ" - "ഒരു അറ്റാച്ചുമെന്റ് ചേർക്കുക" - "സഹായം" - "സ്വാഗതം" - "ഒഴിവാക്കുക" - "അടുത്തത് >" - "അടുത്തത്" - "പുറത്തുകടക്കുക" - "ക്രമീകരണം >" - "ക്രമീകരണം" - "SMS, ഫോൺ, കോൺടാക്റ്റുകൾ എന്നിവയിലേക്ക് സന്ദേശമയയ്ക്കലിന് അനുമതി ആവശ്യമാണ്." - "ക്രമീകരണം > ആപ്സ് > സന്ദേശമയയ്ക്കൽ > അനുമതികൾ എന്നതിൽ നിങ്ങൾക്ക് അനുമതികൾ മാറ്റാവുന്നതാണ്." - "ക്രമീകരണം, ആപ്സ്, സന്ദേശമയയ്ക്കൽ, അനുമതികൾ എന്നതിൽ നിങ്ങൾക്ക് അനുമതികൾ മാറ്റാവുന്നതാണ്." - "പതിവായി കോൺടാക്‌റ്റുചെയ്യുന്നവർ" - "എല്ലാ കോൺടാക്റ്റുകളും" - "%s എന്നതിലേയ്ക്ക് അയയ്ക്കുക" - "ചിത്രങ്ങൾ അല്ലെങ്കിൽ വീഡിയോ എടുക്കുക" - "ഈ ഉപകരണത്തിൽ നിന്ന് ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കുക" - "ഓഡിയോ റെക്കോർഡുചെയ്യുക" - "ഫോട്ടോ തിരഞ്ഞെടുക്കുക" - "മീഡിയ തിരഞ്ഞെടുത്തിരിക്കുന്നു." - "മീഡിയ തിരഞ്ഞെടുത്തത് മാറ്റി." - "%d എണ്ണം തിരഞ്ഞെടുത്തു" - "ചിത്രം %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ചിത്രം" - "ഓഡിയോ റെക്കോർഡുചെയ്യുക" - "പങ്കിടുക" - "ഇപ്പോൾ" - "ഇപ്പോൾ" - - %d മിനിറ്റ് - %d മിനിറ്റ് - - - %d മണിക്കൂർ - %d മണിക്കൂർ - - - %d ദിവസം - %d ദിവസം - - - %d ആഴ്‌ച - ഒരു ആഴ്‌ച - - - %d മാസം - ഒരു മാസം - - - %d വർഷം - ഒരു വർഷം - - "ക്ലാസ് 0 സന്ദേശം" - "സംരക്ഷിക്കുക" - "ഉപകരണത്തിൽ ഇടം കുറവാണ്. ഇടം സ്വതന്ത്രമാക്കുന്നതിന് പഴയ സന്ദേശങ്ങളെ സന്ദേശമയയ്ക്കൽ സ്വയമേവ ഇല്ലാതാക്കും." - "സംഭരണയിടം കഴിഞ്ഞു" - "നിങ്ങളുടെ ഉപകരണത്തിൽ കൂടുതൽ ഇടം ലഭ്യമാകുന്നത് വരെ സന്ദേശമയയ്ക്കലിന് സന്ദേശങ്ങൾ അയയ്ക്കാനോ സ്വീകരിക്കാനോ ആയേക്കില്ല." - "SMS സ്റ്റോറേജ് കുറവാണ്. നിങ്ങൾക്ക് സന്ദേശങ്ങൾ ഇല്ലാതാക്കേണ്ടതായി വരാം." - "നിങ്ങളുടെ ഫോൺ നമ്പർ സ്ഥിരീകരിക്കുക" - "സന്ദേശമയയ്ക്കൽ നിങ്ങളുടെ ഗ്രൂപ്പ് സന്ദേശങ്ങൾ ശരിയായി വിതരണം ചെയ്യുമെന്ന് ഈ ഒറ്റത്തവണയുള്ള ഘട്ടം ഉറപ്പാക്കും." - "ഫോണ്‍‌ നമ്പര്‍‌" - "മീഡിയയുള്ള എല്ലാ സന്ദേശങ്ങളും ഇല്ലാതാക്കുക" - "%s എന്നതിലും പഴയ സന്ദേശങ്ങൾ ഇല്ലാതാക്കുക" - "%s മുമ്പുള്ള സന്ദേശങ്ങൾ യാന്ത്രികമായി ഇല്ലാതാക്കുക" - "നിരസിക്കുക" - "മീഡിയയുള്ള എല്ലാ സന്ദേശങ്ങളും ഇല്ലാതാക്കണോ?" - "%s മുമ്പുള്ള സന്ദേശങ്ങൾ ഇല്ലാതാക്കണോ?" - "%s മുമ്പുള്ള സന്ദേശങ്ങൾ ഇല്ലാതാക്കുകയും യാന്ത്രിക ഇല്ലാതാക്കൽ ഓണാക്കുകയും ചെയ്യണോ?" - "%s, പറഞ്ഞു" - "നിങ്ങൾ പറഞ്ഞു" - "%s എന്നയാളില്‍ നിന്നുള്ള സന്ദേശം" - "നിങ്ങൾ ഒരു സന്ദേശമയച്ചു" - "അയയ്‌ക്കുന്നു…" - "അയച്ചില്ല. വീണ്ടും ശ്രമിക്കാൻ സ്‌പർശിക്കുക." - "അയച്ചില്ല. വീണ്ടും ശ്രമിക്കുന്നു..." - "വീണ്ടും അയയ്ക്കുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക" - "അടിയന്തര സേവനങ്ങളിലേക്ക് വോയ്‌സ് കോൾ ചെയ്യുക. ഇപ്പോൾ നിങ്ങളുടെ ടെക്‌സ്‌റ്റ് സന്ദേശം കൈമാറാനായില്ല." - "പരാജയപ്പെട്ടു" - "ഡൗൺലോഡുചെയ്യാനുള്ള പുതിയ MMS സന്ദേശം" - "പുതിയ MMS സന്ദേശം" - "ഡൗൺലോഡുചെയ്യാനായില്ല" - "വീണ്ടും ശ്രമിക്കാൻ സ്‌പർശിക്കുക" - "ഡൗൺലോഡുചെയ്യാൻ സ്‌പർശിക്കുക" - "ഡൗൺലോഡുചെയ്യുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക" - "ഡൗൺലോഡുചെയ്യുന്നു…" - "സന്ദേശം കാലഹരണപ്പെട്ടു അല്ലെങ്കിൽ ലഭ്യമല്ല" - "വലുപ്പം: %1$s, കാലഹരണപ്പെടുന്നത്: %2$s" - "അയയ്‌ക്കാനാവില്ല. സ്വീകർത്താവ് അസാധുവാണ്." - "സേവനം നെറ്റ്‌വർക്കിൽ സജീവമാക്കിയിട്ടില്ല" - "നെറ്റ്‌വർക്ക് പ്രശ്‌നം കാരണം അയ‌യ്‌ക്കാനായില്ല" - "സന്ദേശം കാലഹരണപ്പെട്ടു അല്ലെങ്കിൽ ലഭ്യമല്ല" - "(വിഷയമൊന്നുമില്ല)" - "അയച്ചയാൾ അജ്ഞാതനാണ്" - "ഡെലിവർ ചെയ്‌തു" - "%2$s എന്നയാളിൽ നിന്നുള്ള %1$s എന്ന സന്ദേശം ഡൗൺലോഡുചെയ്യാനായില്ല." - "മെമ്മറി കുറവായതിനാൽ ഡാറ്റാബേസ് പ്രവർത്തനം പൂർത്തിയാക്കാനായില്ല" - "സന്ദേശം അയച്ചില്ല" - "സന്ദേശമയയ്ക്കലിൽ ചില സന്ദേശങ്ങൾ അയച്ചിട്ടില്ല" - - %d സംഭാഷണങ്ങളിൽ %d സന്ദേശങ്ങൾ - ഒരു സംഭാഷണത്തിൽ %d സന്ദേശങ്ങൾ - - "സന്ദേശം ഡൗൺലോഡുചെയ്‌തില്ല" - "സന്ദേശമയയ്ക്കലിൽ ചില സന്ദേശങ്ങൾ ഡൗൺലോഡ് ചെയ്തിട്ടില്ല" - - %d സംഭാഷണങ്ങളിൽ %d സന്ദേശങ്ങൾ - ഒരു സംഭാഷണത്തിൽ %d സന്ദേശങ്ങൾ - - "%1$s എന്ന നമ്പറിലേക്ക് സന്ദേശം അയച്ചില്ല" - "അടിയന്തര സേവനങ്ങളിലേക്ക് വോയ്‌സ് കോൾ ചെയ്യുക. ഇപ്പോൾ നിങ്ങളുടെ ടെക്‌സ്‌റ്റ് സന്ദേശം %1$s എന്ന നമ്പറിലേക്ക് കൈമാറാനായില്ല." - - %d പുതിയ സന്ദേശങ്ങൾ - പുതിയ സന്ദേശം - - "ആരംഭിക്കുക" - "ക്യാമറ ലഭ്യമല്ല" - "ക്യാമറ ലഭ്യമല്ല" - "വീഡിയോ എടുക്കൽ ലഭ്യമല്ല" - "മീഡിയ സംരക്ഷിക്കാനാവില്ല" - "ചിത്രമെടുക്കാനാവില്ല" - "മടങ്ങുക" - "ആർക്കൈവുചെയ്‌തവ" - "ഇല്ലാതാക്കുക" - "ആര്‍ക്കൈവുചെയ്യുക" - "ആർക്കൈവുചെയ്‌തത് മാറ്റുക" - "അറിയിപ്പുകൾ ഓഫാക്കുക" - "അറിയിപ്പുകൾ ഓണാക്കുക" - "കോൺടാക്റ്റ് ചേർക്കുക" - "ഡൗൺലോഡ്" - "അയയ്‌ക്കുക" - "ഇല്ലാതാക്കുക" - "ഈ സന്ദേശം ഇല്ലാതാക്കണോ?" - "ഈ പ്രവർത്തനം പഴയപടിയാക്കാനാകില്ല." - "ഇല്ലാതാക്കുക" - - ഈ സംഭാഷണങ്ങൾ ഇല്ലാതാക്കണോ? - ഈ സംഭാഷണം ഇല്ലാതാക്കണോ? - - "ഇല്ലാതാക്കുക" - "റദ്ദാക്കുക" - "സ്വീകര്‍ത്താവ്" - "ഒന്നിലധികം ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കുക" - "തിരഞ്ഞെടുത്തവ സ്ഥിരീകരിക്കുക" - "%d എണ്ണം കൂടി" - "ഓഡിയോ റെക്കോർഡ് ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക." - "ഓഡിയോ പ്ലേ ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക." - "ഓഡിയോ സംരക്ഷിക്കാനായില്ല. വീണ്ടും ശ്രമിക്കുക." - "സ്‌പർശിച്ച് പിടിക്കുക" - ", " - " " - ": " - " " - "ചിത്രം" - "ഓഡിയോ ക്ലിപ്പ്" - "വീഡിയോ" - "കോൺടാക്‌റ്റ് കാർഡ്" - "ഡൗൺലോഡുചെയ്യുക" - "SMS വഴി മറുപടി നൽകുക" - "MMS വഴി മറുപടി നൽകുക" - "മറുപടി നൽകുക" - - %d പങ്കെടുക്കുന്നവർ - %d പങ്കെടുക്കുന്നയാൾ - - "ഞാന്‍" - "കോൺടാക്‌റ്റ് തടഞ്ഞ് ആർക്കൈവുചെയ്‌തു" - "കോൺടാക്റ്റിനെ തടഞ്ഞതും ആർക്കൈവ് ചെയ്‌തതും മാറ്റി" - "%d എണ്ണം ആർക്കൈവുചെ‌യ്‌തു" - "%d എണ്ണം ആർക്കൈവുചെയ്‌തത് മാറ്റി" - "അറിയിപ്പുകൾ ഓഫാക്കി" - "അറിയിപ്പുകൾ ഓണാക്കി" - "എല്ലാം സജ്ജീകരിച്ചു. വീണ്ടും അയയ്‌ക്കാൻ സ്‌‌‌പർശിക്കുക." - "സ്ഥിര SMS ആപ്പായി സന്ദേശമയയ്ക്കൽ സജ്ജീകരിച്ചു." - - അറ്റാച്ച്‌മെന്റുകൾ ഉപേക്ഷിക്കുക - അറ്റാച്ച്‌മെന്റ് ഉപേക്ഷിക്കുക - - "ഓഡിയോ അറ്റാച്ചുമെന്റ്" - "ഓഡിയോ അറ്റാച്ചുമെന്റ് പ്ലേ ചെയ്യുക" - "താൽക്കാലികമായി നിർത്തുക" - "%s എന്നയാളിൽ നിന്നുള്ള സന്ദേശം : %s ." - "%s എന്നയാളിൽ നിന്നുള്ള സന്ദേശം പരാജയപ്പെട്ടു: %s. സമയം: %s." - "%s എന്നയാളിൽ നിന്നുള്ള സന്ദേശം: %s. സമയം: %s." - "%s എന്നതിലേക്ക് സന്ദേശം അയച്ചില്ല: %s. സമയം: %s." - "%s എന്നതിലേക്ക് സന്ദേശം അയയ്‌ക്കുന്നു: %s. സമയം: %s." - "%s എന്നതിലേക്കുള്ള സന്ദേശം പരാജയപ്പെട്ടു: %s. സമയം: %s." - "%s എന്നതിലേക്കുള്ള സന്ദേശം: %s. സമയം: %s." - "%s എന്നയാളിൽ നിന്നുള്ള സന്ദേശം പരാജയപ്പെട്ടു: %s. സമയം: %s. %s." - "%s എന്നയാളിൽ നിന്നുള്ള സന്ദേശം: %s. സമയം: %s. %s." - "%s എന്നതിലേക്ക് സന്ദേശം അയച്ചില്ല: %s. സമയം: %s." - "%s എന്നതിലേക്ക് സന്ദേശം അയയ്‌ക്കുന്നു: %s. സമയം: %s." - "%s എന്നതിലേക്കുള്ള സന്ദേശം പരാജയപ്പെട്ടു: %s. സമയം: %s." - "%s എന്നതിലേക്കുള്ള സന്ദേശം: %s. സമയം: %s." - "സന്ദേശം പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നതിന് സ്‌പർശിക്കുക." - "%s എന്നിവരുമായുള്ള സംഭാഷണം" - "വിഷയം ഇല്ലാതാക്കുക" - "വീഡിയോ എടുക്കുക" - "ഒരു സ്റ്റിൽ ചിത്രം എടുക്കുക" - "ചിത്രം എടുക്കുക" - "വീഡിയോ റെക്കോർഡുചെയ്യാൻ ആരംഭിക്കുക" - "പൂർണ്ണസ്ക്രീൻ ക്യാമറയിലേക്ക് മാറുക" - "മുന്നിലെയും പിന്നിലെയും ക്യാമറയ്‌ക്കിടയിൽ മാറുക" - "റെക്കോർഡുചെയ്യൽ നിർത്തി, വീഡിയോ അറ്റാച്ചുചെയ്യുക" - "വീഡിയോ റെക്കോർഡുചെയ്യൽ നിർത്തുക" - "സന്ദേശമയയ്ക്കൽ ഫോട്ടോകൾ" - - %d ഫോട്ടോകൾ \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - %d ഫോട്ടോ \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - - - %d വീഡിയോകൾ \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - %d വീഡിയോ \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - - - %d അറ്റാച്ചുമെന്റുകൾ \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - %d അറ്റാച്ച്‌മെന്റ് \"%s\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു - - - %d അറ്റാച്ചുമെന്റുകൾ \"ഡൗൺലോഡുകളി\"ലേക്ക് സംരക്ഷിച്ചു - %d അറ്റാച്ചുമെന്റ് \"ഡൗൺലോഡുകളി\"ലേക്ക് സംരക്ഷിച്ചു - - - %d അറ്റാച്ച്‌മെന്റുകൾ സംരക്ഷിച്ചു - %d അറ്റാച്ച്‌മെന്റ് സംരക്ഷിച്ചു - - - %dഅറ്റാച്ച്‌മെന്റുകൾ സംരക്ഷിക്കാനായില്ല - %d അറ്റാച്ച്‌മെന്റ് സംരക്ഷിക്കാനായില്ല - - "MMS അറ്റാച്ചുമെന്റ് സംരക്ഷിച്ചു" - "ക്രമീകരണങ്ങൾ" - "ആർക്കൈവുചെയ്‌തവ" - "അടയ്‌ക്കുക" - "MMS" - "വിപുലം" - "ഡീബഗ്" - "അറിയിപ്പുകൾ" - "ശബ്‌ദം" - "നിശബ്‌ദം" - "വൈബ്രേറ്റുചെയ്യുക" - "തടഞ്ഞിരിക്കുന്നു" - "SMS ഡെലിവറി റിപ്പോർട്ടുകൾ" - "അയയ്‌ക്കുന്ന ഓരോ SMS-നും ഒരു ഡെലിവറി റിപ്പോർട്ട് അഭ്യർത്ഥിക്കുക" - "സ്വയമേവ വീണ്ടെടുക്കുക" - "MMS സന്ദേശങ്ങൾ സ്വയമേവ ലഭ്യമാക്കുക" - "റോമിംഗിൽ ലഭ്യമാക്കുക" - "റോമിംഗിലായിരിക്കുമ്പോൾ സ്വയമേ MMS വീണ്ടെടുക്കുന്നു" - "ഗ്രൂപ്പ് സന്ദേശമയയ്‌ക്കൽ" - "ഒന്നിലധികം സ്വീകർത്താക്കൾ ഉള്ളപ്പോൾ ഒരൊറ്റ സന്ദേശം അയയ്‌ക്കാൻ MMS ഉപയോഗിക്കുക." - "സ്ഥിര SMS അപ്ലിക്കേഷൻ" - "സ്ഥിര SMS അപ്ലിക്കേഷൻ" - - - "നിങ്ങളുടെ ഫോൺ നമ്പർ" - "അജ്ഞാതം" - "ഔട്ട്‌ഗോയിംഗ് സന്ദേശ ശബ്‌ദങ്ങൾ" - "SMS നൽകുക" - "ബാഹ്യ സംഭരണ ഫയലിലേക്ക് സ്വീകരിച്ച SMS റോ ഡാറ്റ നൽകുക" - "MMS നൽകുക" - "ബാഹ്യ സംഭരണ ഫയലിലേക്ക് സ്വീകരിച്ച MMS റോ ഡാറ്റ നൽകുക" - "വയർലെസ് അലേർട്ടുകൾ" - "സന്ദേശ ഓ‌പ്ഷനുകൾ" - "ടെക്‌‌സ്‌റ്റ് പകർത്തുക" - "വിശദാംശങ്ങൾ കാണുക" - "ഇല്ലാതാക്കുക" - "കൈമാറുക" - "സന്ദേശ വിശദാംശങ്ങൾ" - "തരം: " - "ടെക്‌സ്റ്റ് സന്ദേശം" - "മൾട്ടിമീഡിയ സന്ദേശം" - "അയച്ചയാൾ: " - "സ്വീകർത്താവ്: " - "അയച്ചത്: " - "ലഭിച്ചവ: " - "വിഷയം: " - "വലുപ്പം: " - "പ്രധാനപ്പെട്ടവ: " - "SIM: " - "ഉയർന്നത്" - "സാധാരണ" - "കുറഞ്ഞവ" - "SIM %s" - "അയച്ചയാളിന്റെ വിലാസം മറച്ചിരിക്കുന്നു" - "അറ്റാച്ചുമെന്റുകൾ ലോഡുചെയ്യുന്ന സമയത്ത് സന്ദേശം അയയ്‌ക്കാനാവില്ല." - "അറ്റാച്ചുമെന്റ് ലോഡുചെയ്യാനാവില്ല. വീണ്ടും ശ്രമിക്കുക." - "നെറ്റ്‌വർക്ക് തയ്യാറായില്ല. വീണ്ടും ശ്രമിക്കുക." - "ടെക്‌സ്റ്റ് ഇല്ലാതാക്കുക" - "ടെക്സ്റ്റും നമ്പറുകളും നൽകുന്നതിനിടയിൽ മാറുക" - "കൂടുതൽ പങ്കാളികളെ ചേർക്കുക" - "പങ്കെടുക്കുന്നവരെ സ്ഥിരീകരിക്കുക" - "പുതിയ സംഭാഷണം ആരംഭിക്കുക" - "ഈ ഇനം തിരഞ്ഞെടുക്കുക" - "വീഡിയോ പ്ലേചെയ്യുക" - "ആളുകളും ഓപ്‌ഷനുകളും" - "ഡീബഗ്" - "ആളുകളും ഓപ്‌ഷനുകളും" - "പൊതുവായത്" - "ഈ സംഭാഷണത്തിലെ ആളുകൾ" - "ഒരു കോൾ ചെയ്യുക" - "സന്ദേശം അയയ്ക്കുക" - "<br/><small>%s -ൽ നിന്ന്</small> സന്ദേശമയയ്‌ക്കുക" - - ഫോട്ടോകൾ അയയ്‌ക്കുക - ഫോട്ടോ അയയ്ക്കുക - - - ഓഡിയോകൾ അയയ്‌ക്കുക - ഓഡിയോ അയയ്ക്കുക - - - വീഡിയോകൾ അയയ്‌ക്കുക - വീഡിയോ അയയ്‌ക്കുക - - - കോൺടാക്റ്റ് കാർഡുകൾ അയയ്‌ക്കുക - കോൺടാക്റ്റ് കാർഡ് അയയ്ക്കുക - - - അറ്റാച്ച്‌മെന്റുകൾ അയയ്ക്കുക - അറ്റാച്ച്‌മെന്റ് അയയ്ക്കുക - - - %d അറ്റാച്ച്‌മെന്റുകൾ അയയ്‌ക്കാൻ തയ്യാറാണ് - ഒരു അറ്റാച്ച്‌മെന്റ് അയയ്‌ക്കാൻ തയ്യാറാണ് - - "ഫീഡ്‍ബാക്ക് അയയ്ക്കുക" - "Google Play Store-ൽ കാണുക" - "പതിപ്പ് വിവരം" - "പതിപ്പ് %1$s" - "ഓപ്പൺ സോഴ്‌സ് ലൈസൻസുകൾ" - "അറിയിപ്പുകൾ" - "അറ്റാച്ചുമെന്റ് പരിധിയെത്തി" - "അറ്റാച്ചുമെന്റ് ലോഡുചെയ്യുന്നത് പരാജയപ്പെട്ടു." - "കോൺടാക്റ്റിലേക്ക് ചേർക്കണോ?" - "കോൺടാക്‌റ്റ് ചേർക്കുക" - "വിഷയം" - "വിഷയം: " - "%s%s" - "കോൺടാക്‌റ്റ് കാർഡ് ലോഡുചെയ്യുന്നു" - "കോൺടാക്‌റ്റ് കാർഡ് ലോഡുചെയ്യാനായില്ല" - "കോൺടാക്റ്റ് കാർഡ് കാണുക" - - %d കോൺടാക്റ്റുകൾ - %d കോൺടാക്റ്റ് - - "കോൺ‌ടാക്‌റ്റ് കാർഡുകൾ" - "ജന്മദിനം" - "കുറിപ്പുകള്‍" - "സന്ദേശം കൈമാറൽ" - "മറുപടി നൽകുക" - "+1 ചെയ്യുക" - "+%d" - "SMS പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു" - "അയയ്ക്കുന്നതിന്, സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക" - "സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക" - "മാറ്റുക" - "സന്ദേശങ്ങൾ സ്വീകരിക്കുന്നതിന്, സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക" - "SMS സന്ദേശങ്ങൾ അയയ്‌ക്കാൻ പ്രസ്താവിത SIM ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല" - "ഉപകരണത്തിന്റെ ഉടമ ഈ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നില്ല." - "ശരി" - "ഒരു സംഭാഷണത്തിൽ നിരവധി പേർ പങ്കെടുക്കുന്നു" - - അസാധുവായ കോൺടാക്റ്റുകൾ - അസാധുവായ കോൺടാക്‌റ്റ് - - "ക്യാമറ ചിത്രം ലോഡുചെയ്യാനായില്ല" - "നിങ്ങൾ: " - "%s: " - "ഡ്രാഫ്റ്റ്" - "ഒരു പുതിയ സംഭാഷണം ആരംഭിക്കുമ്പോൾ, ഇത് ഇവിടെ ലിസ്‌റ്റുചെയ്‌തിരിക്കുന്നത് നിങ്ങൾ കാണും" - "ആർക്കൈവുചെയ്‌ത സംഭാഷണങ്ങൾ ഇവിടെ ദൃശ്യമാകും" - "സംഭാഷണങ്ങൾ ലോഡുചെയ്യുന്നു…" - "ചിത്രം" - "ഓഡിയോ ക്ലിപ്പ്" - "വീഡിയോ" - "കോൺടാക്‌റ്റ് കാർഡ്" - "MMS" - "പഴയപടിയാക്കുക" - "വീണ്ടും ശ്രമിക്കുക" - "ഒരു പുതിയ സന്ദേശം ആരംഭിക്കാൻ കോൺടാക്‌റ്റ് പേരോ ഫോൺ നമ്പറോ നൽകുക" - "തടയുക" - "%s എന്നയാളെ തടയുക" - "%s എന്നയാളെ തടഞ്ഞത് മാറ്റുക" - "%s തടയണോ?" - "ഈ നമ്പറിൽ നിന്ന് സന്ദേശങ്ങൾ തുടർന്നും ലഭിക്കുമെങ്കിലും ഇനി അറിയിപ്പൊന്നും ലഭിക്കില്ല. ഈ സംഭാഷണം ആർക്കൈവുചെയ്യും." - "തടഞ്ഞ കോൺടാക്‌റ്റുകൾ" - "തടഞ്ഞത് മാറ്റുക" - "തടഞ്ഞ കോൺടാക്‌റ്റുകൾ" - "പ്രമാണ ലൈബ്രറിയിൽ നിന്ന് ചിത്രം തിരഞ്ഞെടുക്കുക" - "സന്ദേശം അയക്കുന്നു" - "സന്ദേശം അയച്ചു" - "സെല്ലുലാർ ഡാറ്റ ഓഫാക്കിയിരിക്കുന്നു. നിങ്ങളുടെ ക്രമീകരണങ്ങൾ പരിശോധിക്കുക." - "വിമാന മോഡിൽ സന്ദേശങ്ങൾ അയയ്‌ക്കാനാവില്ല" - "സന്ദേശം അയയ്ക്കാനായില്ല" - "സന്ദേശം ഡൗൺലോഡുചെയ്‌തു" - "സെല്ലുലാർ ഡാറ്റ ഓഫാണ്. നിങ്ങളുടെ ക്രമീകരണം പരിശോധിക്കുക." - "വിമാന മോഡിൽ സന്ദേശങ്ങൾ ഡൗൺലോഡുചെയ്യാനാവില്ല" - "സന്ദേശം ഡൗൺലോഡുചെയ്യാനായില്ല" - "പൂജ്യം" - "ഒന്ന്" - "രണ്ട്" - "മൂന്ന്" - "നാല്" - "അഞ്ച്" - "ആറ്" - "ഏഴ്" - "എട്ട്" - "ഒൻപത്" - "%1$s എന്നതിലൂടെ സന്ദേശമയയ്‌ക്കാനായില്ല, പിശക് %2$d" - "അജ്ഞാത കാരിയറിലൂടെ സന്ദേശമയയ്‌ക്കാനായില്ല, പിശക് %1$d" - "കൈമാറിയത്: %s" - "സന്ദേശം അയച്ചില്ല: നെറ്റ്‌വർക്കിൽ സേവനം സജീവമാക്കിയിട്ടില്ല" - "സന്ദേശം അയച്ചില്ല: ലക്ഷ്യസ്ഥാന വിലാസം അസാധുവാണ്" - "സന്ദേശം അയച്ചില്ല: സന്ദേശം അസാധുവാണ്" - "സന്ദേശം അയച്ചില്ല: ഉള്ളടക്കം പിന്തുണയ്ക്കാത്തതാണ്" - "സന്ദേശം അയച്ചില്ല: സന്ദേശം പിന്തുണയ്ക്കാത്തതാണ്" - "സന്ദേശം അയച്ചില്ല: വളരെ വലുതാണ്" - "പുതിയ സന്ദേശം" - "കാണുക" - "ചിത്രം" - "അനുയോജ്യമായൊരു അപ്ലിക്കേഷൻ കണ്ടെത്താനായില്ല" - "സ്വീകർത്താവിനെ നീക്കംചെയ്യുക" - "പുതിയ സന്ദേശം" - "റദ്ദാക്കുക" - "ആക്‌സസ്സ്‌പോയിന്റ് എഡിറ്റുചെയ്യൂ" - "സജ്ജീകരിച്ചിട്ടില്ല" - "പേര്" - "APN" - "MMSC" - "MMS പ്രോക്‌സി" - "MMS പോർട്ട്" - "MCC" - "MNC" - "APN തരം" - "APN ഇല്ലാതാക്കുക" - "പുതിയ APN" - "സംരക്ഷിക്കുക" - "നിരസിക്കുക" - "പേരിന്റെ ഫീൽഡ് ശൂന്യമായിരിക്കരുത്." - "APN ശൂന്യമായിരിക്കരുത്." - "MCC ഫീൽഡിൽ 3 സംഖ്യകൾ ഉണ്ടായിരിക്കണം." - "MNC ഫീൽഡിൽ 2 അല്ലെങ്കിൽ 3 സംഖ്യകൾ ഉണ്ടായിരിക്കണം." - "സ്ഥിര APN ക്രമീകരണങ്ങൾ പുനഃസ്ഥാപിക്കുന്നു" - "സ്ഥിരമായതിലേക്ക് പുനഃസജ്ജമാക്കുക" - "സ്ഥിര APN ക്രമീകരണങ്ങൾ പുനഃസജ്ജീകരിക്കൽ പൂർത്തിയാക്കി." - "പേരില്ലാത്തത്" - "ആക്‌സസ്സ് പോയിന്റ് പേരുകൾ" - "APN-കൾ" - "പുതിയ APN" - "ആക്‌സസ്സ് പോയിന്റ് നെയിം ക്രമീകരണങ്ങൾ ഈ ഉപയോക്താവിനായി ലഭ്യമല്ല" - "ക്ലിപ്പ്‌ബോർഡിലേക്ക് പകർത്തണോ?" - "പകര്‍ത്തുക" - "%s എന്നതിലേക്ക്" - "പൊതുവായത്" - "വിപുലമായത്" - "പൊതു ക്രമീകരണങ്ങൾ" - "വിപുലമായ ക്രമീകരണങ്ങൾ" - "\"%s\" SIM" - "എല്ലാ സ്വീകർത്താക്കൾക്കും വെവ്വേറെ SMS സന്ദേശങ്ങൾ അയയ്‌ക്കുക. മറുപടികളെല്ലാം നിങ്ങൾക്കുമാത്രമേ ലഭിക്കൂ" - "എല്ലാ സ്വീകർത്താക്കൾക്കും ഒരൊറ്റ MMS അയയ്‌ക്കുക" - "അജ്ഞാത നമ്പര്‍" - "പുതിയ സന്ദേശം" - "പുതിയ സന്ദേശം." - "SIM സെലക്‌ടർ" - "%1$s തിരഞ്ഞെടുത്തു, SIM സെലക്‌ടർ" - "വിഷയം എഡിറ്റുചെയ്യുക" - "SIM തിരഞ്ഞെടുക്കുക അല്ലെങ്കിൽ വിഷയം എഡിറ്റുചെയ്യുക" - "ഓഡിയോ റെക്കോർഡുചെയ്യാൻ സ്‌പർശിച്ച് പിടിക്കുക" - "പുതിയ സംഭാഷണം ആരംഭിക്കുക" - "സന്ദേശമയയ്‌ക്കൽ" - "സന്ദേശമയയ്ക്കൽ ലിസ്റ്റ്" - "സന്ദേശമയയ്‌ക്കൽ" - "പുതിയ സന്ദേശം" - "സംഭാഷണ ലിസ്റ്റ്" - "സംഭാഷണങ്ങൾ ലോഡുചെയ്യുന്നു" - "സന്ദേശങ്ങൾ ലോഡുചെയ്യുന്നു" - "കൂടുതൽ സംഭാഷണങ്ങൾ കാണുക" - "കൂടുതൽ സന്ദേശങ്ങൾ കാണുക" - "സംഭാഷണം ഇല്ലാതാക്കി" - "സംഭാഷണം ഇല്ലാതാക്കി. മറ്റൊരു സന്ദേശമയയ്ക്കൽ സംഭാഷണം കാണിക്കുന്നതിന് സ്പർശിക്കുക" - "തടഞ്ഞു" - "തടഞ്ഞത് മാറ്റി" - "സംഭരണത്തിനുള്ള ഇടം കുറവാണ്. ചില വിവരം നഷ്‌ടപ്പെട്ടേക്കാം." - "അറ്റാച്ചുമെന്റുകൾ തിരഞ്ഞെടുക്കുക" - "തിരഞ്ഞെടുത്തവ സ്ഥിരീകരിക്കുക" - "%d എണ്ണം തിരഞ്ഞെടുത്തു" - "ഒന്നോ അതിൽ കൂടുതലോ അറ്റാച്ചുമെന്റുകൾ നീക്കംചെയ്‌തുകൊണ്ട് വീണ്ടും ശ്രമിക്കുക." - "നിങ്ങളുടെ സന്ദേശം അയയ്‌ക്കാൻ ശ്രമിക്കാമെങ്കിലും ഒന്നോ അതിലധികമോ അറ്റാച്ചുമെന്റുകൾ നീക്കംചെയ്യാതെ അത് ഡെലിവർ ചെയ്തേക്കില്ല." - "നിങ്ങൾക്ക് ഒരു സന്ദേശത്തിന് ഒരു വീഡിയോ മാത്രമേ അയയ്‌ക്കാനാകൂ. കൂടുതൽ വീഡിയോകൾ നീക്കംചെയ്‌ത് വീണ്ടും ശ്രമിക്കുക." - "അറ്റാച്ച്മെന്റ് ലോഡുചെയ്യുന്നതിൽ സന്ദേശമയയ്‌ക്കൽ പരാജയപ്പെട്ടു." - "ഏതുവിധേനയും അയയ്‌ക്കുക" - "സംഭാഷണം ആരംഭിക്കാനായില്ല" - "%1$s (%2$s)" - "തിരഞ്ഞെടുത്തത്, %s" - diff --git a/res/values-mn-rMN/arrays.xml b/res/values-mn-rMN/arrays.xml deleted file mode 100644 index b47063a..0000000 --- a/res/values-mn-rMN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "гарчиггүй" - "nosubject" - - - "Тийм" - "Үгүй" - "Тийм" - "Хэхэ" - "Баярлалаа" - "Би зөвшөөрч байна" - "Янзтай" - "Очиж байна" - "За, би эргээд холбоо баръя" - ":)" - ":(" - - diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml deleted file mode 100644 index fb9106b..0000000 --- a/res/values-mn-rMN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Зурвас" - "Зурвас" - "Харилцааг сонгох" - "Тохиргоо" - "Зурвас Илгээх" - "Хавсралт нэмэх" - "Тусламж" - "Тавтай морилно уу" - "Алгасах" - "Дараах >" - "Дараах" - "Гарах" - "Тохиргоо >" - "Тохиргоо" - "Messaging-д SMS, Утас, Харилцагч хаягийн зөвшөөрөл шаардлагатай." - "Та зөвшөөрлийг Тохиргоо > Aпп > Зурвас > Зөвшөөрөл хэсэгт өөрчилж болно." - "Та зөвшөөрлийг Тохиргоо, Апп, Messaging, Зөвшөөрөл хэсэгт өөрчилж болно." - "БАЙНГЫН ХАРИЛЦАГЧ" - "Бүх харилцагч" - "%s руу илгээх" - "Зураг буюу видео авах" - "Энэ төхөөрөмжөөс зураг сонгоно уу" - "Аудио бичих" - "Зураг сонгох" - "Медиаг сонгосон байна." - "Медиаг сонгоогүй байна." - "%d сонгогдсон" - "зураг %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "зураг" - "Аудио бичих" - "Хуваалцах" - "Дөнгөж сая" - "Одоо" - - %d минут - %d минут - - - %d цаг - %d цаг - - - %d өдөр - %d өдөр - - - %d долоо хоног - долоо хоног - - - %d сар - нэг сар - - - %d жил - нэг жил - - "Ангилал 0 зурвас" - "Хадгалах" - "Төхөөрөмжийн багтаамж бага байна. Messaging нь багтаамж үүсгэхийн тулд хуучин зурвасыг автоматаар устгана." - "Сангийн хэмжээ дутагдаж байна" - "Мessaging нь таны төхөөрөмжид хангалттай багтаамж гарах хүртэл зурвас илгээх эсвэл хүлээн авах боломжгүй." - "SMS сан бага байна. Та зурвасуудаас устгах шаардлагатай бололтой." - "Утасны дугаараа баталгаажуулна уу" - "Нэг удаагийн алхам нь Messaging-г таны бүлэг зурвасыг асуудалгүйгээр илгээнэ." - "Утасны дугаар" - "Медиатай бүх зурвасуудыг устгах" - "%s-с өмнөх зурвасуудыг устгах" - "%s-с өмнөх зурвасуудыг автоматаар устгах" - "Үл тоох" - "Медиа бүхий бүх зурвасыг устгах уу?" - "%s-с өмнөх зурвасуудыг устгах уу?" - "%s-с өмнөх зурвасуудыг устгаж, автомат устгалтыг асаах уу?" - "%s гэж хэлсэн" - "Та хэлсэн байна" - "%s-ээс ирсэн мессеж" - "Та мессеж илгээсэн байна" - "Илгээж байна ..." - "Илгээгээгүй. Дахин оролдохын тулд хүрнэ үү." - "Илгээгээгүй. Дахин оролдож байна…" - "Дахин илгээх буюу устгах" - "Яаралтай түргэн тусламж руу залгана уу. Таны мессежийг яг одоо явуулах боломжгүй байна." - "Амжилтгүй" - "Татаж авах шинэ MMS мессеж" - "Шинэ MMS мессеж" - "Татан авч чадсангүй" - "Дахин оролдохын тулд хүрнэ үү" - "Татаж авахын тулд хүрнэ үү" - "Татаж авах буюу устгах" - "Татаж авч байна..." - "Зурвасын хугацаа өнгөрсөн буюу байхгүй" - "хэмжээ: %1$s, хугацаа дуусах: %2$s" - "Илгээх боломжгүй. Хүлээн авагч буруу." - "Сүлжээнд үйлчилгээг идэвхжүүлээгүй байна." - "Сүлжээний асуудлаас шалтгаалан илгээж чадсангүй." - "Зурвасын хугацаа өнгөрсөн буюу байхгүй" - "(Гарчиггүй)" - "Тодорхойгүй илгээгч" - "Хүргэгдсэн" - "%2$s%1$s зурвасыг татаж чадсангүй." - "Санах ой бага байгаагаас шалтгаалан датабаазын үйлдлийг гүйцээж чадсангүй" - "Зурвас илгээгдээгүй" - "Messaging-н зарим зурвасыг илгээгээгүй" - - %d Харилцан ярианд %d зурвас байна - Нэг харилцан ярианд %d зурвас байна - - "зурвасыг татаж болсонгүй" - "Зарим зурвасыг Messaging-д татаагүй" - - %d Харилцан ярианд %d зурвас байна - Нэг харилцан ярианд %d зурвас байна - - "%1$s явуулсан мессеж амжилтгүй болсон байна" - "Яаралтай түргэн тусламж руу залгана уу. Таны мессежийг %1$s дугаарт яг одоо явуулах боломжгүй байна. " - - %d шинэ зурвас - Шинэ зурвас - - "Эхлэх" - "Камер ашиглах боломжгүй" - "Камер ашиглах боломжгүй" - "Видео бичих боломжгүй" - "Медиаг хадгалах боломжгүй" - "Зураг авч болохгүй байна" - "Буцах" - "Архивлагдсан" - "Устгах" - "Архивлах" - "Архиваас гаргах" - "Мэдэгдэл унтраах" - "Мэдэгдлүүдийг асаах" - "Харилцагч нэмэх" - "Татаж авах" - "Илгээх" - "Устгах" - "Энэ зурвасыг устгах уу?" - "Энэ үйлдлийг буцаах боломжгүй." - "Устгах" - - Эдгээр харилцан яриаг устгах уу? - Энэ харилцан яриаг устах уу? - - "Устгах" - "Цуцлах" - "Хэнд" - "Олон зураг сонгох" - "Сонголтыг баталгаажуулах" - "+%d" - "Аудиог бичих боломжгүй. Дахин оролдоно уу." - "Аудиог тоглуулах боломжгүй. Дахин оролдоно уу." - "Аудиог хадгалж чадсангүй. Дахин оролдоно уу." - "Хүрээд & барих" - ", " - " " - ": " - " " - "Зураг" - "Аудио клип" - "Видео" - "Харилцагчийн карт" - "Татаж авах" - "SMS-р хариулах" - "MMS-ээр хариулах" - "Хариулах" - - %d оролцогч - %d оролцогч - - "Би" - "Харилцагчийг хориглож & архивласан" - "Харилцаа холбоог хориглоогүй & архивлагдаагүй" - "%d aрхивлагдсан" - "%d архивлагдаагүй" - "Мэдэгдлүүдийг унтраасан" - "Мэдэгдлүүдийг асаасан" - "Бүгдийг нь тохируулсан. Илгээх-д дахин хүрнэ үү." - "Мessaging-г SMS-н үндсэн апп-аар амжилттай тохирууллаа." - - Хавсралтыг цуцлах - Хавсралтыг цуцлах - - "Аудио хавсралт" - "Аудио хавсралтыг тоглуулах" - "Түр зогсоох" - "%s: %s-аас ирсэн зурвас." - "%s-с илгээсэн амжилтгүй болсон мессеж: %s. Цаг: %s." - "%s-с илгээсэн мессеж: %s. Цаг: %s." - "%s-д илгээгээгүй байгаа мессеж: %s. Цаг: %s." - "%s-д мессеж илгээж байна: %s. Цаг: %s." - "%s-д илгээсэн амжилтгүй болсон мессеж: %s. Цаг: %s." - "%s-д мессеж илгээх: %s. Цаг: %s." - "%s-с илгээсэн амжилтгүй болсон мессеж: %s. Цаг: %s. %s" - "%s-с илгээсэн мессеж: %s. Цаг: %s. %s." - "%s-т зориулсан илгээгээгүй байгаа мессеж: %s. Цаг: %s." - "%s-т мессеж илгээж байна: %s. Цаг: %s." - "%s-т зориулсан амжилтгүй болсон мессеж: %s. Цаг: %s." - "%s-т илгээх мессеж: %s. Цаг: %s." - "Амжилтгүй болсон мессеж. Дахин оролдоно уу." - "%s нартай холбоо тогтоох" - "Гарчгийг устгах" - "Видео авах" - "Хөдөлгөөнгүй зураг авах" - "Зураг авах" - "Видео бичлэг хийж эхлэх" - "Бүтэн дэлгэцийн камер руу сэлгэх" - "Урд болон хойд камер хооронд сэлгэх" - "Бичлэгийг зогсоож видеог хавсаргах" - "Видео бичилтийг зогсоох" - "Messaging-н зураг" - - %d зургийг \"%s\" цомогт хадгаллаа - %d зургийг \"%s\" цомогт хадгаллаа - - - %d бичлэгийг \"%s\" цомогт хадгаллаа - %d бичлэгийг \"%s\" цомогт хадгаллаа - - - %d хавсралтыг \"%s\" цомогт хадгаллаа - %d хавсралтыг \"%s\" цомогт хадгаллаа - - - %d хавсралтыг \"Татаж авсан файл\"-д хадгаллаа - %d хавсралтыг \"Татаж aвсан файл\"-д хадгаллаа - - - %d хавсралт хадгалсан - %d хавсралт хадгалсан - - - %d хавсралтыг хадгалж чадсангүй - %d хавсралтыг хадгалж чадсангүй - - "MMS хавсралтыг хадгалсан" - "Тохиргоо" - "Архивлагдсан" - "Хаах" - "MMS" - "Дэлгэрэнгүй" - "Дебаг" - "Мэдэгдэл" - "Дуу" - "Чимээгүй" - "Чичиргээ" - "Хориглогдсон" - "SMS хүргэлтийн репорт" - "Өөрийн илгээх SMS бүрт хүргэлтийн репорт хүснэ үү" - "Автомат-татагч" - "MMS зурвасуудыг автоматаар татах" - "Роумингын автомат-татагч" - "Роуминг ашиглаж байх үедээ MMS-ийг автоматаар сэргээж авч байх" - "Бүлгэмийн зурвас" - "Олон хүлээн авагчид нэг зурвас явуулахдаа MMS ашиглаарай" - "Үндсэн SMS програм" - "Үндсэн SMS програм" - - - "Таны утасны дугаар" - "Тодорхойгүй" - "Мессеж илгээх үеийн ая" - "SMS хадгалах" - "Хүлээн авсан SMS түүхий датаг гадаад сангийн файлд хадгалах" - "MMS хадгалах" - "Dump Хүлээн авсан MMS түүхий датаг гадаад сангийн файлд хадгалах" - "Утасгүй сэрэмжлүүлэг" - "Зурвасын тохиргоо" - "Текстийг хуулах" - "Дэлгэрэнгүй үзэх" - "Устгах" - "Дамжуулах" - "Зурвасын мэдээлэл" - "Төрөл: " - "Текст зурвас" - "Мультимедиа зурвас" - "Хэнээс: " - "Хэнд: " - "Илгээсэн: " - "Хүлээн авсан: " - "Гарчиг: " - "Хэмжээ: " - "Ач холбогдол: " - "SIM: " - "Өндөр" - "Энгийн" - "Бага" - "SIM %s" - "Илгээгчийн хаяг нууцлагдсан" - "Хавсралтуудыг ачаалах үед зурвас илгээх боломжгүй." - "Хавсралтыг ачаалж чадсангүй. Дахин оролдоно уу." - "Сүлжээ бэлэн биш байна. Дахин оролдоно уу." - "Текстийг устгах" - "Текст болон тоо оруулах хооронд сэлгэх" - "Өөр оролцогчид нэмэх" - "Оролцогчдыг баталгаажуулах" - "Шинээр харилцан яриа үүсгэж байна" - "Энэ зүйлийг сонгох" - "Видео тоглуулах" - "Хүмүүс & тохируулга" - "Дебаг" - "Хүмүүс & тохируулга" - "Ерөнхий" - "Энэ харилцаан доторх хүмүүс" - "Дуудлага хийх" - "Зурвас илгээх" - "Зурвас илгээх<br/><small>%s-с</small>" - - Зураг илгээх - Зураг илгээх - - - Аудио илгээх - Аудио илгээх - - - Видео илгээх - Видео илгээх - - - Харилцагчийн карт илгээх - Харилцагчийн карт илгээх - - - Хавсралт илгээх - Хавсралт илгээх - - - Илгээхэд бэлэн %d хавсралт байна - Илгээхэд бэлэн нэг хавсралт байна - - "Санал хүсэлт илгээх" - "Google Play дэлгүүр дээр харах" - "Хувилбарын мэдээлэл" - "Хувилбар %1$s" - "Нээлттэй эхийн лиценз" - "Мэдэгдэл" - "Хавсралтын дээд хэмжээнд хүрсэн байна" - "Хавсралтыг татаж авах үйлдэл амжилтгүй болсон байна." - "Харилцагчидад нэмэх үү?" - "Харилцагч нэмэх" - "Гарчиг" - "Гарчиг: " - "%s%s" - "Харилцагчийн картыг ачаалж байна" - "Харилцагчийн картыг дуудаж чадсангүй" - "Харилцагчийн картыг харах" - - %d харилцагч - %d харилцагч - - "Харилцагчийн картууд" - "Төрсөн өдөр" - "Тэмдэглэл" - "Зурвасыг дамжуулах" - "Хариулах" - "+1" - "+%d" - "SMS идэвхгүй" - "Илгээхийн тулд Messaging-г үндсэн SMS апп болго" - "Messaging-г үндсэн SMS апп болгох" - "Солих" - "Зурвас хүлээн авахын тулд Messaging-г үндсэн SMS апп-аар тохируулаарай" - "SMS зурвас илгээхээр сонгосон ямар ч SIM алга байна" - "Энэ апп-г төхөөрөмжийн эзэмшигч зөвшөөрөөгүй." - "ТИЙМ" - "Харилцаанд хэт олон оролцогч байна" - - Харилцагчийн хаяг буруу байна - Харилцагчийн хаяг буруу байна - - "Камерийн дүрсийг дуудаж чадсангүй" - "Та: " - "%s: " - "Ноорог" - "Шинэ харилцаа эхлүүлмэгц энэ жагсаалтад харагдах болно" - "Архивлагдсан харилцаанууд энд харагдана" - "Харилцааг ачаалж байна..." - "Зураг" - "Аудио клип" - "Видео" - "Харилцагчийн карт" - "MMS" - "Буцаах" - "Дахин оролдох" - "Шинэ зурвас эхлүүлэхийн тулд харилцагчийн нэр буюу утасны дугаар оруулна уу" - "Хориглох" - "%s-г хориглох" - "%s-н хориг авах" - "%s-г хориглох уу?" - "Та энэ дугаараас үргэлжлүүлэн зурвас хүлээн авах боловч мэдэгдэл авахгүй. Энэ харилцааг архивлах болно." - "Хориглогдсон харилцагчид" - "ХОРИГ АВАХ" - "Хориглогдсон харилцагчид" - "Документ сангаас зураг сонгох" - "Зурвасыг илгээж байна" - "Зурвас илгээгдсэн" - "Үүрэн дата унтарсан. Тохиргоог шалгана уу." - "Нислэгийн горимд зурвас илгээх боломжгүй" - "Зурвасыг илгээж чадсангүй." - "Зурвасыг татаж авсан" - "Гар утасны интернет холболт идэвхгүй байна. Тохиргоог дахин шалгана уу." - "Airplane mode дээр тавьсан байгаа тул мэдээллийг татаж авах боломжгүй байна" - "Мэдээллийг татаж авах боломжгүй байна" - "Тэг" - "Нэг" - "Хоёр" - "Гурав" - "Дөрөв" - "Тав" - "Зургаа" - "Долоо" - "Найм" - "Ес" - "%2$d алдаа бүхий %1$s мессежийг илгээх боломжгүй" - "Үл мэдэгдэх зөөвөрлөгчөөр ирсэн мессежийг илгээх боломжгүй, алдаа %1$d" - "Fwd: %s" - "Зурвас илгээгдээгүй: үйлчилгээ сүлжээн дээр идэвхжээгүй" - "Зурвас илгээгдээгүй: чиглэлийн хаяг буруу" - "Зурвас илгээгдээгүй: хүчингүй зурвас" - "Зурвас илгээгдээгүй: дэмжигдээгүй контент" - "Зурвас илгээгдээгүй: дэмжигдээгүй зурвас" - "Мессежийг илгээж болохгүй байна: хэтэрхий их хэмжээтэй байна" - "Шинэ зурвас" - "Харагдац" - "Зураг" - "Тохирох аппликешныг олж чадсангүй" - "Хүлээн авагчийг арилгах" - "Шинэ зурвас" - "Цуцлах" - "Хандалтын цэгийг засах" - "Тохируулаагүй" - "Нэр" - "APN" - "MMSC" - "MMS прокси" - "MMS порт" - "MCC" - "MNC" - "APN төрөл" - "APN устгах" - "Шинэ APN" - "Хадгалах" - "Устгах" - "Нэр оруулах талбар хоосон байж болохгүй." - "APN хоосон байж болохгүй." - "MCC талбар 3 цифртэй байх ёстой." - "MNC талбар 2 буюу 3 цифртэй байх ёстой." - "Үндсэн APN тохиргоог сэргээж байна" - "Үндсэн рүү сэргээх" - "Үндсэн APN тохиргоог дахин шинэчилж дууслаа." - "Гарчиггүй" - "Хандалтын Цэгийн Нэрс" - "APNs" - "Шинэ APN" - "Энэ хэрэглэгчийн хувьд Хандалтын цэгийн нэрийн тохиргоог ашиглах боломжгүй" - "Санах ойд хуулах уу?" - "Хуулах" - "%s руу" - "Ерөнхий" - "Дэлгэрэнгүй" - "Ерөнхий тохиргоо" - "Дэлгэрэнгүй тохиргоо" - "\" %s \"SIM" - "Бүх хүлээн авагч руу хувийн SMS мессеж илгээх. Зөвхөн та ямар ч хариу авахгүй" - "Бүх хүлээн авагч руу нэг MMS илгээх" - "Тодорхойгүй дугаар" - "Шинэ зурвас" - "Шинэ зурвас." - "SIM сонгогч" - "%1$s сонгосон, СИМ сонгогч" - "Гарчигийг засах" - "SIM-г сонгох эсвэл гарчигийг засах" - "Дуут бичлэг хийхийн тулд холбогдох хэсэгт даран түр хүлээнэ үү" - "Шинээр харилцан яриа үүсгэх" - "Messaging" - "Messaging-н жагсаалт" - "Messaging" - "Шинэ мэдээлэл" - "Харилцааны жагсаалт" - "Харилцан солилцсон мэдээллийг ачаалж байна" - "Зурвасуудыг дуудаж байна" - "Харилцан яриаг дэлгэрэнгүй харах" - "Илүү олон зурвасуудыг харах" - "Харилцаа холбоог устгасан байна" - "Харилцан яриа устсан. Messaging-н өөр харилцан яриа харуулахын тулд хүрнэ үү" - "Блоклосон байна" - "Блокыг арилгасан байна" - "Таны мэдээлэл хадгалах зай хангалтгүй байна. Зарим мэдээлэл алдагдах магадлалтай байна." - "Хавсралтыг сонгох" - "Сонголтыг баталгаажуулах" - "%d сонгосон" - "Нэг эсвэл түүнээс дээш тооны хавсралтыг арилгасны дараагаар дахин оролдоно уу." - "Та өөрийн мессежийг илгээх оролдлогыг хийж болох боловч нэг эсвэл түүнээс дээг тооны хавсралтыг арилгахаас нааш илгээгдэхгүй байх магадлалтай." - "Та нэг мессежээр нэг л видео илгээж болно. Нэмэлт видеонуудыг устгаад, дахин оролдоно уу." - "Messaging нь хавсралтыг ачаалж чадсангүй." - "Юутай ч илгээх" - "Харилцан яриаг эхэлж болохгүй байна" - "%1$s (%2$s)" - "%s сонгогдсон" - diff --git a/res/values-mr-rIN/arrays.xml b/res/values-mr-rIN/arrays.xml deleted file mode 100644 index 73cd4c2..0000000 --- a/res/values-mr-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "कोणताही विषय नाही" - "कोणताही विषय नाही" - - - "होय" - "नाही" - "ठीक आहे" - "हेहे" - "धन्यवाद" - "मी सहमत आहे" - "छान" - "मी वाटेत आहे" - "ठीक, नंतर मला आपल्याकडे परत येऊ द्या" - ":)" - ":(" - - diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml deleted file mode 100644 index 418199c..0000000 --- a/res/values-mr-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "संदेशन" - "संदेशन" - "संभाषण निवडा" - "सेटिंग्ज" - "संदेश पाठवा" - "एखादे संलग्नक जोडा" - "मदत" - "सुस्वागतम" - "वगळा" - "पुढील >" - "पुढील" - "बाहेर पडा" - "सेटिंग्ज >" - "सेटिंग्ज" - "संदेशनला SMS, फोन आणि संपर्कांसाठी परवानगी आवश्यक आहे." - "आपण सेटिंग्ज > अॅप्स > संदेशन > परवानग्या मध्ये परवानग्या बदलू शकता." - "आपण सेटिंग्ज, अॅप्स, संदेशन, परवानग्या मध्ये परवानग्या बदलू शकता." - "वारंवार" - "सर्व संपर्क" - "%s वर पाठवा" - "चित्रे किंवा व्हिडिओ कॅप्चर करा" - "या डिव्हाइसवरून प्रतिमा निवडा" - "ऑडिओ रेकॉर्ड करा" - "फोटो निवडा" - "मीडिया निवडण्यात आला आहे." - "मीडियाची निवड रद्द करण्यात आली आहे." - "%d निवडले" - "प्रतिमा %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "प्रतिमा" - "ऑडिओ रेकॉर्ड करा" - "सामायिक करा" - "आत्ताच" - "आत्ता" - - %d मिनि - %d मिनिटे - - - %d तास - %d तास - - - %d दिवस - %d दिवस - - - %d आठवडा - %d आठवडे - - - %d महिना - %d महिने - - - %d वर्ष - %d वर्षे - - "वर्ग 0 संदेश" - "जतन करा" - "डिव्हाइसवर स्थान कमी आहे. स्थान मोकळे करण्‍यासाठी संदेशन जुने संदेश स्वयंचलितपणे हटवेल." - "संचयन स्थान संपत आहे" - "आपल्या डिव्हाइसवर आणखी स्थान उपलब्ध होईपर्यंत संदेशन संदेश पाठवू किंवा प्राप्त करू शकत नाही." - "कमी SMS संचयन. आपल्याला संदेश हटविण्याची आवश्यकता असू शकते." - "आपल्‍या फोन नंबरची पुष्‍टी करा" - "हे एक-वेळ चरण संदेशन आपले गट संदेश योग्यरित्या वितरीत करेल हे सुनिश्चित करेल." - "फोन नंबर" - "मीडिया असलेले सर्व संदेश हटवा" - "%s पेक्षा अधिक जुने संदेश हटवा" - "%s पेक्षा जुने संदेश स्वयं हटवा" - "दुर्लक्ष करा" - "मीडिया असलेले सर्व संदेश हटवायचे?" - "%s पेक्षा जुने संदेश हटवायचे?" - "%s पेक्षा जुने संदेश हटवायचे आणि स्वयं-हटवा चालू करायचे?" - "%s म्हणाले" - "आपण म्हटले" - "%s कडून संदेश" - "आपण एक संदेश पाठविला" - "पाठवित आहे..." - "पाठविले नाही. पुन्हा प्रयत्न करण्यासाठी स्पर्श करा." - "पाठविले नाही. पुन्हा प्रयत्न करत आहे…" - "पुन्हा पाठवा किंवा हटवा" - "कृपया आणीबाणी सेवांना एक व्हॉईस कॉल करा. यावेळी आपला मजकूर संदेश वितरीत केला जाऊ शकत नाही." - "अयशस्‍वी झाले" - "डाउनलोड करण्यासाठी नवीन MMS संदेश" - "नवीन MMS संदेश" - "डाउनलोड करू शकलो नाही" - "पुन्हा प्रयत्न करण्यासाठी स्पर्श करा" - "डाउनलोड करण्यासाठी स्पर्श करा" - "डाउनलोड करा किंवा हटवा" - "डाउनलोड करत आहे..." - "संदेश कालबाह्य झाला किंवा उपलब्‍ध नाही" - "आकार: %1$s, कालबाह्यता: %2$s" - "पाठवू शकलो नाही. प्राप्तकर्ता वैध नाही." - "नेटवर्कवर सेवा सक्रिय केली नाही" - "नेटवर्क समस्येमुळे पाठवू शकलो नाही" - "संदेश कालबाह्य झाला किंवा उपलब्‍ध नाही" - "(विषय नाही)" - "अज्ञात प्रेषक" - "वितरित" - "%2$s कडील %1$s संदेश डाउनलोड करू शकलो नाही" - "कमी मेमरीमुळे डेटाबेस ऑपरेशन पूर्ण करू शकलो नाही" - "संदेश पाठविला नाही" - "काही संदेश संदेशन मध्ये पाठविले गेले नाहीत" - - %d संभाषणांमधील %d संदेश - %d संभाषणांमधील %d संदेश - - "संदेश डाउनलोड केला नाही" - "काही संदेश संदेशन मध्ये डाउनलोड झाले नाहीत" - - %d संभाषणांमधील %d संदेश - %d संभाषणांमधील %d संदेश - - "%1$s वर संदेश पाठविला नाही" - "कृपया आणीबाणी सेवांना एक व्हॉईस कॉल करा. यावेळी आपला मजकूर संदेश %1$s ला वितरीत केला जाऊ शकत नाही." - - %d नवीन संदेश - %d नवीन संदेश - - "प्रारंभ करा" - "कॅमेरा उपलब्ध नाही" - "कॅमेरा उपलब्ध नाही" - "व्हिडिओ कॅप्चर उपलब्ध नाही" - "मीडिया जतन करू शकत नाही" - "चित्र घेऊ शकत नाही" - "परत" - "संग्रहित" - "हटवा" - "संग्रहण करा" - "संग्रहण रद्द करा" - "सूचना बंद करा" - "सूचना चालू करा" - "संपर्क जोडा" - "डाउनलोड करा" - "पाठवा" - "हटवा" - "हा संदेश हटवायचा?" - "ही क्रिया पूर्ववत केली जाणे शक्य नाही." - "हटवा" - - हे संभाषण हटवायचे? - ही संभाषणे हटवायची? - - "हटवा" - "रद्द करा" - "प्रति" - "एकाधिक प्रतिमा निवडा" - "निवडीची पुष्टी करा" - "+%d" - "ऑडिओ रेकॉर्ड करू शकत नाही. पुन्हा प्रयत्न करा." - "ऑडिओ प्ले करू शकत नाही. पुन्हा प्रयत्न करा." - "ऑडिओ जतन करू शकलो नाही. पुन्हा प्रयत्न करा." - "स्पर्श करा आणि धरून ठेवा" - ", " - " " - ": " - " " - "चित्र" - "ऑडिओ क्लिप" - "व्हिडिओ" - "संपर्क कार्ड" - "डाउनलोड करा" - "SMS द्वारे प्रत्युत्तर द्या" - "MMS द्वारे प्रत्‍युत्तर द्या" - "प्रत्युत्तर द्या" - - %d सहभागी - %d सहभागी - - "मी" - "संपर्क अवरोधित आणि संग्रहित केला" - "संपर्क अनावरोधित केला आणि संग्रहण करणे रद्द केले" - "%d संग्रहित केले" - "%d संग्रहण करणे रद्द केले" - "सूचना बंद" - "सूचना चालू" - "सर्व सेट केले. पुन्हा पाठवा ला स्पर्श करा." - "डीफॉल्ट SMS अॅप म्हणून संदेशन यशस्वीरित्या सेट झाले." - - संलग्नक टाकून द्या - संलग्नके टाकून द्या - - "ऑडिओ संलग्नक" - "ऑडिओ संलग्नक प्ले करा" - "विराम द्या" - "%s कडून संदेश: %s." - "%s कडील अयशस्‍वी संदेश: %s. वेळ: %s" - "%s कडील संदेश: %s. वेळ: %s" - "%s ला न पाठविलेला संदेश: %s. वेळ: %s" - "%s ला संदेश पाठवित आहे: %s. वेळ: %s" - "%s चा अयशस्‍वी संदेश: %s. वेळ: %s" - "%s चा संदेश: %s. वेळ: %s" - "%s कडील अयशस्‍वी संदेश: %s. वेळ: %s. %s" - "%s कडील संदेश: %s. वेळ: %s. %s" - "%s ला न पाठविलेला संदेश: %s. वेळ: %s" - "%s ला संदेश पाठवित आहे: %s. वेळ: %s" - "%s चा अयशस्‍वी संदेश: %s. वेळ: %s" - "%s चा संदेश: %s. वेळ: %s" - "संदेश अयशस्वी. पुन्हा प्रयत्न करण्यासाठी स्पर्श करा." - "%s सह संभाषण" - "विषय हटवा" - "व्‍हिडिओ कॅप्‍चर करा" - "एक स्थिर प्रतिमा कॅप्च करा" - "चित्र घ्या" - "व्हिडिओ रेकॉर्ड करणे प्रारंभ करा" - "पूर्ण स्क्रीन कॅमेर्‍यावर स्विच करा" - "पुढील आणि मागील कॅमेर्‍यात स्विच करा" - "रेकॉर्डिंग थांबवा आणि व्‍हिडिओ संलग्न करा" - "व्हिडिओ रेकॉर्ड करणे थांबवा" - "संदेशन फोटो" - - %d फोटो \"%s\" अल्बममध्‍ये जतन केला - %d फोटो \"%s\" अल्बममध्‍ये जतन केले - - - %d व्हिडिओ \"%s\" अल्बममध्‍ये जतन केला - %d व्हिडिओ \"%s\" अल्बममध्‍ये जतन केले - - - %d संलग्नक \"%s\" अल्बममध्‍ये जतन केले - %d संलग्नके \"%s\" अल्बममध्‍ये जतन केली - - - %d संलग्नक \"डाउनलोड\" मध्‍ये जतन केले - %d संलग्नके \"डाउनलोड\" मध्‍ये जतन केली - - - %d संलग्नक जतन केले - %d संलग्नके जतन केली - - - %d संलग्नक जतन करणे शक्य झाले नाही - %d संलग्नके जतन करणे शक्य झाले नाही - - "MMS संलग्नक जतन केले" - "सेटिंग्ज" - "संग्रहित" - "बंद करा" - "MMS" - "प्रगत" - "डीबग करणे" - "सूचना" - "ध्वनी" - "मूक" - "कंपन" - "अवरोधित केले" - "SMS वितरण अहवाल" - "आपण पाठविता त्या प्रत्‍येक SMS साठी वितरण अहवालाची विनंती करा" - "स्‍वयं-पुनर्प्राप्त करा" - "MMS संदेश स्वयंचलितपणे पुनर्प्राप्त करा" - "रोमिंग स्‍वयं-पुनर्प्राप्त करा" - "रोमिंगमध्‍ये असताना स्वयंचलितपणे MMS पुनर्प्राप्त करा" - "गट संदेशन" - "एक संदेश एकाधिक प्राप्तकर्त्यांना पाठविण्‍यासाठी MMS चा वापर करा." - "डीफॉल्ट SMS अॅप" - "डीफॉल्ट SMS अॅप" - - - "आपला फोन नंबर" - "अज्ञात" - "पाठविला जाणारा संदेश ध्वनी करतो" - "SMS साचवा" - "प्राप्त केलेला SMS अविश्लेषित डेटा बाह्य संचयन फायलीमध्‍ये साचवा" - "MMS साचवा" - "प्राप्त केलेला MMS अविश्लेषित डेटा बाह्य संचयन फायलीमध्‍ये साचवा" - "वायरलेस अॅलर्ट" - "संदेश पर्याय" - "मजकूर कॉपी करा" - "तपशील पहा" - "हटवा" - "अग्रेषित करा" - "संदेश तपशील" - "प्रकार: " - "मजकूर संदेश" - "मल्‍टीमीडिया संदेश" - "प्रेषक: " - "प्रति: " - "प्रेषित: " - "प्राप्त झाले: " - "विषय: " - "आकार: " - "प्राधान्य: " - "सिम: " - "उच्च" - "सामान्य" - "निम्न" - "सिम %s" - "लपविलेला प्रेषक पत्ता" - "संलग्नके लोड करताना संदेश पाठवू शकत नाही." - "संलग्नक लोड करू शकत नाही. पुन्हा प्रयत्न करा." - "नेटवर्क सज्ज नाही. पुन्हा प्रयत्न करा." - "मजकूर हटवा" - "मजकूर आणि संख्या प्रविष्ट करणे दरम्यान स्विच करा" - "अधिक सहभागी जोडा" - "सहभाग्यांची पुष्टी करा" - "नवीन संभाषण प्रारंभ करा" - "हा आयटम निवडा" - "व्हिडिओ प्ले करा" - "लोक आणि पर्याय" - "डीबग करणे" - "लोक आणि पर्याय" - "सामान्य" - "या संभाषणातील लोक" - "एक कॉल करा" - "संदेश पाठवा" - "<br/><small>%s वरून</small>संदेश पाठवा" - - फोटो पाठवा - फोटो पाठवा - - - ऑडिओ पाठवा - ऑडिओ पाठवा - - - व्हिडिओ पाठवा - व्हिडिओ पाठवा - - - संपर्क कार्ड पाठवा - संपर्क कार्ड पाठवा - - - संलग्नक पाठवा - संलग्नके पाठवा - - - पाठविण्‍यासाठी %d संलग्नक सज्ज - पाठविण्‍यासाठी %d संलग्नक सज्ज - - "अभिप्राय पाठवा" - "Google Play Store मध्ये पहा" - "आवृत्ती माहिती" - "आवृत्ती %1$s" - "मुक्त स्त्रोत परवाने" - "सूचना" - "संलग्नक मर्यादेपर्यंत पोहोचले" - "संलग्नक लोड करण्यात अयशस्वी." - "संपर्कांवर जोडायचे?" - "संपर्क जोडा" - "विषय" - "विषय: " - "%s%s" - "संपर्क कार्ड लोड करीत आहे" - "संपर्क कार्ड लोड करू शकलो नाही" - "संपर्क कार्ड पहा" - - %d संपर्क - %d संपर्क - - "संपर्क कार्ड" - "जन्मदिवस" - "टिपा" - "संदेश अग्रेषित करा" - "प्रत्युत्तर द्या" - "+1" - "+%d" - "SMS अक्षम" - "पाठविण्यासाठी, डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा" - "डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा" - "बदला" - "संदेश प्राप्त करण्यासाठी, डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा" - "SMS संदेश पाठविण्यासाठी कोणतेही प्राधान्यकृत सिम निवडले नाही" - "या अॅप ला डिव्हाइस मालकाने अनुमती दिलेली नाही." - "ठीक आहे" - "एका संभाषणात पुष्कळ सहभागी आहेत" - - अवैध संपर्क - अवैध संपर्क - - "कॅमेरा प्रतिमा लोड करू शकलो नाही" - "आपण: " - "%s: " - "मसुदा" - "आपण नवीन संभाषण प्रारंभ केल्यावर, आपल्याला तो येथे सूचीबद्ध केलेला दिसेल" - "संग्रहित केलेली संभाषणे येथे दिसतात" - "संभाषणे लोड करीत आहे…" - "चित्र" - "ऑडिओ क्लिप" - "व्हिडिओ" - "संपर्क कार्ड" - "MMS" - "पूर्ववत करा" - "पुन्हा प्रयत्न करा" - "एक नवीन संदेश प्रारंभ करण्यासाठी एक संपर्क नाव किंवा फोन नंबर प्रविष्ट करा" - "अवरोधित करा" - "%s अवरोधित करा" - "%s अनावरोधित करा" - "%s अवरोधित करायचे?" - "आपण या नंबरवरून संदेश प्राप्त करणे सुरु ठेवाल परंतु आपल्याला आणखी सूचित केले जाणार नाही. हे संभाषम संग्रहित केले जाईल." - "अवरोधित संपर्क" - "अनावरोधित करा" - "अवरोधित संपर्क" - "दस्तऐवज लायब्ररीतून प्रतिमा निवडा" - "संदेश पाठवित आहे" - "संदेश पाठवला" - "सेल्युलर डेटा बंद आहे. आपल्या सेटिंग्ज तपासा." - "विमान मोडमध्ये संदेश पाठवू शकत नाही" - "संदेश पाठविला जाऊ शकला नाही" - "संदेश डाउनलोड केला" - "सेल्युलर डेटा बंद आहे. आपल्या सेटिंग्ज तपासा." - "विमान मोडमध्ये संदेश डाउनलोड करू शकत नाही" - "संदेश डाउनलोड करणे शक्य झाले नाही" - "शून्य" - "एक" - "दोन" - "तीन" - "चार" - "पाच" - "सहा" - "सात" - "आठ" - "नऊ" - "%1$s सह संदेश पाठवू शकत नाही, त्रुटी %2$d" - "अज्ञात वाहकासह संदेश पाठवू शकत नाही, त्रुटी %1$d" - "अग्रेषित: %s" - "संदेश पाठविला नाही: नेटवर्क वर सेवा सक्रिय नाही" - "संदेश पाठविला नाही: अवैध गंतव्यस्‍थान पत्ता" - "संदेश पाठविला नाही: अवैध संदेश" - "संदेश पाठविला नाही: असमर्थित सामग्री" - "संदेश पाठविला नाही: असमर्थित संदेश" - "संदेश पाठविला नाही: खूप मोठा" - "नवीन संदेश" - "पहा" - "प्रतिमा" - "योग्य अनुप्रयोग शोधणे शक्य झाले नाही" - "प्राप्तकर्ता काढा" - "नवीन संदेश" - "रद्द करा" - "प्रवेश बिंदू संपादित करा" - "सेट केले नाही" - "नाव" - "APN" - "MMSC" - "MMS प्रॉक्सी" - "MMS पोर्ट" - "MCC" - "MNC" - "APN प्रकार" - "APN हटवा" - "नवीन APN" - "जतन करा" - "टाकून द्या" - "नाव फील्ड रिक्त असू शकत नाही." - "APN रिक्त असू शकत नाही." - "MCC फील्ड 3 अंकी असणे आवश्यक आहे." - "MNC फील्ड 2 किंवा 3 अंकी असणे आवश्यक आहे." - "डीफॉल्ट APN सेटिंग्ज पुनर्संचयित करत आहे." - "डीफॉल्टवर रीसेट करा" - "डीफॉल्ट APN सेटिंग्ज रीसेट करणे पूर्ण झाले." - "अशीर्षकांकित" - "प्रवेश बिंदू नावे" - "APN" - "नवीन APN" - "या वापरकर्त्यासाठी प्रवेश बिंदू नाव सेटिंग्ज उपलब्ध नाहीत" - "क्लिपबोर्डवर कॉपी करायचे?" - "कॉपी करा" - "%s वर" - "सामान्य" - "प्रगत" - "सामान्य सेटिंग्ज" - "प्रगत सेटिंग्ज" - "\"%s\" सिम" - "सर्व प्राप्तकर्त्यांना वैयक्तिक SMS संदेश पाठवा. केवळ आपणच कोणतीही प्रत्युत्तरे मिळवाल" - "सर्व प्राप्तकर्त्यांना एक एकल MMS पाठवा" - "अज्ञात नंबर" - "नवीन संदेश" - "नवीन संदेश." - "सिम निवडकर्ता" - "%1$s निवडले, सिम निवडकर्ता" - "विषय संपादित करा" - "सिम निवडा किंवा विषय संपादित करा" - "ऑडिओ रेकॉर्ड करण्‍यासाठी स्पर्श करा आणि धरून ठेवा" - "नवीन संभाषण प्रारंभ करा" - "संदेशन" - "संदेशन सूची" - "संदेशन" - "नवीन संदेश" - "संभाषण सूची" - "संभाषणे लोड करीत आहे" - "संदेश लोड करत आहे" - "अधिक संभाषणे पहा" - "अधिक संदेश पहा" - "संभाषण हटवले" - "संभाषण हटविले. भिन्न संदेशन संभाषण दर्शविण्यासाठी स्पर्श करा" - "अवरोधित केले" - "अनावरोधित केले" - "संचयन स्थान कमी आहे. काही डेटा गमावला जाऊ शकतो." - "संलग्नके निवडा" - "निवडीची पुष्टी करा" - "%d निवडली" - "कृपया एक किंवा अधिक संलग्नके काढा आणि पुन्हा प्रयत्न करा." - "आपण आपला संदेश पाठविण्याचा प्रयत्न करू शकता, परंतु आपण जोपर्यंत एक किंवा अधिक संलग्नके काढत नाही तोपर्यंत तो वितरित केला जाऊ शकत नाही." - "आपण प्रति संदेश केवळ एक व्हिडिओ पाठवू शकता. कृपया अतिरिक्त व्हिडिओ काढा आणि पुन्हा प्रयत्न करा." - "संलग्नक लोड करण्यात संदेशन अयशस्वी झाले." - "तरीही पाठवा" - "संभाषण प्रारंभ करू शकलो नाही" - "%1$s (%2$s)" - "%s निवडले" - diff --git a/res/values-ms-rMY/arrays.xml b/res/values-ms-rMY/arrays.xml deleted file mode 100644 index 10f0c1c..0000000 --- a/res/values-ms-rMY/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "tiada subjek" - "tiada subjek" - - - "Ya" - "Tidak" - "OK" - "Hehe" - "Terima kasih" - "Saya bersetuju" - "Nice" - "Saya dalam perjalanan" - "OK, saya akan hubungi anda semula" - ":)" - ":(" - - diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml deleted file mode 100644 index 80cb5c4..0000000 --- a/res/values-ms-rMY/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Pemesejan" - "Pemesejan" - "Pilih perbualan" - "Tetapan" - "Hantar Mesej" - "Tambah lampiran" - "Bantuan" - "Selamat datang" - "Langkau" - "Seterusnya >" - "Seterusnya" - "Keluar" - "Tetapan >" - "Tetapan" - "Pemesejan memerlukan kebenaran untuk mengakses SMS, Telefon dan Kenalan." - "Anda boleh menukar kebenaran dalam Tetapan > Apl > Pemesejan > Kebenaran." - "Anda boleh menukar kebenaran dalam Tetapan, Apl, Pemesejan, Kebenaran." - "Kerap" - "Semua kenalan" - "Hantar ke %s" - "Abadikan gambar atau video" - "Pilih imej dari peranti ini" - "Rakam audio" - "Pilih foto" - "Media dipilih." - "Media tidak dipilih." - "%d dipilih" - "imej %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imej" - "Rakam audio" - "Kongsi" - "Sebentar tadi" - "Sekarang" - - %d min - %d min - - - %d jam - %d jam - - - %d hari - %d hari - - - %d minggu - seminggu - - - %d bulan - sebulan - - - %d tahun - setahun - - "Mesej kelas 0" - "Simpan" - "Peranti kekurangan ruang. Pemesejan akan memadamkan mesej yang lebih lama secara automatik untuk mengosongkan ruang." - "Ruang storan semakin berkurangan" - "Pemesejan mungkin tidak akan menghantar atau menerima mesej sehingga lebih banyak ruang tersedia pada peranti anda." - "Storan SMS kurang. Anda mungkin perlu memadamkan mesej." - "Sahkan nombor telefon anda" - "Langkah sekali ini akan memastikan Pemesejan menghantar mesej kumpulan anda dengan betul." - "Nombor telefon" - "Padamkan semua mesej dengan media" - "Padamkan mesej yang lebih lama daripada %s" - "Auto padam mesej yang lebih lama daripada %s" - "Abaikan" - "Padam semua mesej dengan media?" - "Padam mesej yang lebih lama daripada %s?" - "Padam mesej yang lebih lama daripada %s dan hidupkan autopadam?" - "%s kata" - "Anda kata" - "Mesej daripada %s" - "Anda telah menghantar mesej" - "Menghantar…" - "Tidak dihantar. Sentuh untuk mencuba lagi." - "Tidak dihantar. Mencuba lagi..." - "Hantar semula atau padam" - "Sila buat panggilan suara ke perkhidmatan kecemasan. Mesej teks anda tidak dapat dihantar pada masa ini." - "Gagal" - "Mesej MMS baharu untuk dimuat turun" - "Mesej MMS baharu" - "Tidak dapat memuat turun" - "Sentuh untuk mencuba lagi" - "Sentuh untuk memuat turun" - "Muat turun atau padam" - "Memuat turun..." - "Mesej telah tamat tempoh atau tidak tersedia" - "saiz: %1$s, tarikh luput: %2$s" - "Tidak dapat menghantar. Penerima tidak sah." - "Perkhidmatan tidak diaktifkan pada rangkaian" - "Tidak dapat menghantar kerana masalah rangkaian" - "Mesej telah tamat tempoh atau tidak tersedia" - "(Tiada subjek)" - "Pengirim tidak dikenali" - "Dikirimkan" - "Tidak dapat memuat turun %1$s daripada %2$s." - "Tidak dapat menyelesaikan operasi pangkalan data akibat memori rendah" - "Mesej tidak dihantar" - "Sesetengah mesej tidak dihantar dalam Pemesejan" - - %d mesej dalam %d perbualan - %d mesej dalam satu perbualan - - "Mesej tidak dimuat turun" - "Sesetengah mesej tidak dimuat turun dalam Pemesejan" - - %d mesej dalam %d perbualan - %d mesej dalam satu perbualan - - "Mesej ke %1$s tidak dihantar" - "Sila buat panggilan suara ke perkhidmatan kecemasan. Mesej teks anda ke %1$s tidak dapat dihantar pada masa ini." - - %d mesej baharu - Mesej baharu - - "Mula" - "Kamera tidak tersedia" - "Kamera tidak tersedia" - "Pengabadian video tidak tersedia" - "Tidak dapat menyimpan media" - "Tidak boleh mengambil gambar" - "Kembali" - "Diarkibkan" - "Padam" - "Arkib" - "Nyaharkib" - "Matikan pemberitahuan" - "Hidupkan pemberitahuan" - "Tambah kenalan" - "Muat turun" - "Hantar" - "Padam" - "Padamkan mesej ini?" - "Tindakan ini tidak boleh dibuat asal." - "Padam" - - Padamkan perbualan ini? - Padamkan perbualan ini? - - "Padam" - "Batal" - "Ke" - "Pilih berbilang imej" - "Sahkan pilihan" - "+%d" - "Tidak dapat merakam audio. Cuba lagi." - "Tidak dapat memainkan audio. Cuba lagi." - "Tidak dapat menyimpan audio. Cuba lagi." - "Sentuh & tahan" - ", " - " " - ": " - " " - "Gambar" - "Klip audio" - "Video dalam strim" - "Kad kenalan" - "Muat turun" - "Balas melalui SMS" - "Balas melalui MMS" - "Balas" - - %d peserta - %d peserta - - "Saya" - "Kenalan yang disekat & diarkib" - "Kenalan dinyahsekat & dinyaharkib" - "%d diarkibkan" - "%d dinyaharkib" - "Pemberitahuan dimatikan" - "Pemberitahuan dihidupkan" - "Semuanya sedia. Sentuh Hantar lagi." - "Pemesejan berjaya dijadikan sebagai apl SMS lalai." - - Buang lampiran - Buang lampiran - - "Lampiran audio" - "Mainkan lampiran audio" - "Jeda" - "Mesej daripada %s: %s." - "Mesej gagal daripada %s: %s. Masa: %s." - "Mesej daripada %s: %s. Masa: %s." - "Mesej tidak dihantar kepada %s: %s. Masa: %s." - "Menghantar mesej kepada %s: %s. Masa: %s." - "Mesej gagal kepada %s: %s. Masa: %s." - "Mesej kepada %s: %s. Masa: %s." - "Mesej gagal daripada %s: %s. Masa: %s. %s." - "Mesej daripada %s: %s. Masa: %s. %s." - "Mesej tidak dihantar kepada %s: %s. Masa: %s." - "Menghantar mesej kepada %s: %s. Masa: %s." - "Mesej gagal kepada %s: %s. Masa: %s." - "Mesej kepada %s: %s. Masa: %s." - "Mesej gagal. Sentuh untuk mencuba semula." - "Perbualan dengan %s" - "Padam subjek" - "Rakam video" - "Abadikan imej pegun" - "Ambil gambar" - "Mula merakam video" - "Tukar kepada kamera skrin penuh" - "Bertukar antara kamera hadapan dan belakang" - "Berhenti merakam dan lampirkan video" - "Berhenti merakam video" - "Foto pemesejan" - - %d foto disimpan ke album \"%s\" - %d foto disimpan ke album \"%s\" - - - %d video disimpan ke album \"%s\" - %d video disimpan ke album \"%s\" - - - %d lampiran disimpan ke album \"%s\" - %d lampiran disimpan ke album \"%s\" - - - %d lampiran disimpan ke \"Muat Turun\" - %d lampiran disimpan ke \"Muat Turun\" - - - %d lampiran disimpan - %d lampiran disimpan - - - Tidak dapat menyimpan %d lampiran - Tidak dapat menyimpan %d lampiran - - "Lampiran MMS yang disimpan" - "Tetapan" - "Diarkibkan" - "Tutup" - "MMS" - "Lanjutan" - "Nyahpepijat" - "Pemberitahuan" - "Bunyi" - "Senyap" - "Bergetar" - "Disekat" - "Laporan penghantaran SMS" - "Minta laporan penghantaran untuk setiap SMS yang anda hantar" - "Auto dapat kembali" - "Dapatkan semula mesej MMS secara automatik" - "Dapatkan kembali perayauan secara automatik" - "Dapatkan MMS secara automatik semasa merayau" - "Pemesejan kumpulan" - "Gunakan MMS untuk menghantar mesej tunggal apabila terdapat berbilang penerima" - "Apl SMS lalai" - "Apl SMS lalai" - - - "Nombor telefon anda" - "Tidak diketahui" - "Bunyi mesej keluar" - "Buang SMS" - "Buang data mentah SMS yang diterima ke dalam fail storan luaran" - "Buang MMS" - "Buang data mentah MMS yang diterima ke dalam fail storan luaran" - "Makluman wayarles" - "Pilihan mesej" - "Salin teks" - "Lihat butiran" - "Padam" - "Kirim semula" - "Butiran mesej" - "Jenis: " - "Mesej teks" - "Mesej multimedia" - "Daripada: " - "Kepada: " - "Dihantar: " - "Diterima: " - "Subjek: " - "Saiz: " - "Keutamaan: " - "SIM: " - "Tinggi" - "Biasa" - "Rendah" - "SIM %s" - "Alamat pengirim tersembunyi" - "Tidak boleh menghantar mesej semasa memuatkan lampiran." - "Tidak dapat memuatkan lampiran. Cuba lagi." - "Rangkaian tidak sedia. Cuba lagi." - "Padam teks" - "Beralih antara memasukkan teks dan nombor" - "Tambah peserta lagi" - "Sahkan peserta" - "Mulakan perbualan baharu" - "Pilih item ini" - "Mainkan video" - "Orang & pilihan" - "Nyahpepijat" - "Orang & pilihan" - "Umum" - "Orang dalam perbualan ini" - "Buat panggilan" - "Hantar mesej" - "Hantar mesej<br/><small>from %s</small>" - - Hantar foto - Hantar foto - - - Hantar audio - Hantar audio - - - Hantar video - Hantar video - - - Hantar kad kenalan - Hantar kad kenalan - - - Hantar lampiran - Hantar lampiran - - - %d lampiran sedia untuk dihantar - Satu lampiran sedia untuk dihantar - - "Hantar maklum balas" - "Lihat di Gedung Google Play" - "Maklumat versi" - "Versi %1$s" - "Lesen sumber terbuka" - "Pemberitahuan" - "Had lampiran dicapai" - "Gagal memuatkan lampiran." - "Tambah pada Kenalan?" - "Tambah Kenalan" - "Subjek" - "Subjek: " - "%s%s" - "Memuatkan kad kenalan" - "Tidak dapat memuatkan kad kenalan" - "Lihat kad kenalan" - - %d kenalan - %d kenalan - - "Kad kenalan" - "Tarikh lahir" - "Nota" - "Kirim semula mesej" - "Balas" - "1" - "+%d" - "SMS dilumpuhkan" - "Untuk menghantar, jadikan Pemesejan apl SMS lalai" - "Jadikan Pemesejan apl SMS lalai" - "Tukar" - "Untuk menerima mesej, jadikan Pemesejan apl SMS lalai" - "Tiada SIM pilihan yang dipilih untuk menghantar mesej SMS" - "Apl ini tidak dibenarkan oleh pemilik peranti." - "OK" - "Terlalu ramai peserta dalam perbualan" - - Kenalan tidak sah - Kenalan tidak sah - - "Tidak dapat memuatkan imej kamera" - "Anda: " - "%s: " - "Draf" - "Sebaik sahaja anda memulakan perbualan baharu, anda akan melihat perbualan itu disenaraikan di sini" - "Perbualan diarkibkan dipaparkan di sini" - "Memuatkan perbualan..." - "Gambar" - "Klip audio" - "Video" - "Kad kenalan" - "MMS" - "Buat asal" - "Cuba semula" - "Masukkan nama kenalan atau nombor telefon untuk memulakan mesej baharu" - "Sekat" - "Sekat %s" - "Nyahsekat %s" - "Sekat %s?" - "Anda akan terus menerima mesej daripada nombor ini tetapi tidak akan dimaklumkan lagi. Perbualan ini akan diarkibkan." - "Kenalan yang disekat" - "NYAHSEKAT" - "Kenalan yang disekat" - "Pilih imej dari perpustakaan dokumen" - "Menghantar mesej" - "Mesej dihantar" - "Data selular dimatikan. Periksa tetapan anda." - "Tidak dapat menghantar mesej dalam mod Pesawat" - "Mesej tidak dapat dihantar" - "Mesej dimuat turun" - "Data selular dimatikan. Periksa tetapan anda." - "Tidak dapat memuat turun mesej dalam mod Pesawat" - "Mesej tidak dapat dimuat turun" - "Sifar" - "Satu" - "Dua" - "Tiga" - "Empat" - "Lima" - "Enam" - "Tujuh" - "Lapan" - "Sembilan" - "Tidak dapat menghantar mesej dengan %1$s, ralat %2$d" - "Tidak dapat menghantar mesej dengan pembawa yang tidak diketahui, ralat %1$d" - "K.S.: %s" - "Mesej tidak dihantar: perkhidmatan tidak diaktifkan di rangkaian" - "Mesej tidak dihantar: alamat destinasi tidak sah" - "Mesej tidak dihantar: mesej tidak sah" - "Mesej tidak dihantar: kandungan tidak disokong" - "Mesej tidak dihantar: mesej tidak disokong" - "Mesej tidak dihantar: terlalu besar" - "Mesej baharu" - "Paparan" - "Imej" - "Tidak menemui aplikasi yang sesuai" - "Alih keluar penerima" - "Mesej baharu" - "Batal" - "Edit titik capaian" - "Tidak ditetapkan" - "Nama" - "APN" - "MMSC" - "Proksi MMS" - "Port MMS" - "MCC" - "MNC" - "Jenis APN" - "Padam APN" - "APN Baharu" - "Simpan" - "Buang" - "Medan Nama tidak boleh kosong." - "APN tidak boleh kosong." - "Medan MCC mestilah 3 digit." - "Medan MNC hendaklah mempunyai 2 atau 3 digit." - "Memulihkan tetapan lalai APN." - "Tetapkan semula kepada lalai" - "Penetapan semula tetapan lalai APN selesai." - "Tidak bertajuk" - "Nama Titik Capaian" - "APN" - "APN Baharu" - "Tetapan Nama Titik Capaian tidak tersedia untuk pengguna ini" - "Salin ke papan keratan?" - "Salin" - "ke %s" - "Umum" - "Lanjutan" - "Tetapan umum" - "Tetapan terperinci" - "SIM \"%s\"" - "Hantar mesej SMS individu kepada semua penerima. Hanya anda yang akan menerima sebarang balasan" - "Hantar MMS tunggal kepada semua penerima" - "Nombor tidak diketahui" - "Mesej baharu" - "Mesej baharu" - "Pemilih SIM" - "%1$s dipilih, pemilih SIM" - "Edit subjek" - "Pilih SIM atau edit subjek" - "Sentuh dan tahan untuk merakam audio" - "Mulakan perbualan baharu" - "Pemesejan" - "Senarai Pemesejan" - "Pemesejan" - "Mesej baharu" - "Senarai perbualan" - "Memuatkan perbualan" - "Memuatkan mesej" - "Lihat lagi perbualan" - "Lihat pesanan lagi" - "Perbualan dipadamkan" - "Perbualan dipadamkan. Sentuh untuk menunjukkan perbualan Pemesejan yang lain" - "Disekat" - "Dinyahsekat" - "Ruang storan rendah. Sesetengah data mungkin hilang." - "Pilih lampiran" - "Sahkan pilihan" - "%d dipilih" - "Sila alih keluar satu atau lebih lampiran, kemudian cuba lagi." - "Anda boleh cuba menghantar mesej tetapi mesej itu mungkin tidak dihantar kecuali anda mengalih keluar satu atau beberapa lampiran." - "Anda hanya boleh menghantar satu video bagi setiap mesej. Sila alih keluar video tambahan dan cuba lagi." - "Pemesejan gagal memuatkan lampiran." - "Hantar juga" - "Tidak dapat memulakan perbualan" - "%1$s (%2$s)" - "%s dipilih" - diff --git a/res/values-my-rMM/arrays.xml b/res/values-my-rMM/arrays.xml deleted file mode 100644 index 331ac85..0000000 --- a/res/values-my-rMM/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "အကြောင်းအရာ မရှိ" - "အကြောင်းအရာမဲ့" - - - "ဟုတ်" - "မဟုတ်" - "အိုကေ" - "ဟဲဟဲ" - "ကျေးဇူးပဲ" - "ကျွန်ုပ် သဘောတူသည်" - "ကောင်း" - "ကျွန်ုပ်၏ လမ်းပေါ်မှာ" - "အိုကေ၊ ကျွန်ုပ် နောက်မှာ ပြန်လာပါရစေ" - ":)" - ":(" - - diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml deleted file mode 100644 index 35a6586..0000000 --- a/res/values-my-rMM/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "စာပို့ခြင်း" - "စာပို့ခြင်း" - "စကားဝိုင်း ရွေးချယ်ပါ" - "ဆက်တင်များ" - "စာ ပို့ရန်" - "ပူးတွချက် တစ်ခု ထည့်ရန်" - "အကူအညီ" - "မင်္ဂလာပါ" - "ကျော်ရန်" - "နောက်တစ်ခု" - "နောက်တစ်ခု" - "ထွက်ရန်" - "ချိန်ညှိချက်များ >" - "ချိန်ညှိချက်များ" - "စာတိုပို့ခြင်က SMS၊ ဖုန်း နှင့် အဆက်အသွယ်များထံ ခွင့်ပြုချက် လိုအပ်သည်။" - "သင်သည် ခွင့်ပြုချက်များကို ဆက်တင်များထဲမှာ ပြောင်းလဲနိုင် > အက်ပ်များ > စာတိုပို့ခြင်း > ခွင့်ပြုချက်များ။" - "သင်သည် ခွင့်ပြုချက်များကို ဆက်တင်များ၊ အက်ပ်များ၊ စာတိုပို့ခြင်း၊ ခွင့်ပြုချက်များထဲတွင် ပြောင်းလဲနိုင်သည်။" - "မကြာခဏ" - "ဆက်သွယ်အားလုံး" - "%sသို့ ပို့ရန်" - "ပုံများ သို့မဟုတ် ဗီဒီယို ရိုက်ယူရန်" - "ဤစက်ပစ္စည်းမှ ပုံများ ရွေးရန်" - "အသံကို ဖမ်းရန်" - "ပုံရွေးရန်" - "မီဒီယာကိုရွေးချယ်ထားသည်။" - "မီဒီယာကို မရွေးချယ်ထားပါ။" - "%d ခု ရွေးချယ်ထား" - "ပုံ %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ပုံ" - "အသံကို ဖမ်းရန်" - "မျှဝေရန်" - "ယခုလေးတင်" - "ယခု" - - %d မိနစ် - %d မိနစ် - - - %d နာရီ - %d နာရီ - - - %d ရက် - %d ရက် - - - %d ပတ် - ၁ ပတ် - - - %d - တစ်လ - - - %d နှစ် - တစ်နှစ် - - "အဆင် ၀ စာတို" - "သိမ်းဆည်းရန်" - "ကိရိယာထဲတွင် နေရာ နည်းနေသည်။ နေရာလွတ်ပေးရန် စာတိုပို့ခြင်းက စာတို စာသား အဟောင်းကို အလိုအလျောက် ဖယ်ရှားပစ်မည်။" - "သိုလှောင်ခန်း နေရာ ကုန်ပါတော့မည်" - "သင်၏ ကိရိယာထဲတွင် နေရာလွတ် နောက်ထပ် ပေါ်မလာမချင်း စာတိုပို့ခြင်းက စာတိုများကို ပို့ခြင်း သို့မဟုတ် လက်ခံခြင်း လုပ်နိုင်မည် မဟုတ်ပါ။" - "SMS သိုလှောင်ခန်း နည်းနေသည်။ သင်သည် စာများကို ဖျက်ချင် ဖျက်ရနိုင်သည်။" - "သင့် ဖုန်းနံပါတ်ကို အတည်ပြုပါ" - "တကြိမ် လုပ်ရသည့် ဒီခြေလှမ်းက စာတိုပို့ခြင်းမှ သင်၏ အုပ်စု စာတိုများကို စနစ်တကျ ပို့ပေးမှာကို စိတ်ချနိုင်ပါသည်။" - "ဖုန်း နံပါတ်" - "မီဒီယာနှင့် စာများ အားလုံးကို ဖျက်မည်" - "%s ထက် ပိုကြာသော စာများကို ဖျက်မည်" - "%s မတိုင်ခင်က စာများအား အလိုအလျောက် ဖျက်ရန်" - "လစ်လျှူရှုရန်" - "မီဒီယာနှင့် စာတိုများ ဖျက်မည်လား?" - "%s ထက် ပိုကြာသော စာများ ဖျက်မည်လား?" - "%s မတိုင်ခင်က စာများအား ဖျက်ပြီး စာများ အလိုအလျောက်ဖျက်နိုင်မှုကို ဖွင့်ထားမည်လား?" - "%s ပြောတာက" - "သင်ပြောတာက" - "%s ထံမှစာ" - "သင်စာပို့သည်" - "ပို့နေ၏…" - "မပို့နိုင်ပါ။ ထပ်မံကြိုးစားရန် ထိပါ။" - "မပို့နိုင်ပါ။ ထပ်မံကြိုးစားနေ၏…" - "ပြန်ပို့ရန် သို့မဟုတ် ဖျက်ရန်" - "အရေးပေါ်ဝန်ဆောင်မှုများထံ အသံဖြင့် ခေါ်ပါ။ ယခုအချိန်တွင် သင့် စာတို မပို့နိုင်ပါ။" - "မအောင်မြင်ခဲ့" - "MMS စာအသစ်ကို ဒေါင်းလုဒ် လုပ်ရန်" - "MMS စာအသစ်" - "ဒေါင်းလုဒ် မလုပ်နိုင်ခဲ့ပါ" - "ထပ်စမ်းရန် ထိပါ" - "ဒေါင်းလုပ်ရယူရန် ထိပါ" - "ဒေါင်းလုဒ် လုပ်ရန် သို့မဟုတ် ဖျက်ရန်" - "ဒေါင်းလုပ် လုပ်နေစဉ်" - "စာသက်တမ်းကုန်ဆုံးသွား၏ သို့မဟုတ် စာမရှိတော့ပါ။" - "ဆိုက်: %1$s၊ သက်တမ်း ကုန်ဆုံး: %2$s" - "မပို့နိုင်ပါ။ လက်ခံမည့်သူ မရယူနိုင်ပါ။" - "ကွန်ရက်တွင် ဝန်ဆောင်မှု စဖွင့်သတ်မှတ်ခြင်း မပြုရသေးပါ" - "ကွန်ရက် ပြဿနာကြောင့် မပို့နိုင်ခဲ့ပါ။" - "စာ သက်တမ်းကုန်ဆုံးသွား၏ သို့မဟုတ် စာမရှိတော့ပါ။" - "(အကြောင်းအရာမရှိ)" - "ပေးပို့သူ မသိရ" - "ပေးသွင်းပြီး" - "%1$sစာကို %2$sမှ ဒေါင်းလုဒ် မလုပ်နိုင်ခဲ့ပါ" - "မှတ်ဉာဏ် နည်းနေ၍ ဒေတာဘေ့စ် လုပ်ကိုင်စရာ မပြီးဆုံးနိုင်ခဲ့ပါ။" - "စာ မပို့ခဲ့ပါ" - "အချို့ စာတိုများကို စာပို့ခြင်းထဲတွင် မပို့ခဲ့ပါ။" - - စာတိုများ %d ခုမှာ စကားပြောခန်း %d ခု အတွင်းတွင် ဖြစ်၏ - စကားပြောခန်းတစ်ခုတွင်းရှိ စာတိုများ %d ခု - - "စာကို ဒေါင်းလုပ်မလုပ်ထားပါ" - "အချို့ စာတိုများကို စာပို့ခြင်းထဲသို့ ဒေါင်းလုဒ် မလုပ်ခဲ့ပါ။" - - စာတိုများ %d ခုမှာ စကားပြောခန်း %d ခု အတွင်းတွင် ဖြစ်၏ - စကားပြောခန်းတစ်ခုတွင်းရှိ စာတိုများ %d ခု - - "%1$s သို့ စာတို ပို့မရပါ" - "အရေးပေါ်ဝန်ဆောင်မှုများထံ အသံဖြင့် ခေါ်ပါ။ %1$s သို့ ယခုအချိန်တွင် သင့် စာတို မပို့နိုင်ပါ။" - - %d စာအသစ် - စာအသစ် - - "စတင်ရန်" - "ကင်မရာ အသုံးမပြုနိုင်ပါ" - "ကင်မရာ အသုံးမပြုနိုင်ပါ" - "ဗွီဒီယို မရိုက်ယူနိုင်ပါ" - "မီဒီယာအား မသိမ်းဆည်းနိုင်ပါ" - "ဓာတ်ပုံရိုက်၍ မရနိုင်ပါ" - "နောက်သို့" - "မော်ကွန်းတင်ပြီး" - "ဖျက်ရန်" - "မော်ကွန်း" - "မော်ကွန်းမှထုတ်ယူရန်" - "အက​ြောင်းကြားချက်များ ပိတ်ရန်" - "အကြောင်းကြားချက်များ ဖွင့်ရန်" - "အဆက်အသွယ် ထည့်ရန်" - "ဒေါင်းလုဒ် လုပ်ရန်" - "ပို့ရန်" - "ဖျက်ရန်" - "ဤ စာကို ဖျက်မလား?" - "ဒီလုပ်ဆောင်ချက်ကို ြပန်ဖျက်မရနိုင်ပါ။" - "ဖျက်ရန်" - - ဤစကားဝိုင်းများအား ဖျက်မည်လား? - ဤစကားဝိုင်းအား ဖျက်မည်လား? - - "ဖျက်ရန်" - "ထားတော့" - "သို့" - "ပုံများစွာ ရွေးချယ်ရန်" - "ရွေးချယ်မှုကို အတည်ပြုရန်" - "+%d" - "အသံ ဖမ်းမရပါ။ ထပ်မံကြိုးစားပါ။" - "အသံဖွင့်မရပါ။ ထပ်မံကြိုးစားပါ။" - "အသံ သိမ်းမရပါ။ ထပ်မံကြိုးစားပါ။" - "ထိပြီး & ကိုင်ထားရန်" - ", " - " " - ": " - " " - "ပုံ" - "အသံ ကလီပ်" - "ဗီဒီယို" - "အဆက်အသွယ် ကဒ်" - "ဒေါင်းလုဒ် လုပ်ရန်" - "SMS မှတစ်ဆင့် ဖြေကြားရန်" - "MMS ဖြင့် ဖြေကြားရန်" - "ဖြေကြားရန်" - - ပါဝင်သူ %d ယောက် - ပါဝင်သူ %d ယောက် - - "ကျွန်ုပ်" - "အဆက်အသွယ်ဖြတ်တောက်ပြီး & သိမ်းဆည်းပြီး" - "အဆက်အသွယ်အား မပိတ်ဆို့တော့ပါ &amp မှော်ကွန်းမတင်တော့ပါ" - "%d မှတ်တမ်းတင်သိမ်းဆည်းခဲ့၏" - "%d သိမ်းထားခြင်းမှ ပြန်ထုတ်ယူသည်" - "အသိပေးချက်များ ပိတ်ထား" - "အသိပေးချက်များ ဖွင့်ထား" - "အဆင်သင့်ဖြစ်ပြီ။ Send အား ထိပါ။" - "စာတို့ပို့ခြင်းကို SMS ပုံသေ app အဖြစ် အောင်မြင်စွာ သတ်မှတ်လိုက်ပြီ။" - - ပူးတွဲပါဖိုင်များအား ဖယ်ရှားပါ - ပူးတွဲပါဖိုင်အား ဖယ်ရှားပါ - - "အသံ ပူးတွဲဖိုင်" - "ပူးတွဲပါအသံကိုဖွင့်ပါ" - "ဆိုင်းငံ့ရန်" - "%s ထံမှ စာတို: %s ။" - "%s: %s မှ စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: %s" - "%s: %s မှ စာတို။ အချိန်: %s" - "%s: %s သို့ စာတိုမပို့ရသေးပါ။ အချိန်: %s" - "%s: %s သို့ စာတိုပို့နေသည်။ အချိန်: %s" - "%s: %s သို့ စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: %s" - "%s: %s သို့ စာတို။ အချိန်: %s" - "%s: %s မှ စာပို့ခြင်းမအောင်မြင်ပါ။အချိန်: %s. %s" - "%s: %sမှ စာတိုပို့နေသည်။ အချိန်: %s. %s" - "%s: %s သို့ စာတိုမပို့ရသေးပါ။ အချိန်: %s" - "%s: %s သို့ စာတိုပို့ နေသည်။အချိန်: %s" - "%s: %s သို့စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: %s" - "%s: %s သို့ စာတိုပို့နေသည်။ အချိန်: %s" - "စာတိုပို့ခြင်းမအောင်မြင်ပါ။ ပြန်လည်ကြိုးစားရန် ထိပါ။" - "%sနှင့် စကားဝိုင်းများ" - "အကြောင်းအရာကို ဖျက်ရန်" - "ဗီဒီယို ဖမ်းယူရန်" - "ပုံသေ တစ်ခုကို ရိုက်ယူရန်" - "ပုံ ရိုက်ယူရန်" - "ဗီဒီယို ဖမ်းယူမှု စတင်ရန်" - "မျက်နှာပြင် အပြည့် ကင်မရာသို့ ပြောင်းရန်" - "ကင်မရာ အရှေ့ဘက်နှင့် အနောက်ဘက် ပြောင်းရန်" - "မှတ်တမ်းတင်မှုရပ်ကာ ဗီဒီယို ချိတ်ရန်" - "ဗွီဒီယိုရိုက်ကူးခြင်း ရပ်ရန်" - "ဓာတ်ပုံများကို စာတိုပို့ခြင်း" - - %d ဓါတ်ပုံများကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - %d ဓါတ်ပုံကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - - - %d ဗီဒီယိုများကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - %d ဗီဒီယိုကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - - - %d ပူးတွဲပါဖိုင်များကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - %d ပူးတွဲပါဖိုင်ကို \"%s\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ - - - %d ပူးတွဲပါဖိုင်များကို \"ဒေါင်းလုဒ်များ\" သို့ သိမ်းဆည်းပြီ:ပါပြီ - %d ပူးတွဲပါဖိုင်ကို \"ဒေါင်းလုဒ်များ\" သို့ သိမ်းဆည်းပြီးပါပြီ - - - ပူးတွဲပါဖိုင် %d ဖိုင် သိမ်းဆည်းပြီး၏ - ပူးတွဲပါဖိုင် %d ဖိုင် သိမ်းဆည်းပြီး၏ - - - ပူးတွဲပါဖိုင် %d အား မသိမ်းဆည်းနိုင်ခဲ့ပါ - ပူးတွဲပါဖိုင် %d ဖိုင်အား မသိမ်းဆည်းနိုင်ခဲ့ပါ - - "သိမ်းဆည်းထားသည့် MMS ပူးတွဲချက်" - "ဆက်တင်များ" - "မော်ကွန်းတင်ပြီး" - "ပိတ်ရန်" - "ရုပ်သံစာ" - "အဆင့်မြင့်" - "ဒီဘာဂ်" - "အကြောင်းကြားချက်များ" - "အသံ" - "အသံတိတ်ရန်" - "တုန်ခါရန်" - "ပိတ်ဆို့ထား" - "SMS ဖြန့်ဝေမှု အစီရင်ခံစာများ" - "သင် ပို့လိုက်သော စာတိုင်း အတွက် ပို့ပြီးကြောင်း အစီရင်ခံစာကို တောင်းဆိုမည်" - "အလိုအလျောက် ပြန်လည်ရယူမည်" - "MMS စာများကို အလိုအလျောက် ရယူရန်" - "ရုန်းမင်းလုပ်စဉ် အလိုအလျောက် ပြန်လည်ထုတ်ယူမည်" - "ရုန်းမင်းသုံးစဉ် MMS အလိုအလျောက် ရယူရန်" - "အဖွဲ့လိုက် စာပို့ခြင်း" - "စာတစ်စောင်ပို့ရန် လက်ခံသူအမြောက်အမြားရှိနေလျှင် ရုပ်သံစာကို သုံးရန်" - "စာတိုပို့ရန်သုံး အပလီကေးရှင်း" - "စာတိုပို့ရန်သုံး အပလီကေးရှင်း" - - - "သင့် ဖုန်းနံပါတ်" - "မသိရ" - "အပြင်သို့စာပေးပို့သည့် အသံများ" - "စိုစွတ် SMS" - "စိုစွတ် SMS အချက်လက်အကြမ်းများကို ပြင်ပသိုလှောင်မှုဖိုင် သို့လက်ခံ ရရှိပါသည်" - "စိုစွတ် MMS" - "စိုစွတ် MMS အချက်လက်အကြမ်းများကို ပြင်ပသိုလှောင်မှုဖိုင် သို့လက်ခံ ရရှိပါသည်" - "ကြိုးမဲ့ သတိပေးချက်များ" - "စာ အတွက် ရွေးစရာများ" - "စာကူးယူရန်" - "အသေးစိတ်များကို ကြည့်မည်" - "ဖျက်ရန်" - "တစ်ဆင့်ပို့ရန်" - "စာသေးစိတ်များ" - "အမျိုးအစား: " - "စာတို" - "မာလ်တီမီဒီယာ စာ" - "မှ: " - "သို့: " - "ပို့ပြီး: " - "လက်ခံရရှိခဲ့: " - "အကြောင်းအရာ: " - "အရွယ်အစား: " - "ဦးစားပေးမှု: " - "ဆင်းမ်: " - "မြင့်" - "ပုံမှန်" - "နိမ့်" - "ဆင်းမ် %s" - "ဝှက်ထားသော စာပို့သူ လိပ်စာ" - "ပူးတွဲချက်များဖွင့်စဉ် စာပို့၍မရပါ။" - "ပူးတွဲဖိုင် မရယူနိုင်ပါ။ ထပ်မံကြိုးစားပါ။" - "ကွန်ရက်အဆင်သင့် မဖြစ်သေးပါ။ ထပ်မံကြိုးစားပါ။" - "စာသားကို ဖျက်ရန်" - "စာသား နှင့် ဂဏန်းများ ရိုက်ထည့်မှု အကြားမှာ ပြောင်းလဲရန်" - "ပါဝင်သူများထပ်ထည့်မည်" - "ပါဝင်သူများ အတည်ပြုရန်" - "ပြောဆိုမှု အသစ် စတင်ရန်" - "ဒီအရာကို ရွေးရန်" - "ဗီဒီယို ဖွင့်ရန်" - "လူများ & ရွေးစရာများ" - "ဒီဘာဂ်" - "လူများ & ရွေးစရာများ" - "အထွေထွေ" - "ဒီစကားဝိုင်း ထဲက လူများ" - "ခေါ်ဆိုမှု ပြုလုပ်ရန်" - "စာတို ပို့ရန်" - "စာတိုကို<br/><> %s</small> ထံမှပို့ရန်" - - ဓာတ်ပုံများ ပို့ရန် - ဓာတ်ပုံ ပို့ရန် - - - အသံဖိုင်များ ပို့ရန် - အသံဖိုင် ပို့ရန် - - - ဗီဒီယိုများ ပို့ရန် - ဗီဒီယို ပို့ရန် - - - အဆက်အသွယ်ကဒ်များ ပို့ရန် - အဆက်အသွယ်ကဒ် ပို့ရန် - - - ပူးတွဲပါဖိုင်များ ပို့ရန် - ပူးတွဲပါဖိုင် ပို့ရန် - - - ပူးတွဲဖိုင် %d ဖိုင် ပို့ရန် အဆင်သင့်ဖြစ်၏ - ပူးတွဲဖိုင်တစ်ဖိုင် ပို့ရန် အဆင်သင့်ဖြစ်၏ - - "တုံ့ပြန်ချက် ပို့ရန်" - "Google ပလေး စတိုးတွင် ကြည့်ရန်" - "ဗားရှင်း အင်ဖို" - "ဗားရှင်း %1$s" - "အခမဲ့ ရင်းမြစ် လိုင်စင်များ" - "အကြောင်းကြားချက်များ" - "ချိတ်တွဲမှု ကန့်သတ်ချက်သို့ရောက်ရှိပါပြီ" - "ချိတ်တွဲမှု ဖွင့်ရန်မအောင်မြင်ပါ။" - "အဆက်အသွယ်များသို့ ထည့်ရမလား?" - "အဆက်အသွယ် ထည့်ရန်" - "ဘာသာရပ်" - "အကြောင်းအရာ: " - "%s%s" - "အဆက်အသွယ် ကဒ်ကို တင်ပေးနေ" - "နာမည်ကဒ် မရယူနိုင်ပါ" - "အဆက်အသွယ် ကဒ်ကို ကြည့်ရန်" - - %d အဆက်အသွယ် - %d အဆက်အသွယ် - - "နာမည် ကဒ်များ" - "မွေးနေ့" - "မှတ်စုများ" - "စာကို တစ်ဆင့်ပို့ရန်" - "စာပြန်ရန်" - "+၁" - "+%d" - "SMS ပိတ်ထား" - "ပို့ရန်အတွက် စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ပေးပါ" - "စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ရန်" - "ပြောင်းရန်" - "စာတိုများကို လက်ခံရန်အတွက်၊ စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ပေးပါ" - "SMS စာများ ပို့ရန်အတွက် ဦးစားပေး ဆင်းမ်ကို မရွေးရသေးပါ" - "ဒီappကို ကိရိယာ ပိုင်ရှင်က ခွင့် မပြုပါ။" - "အိုကေ" - "စကားဝိုင်း တစ်ခုထဲမှာ ပါဝင်သူ များလွန်းနေ" - - အဆက်အသွယ်များ မမှန်ကန်ပါ - အဆက်အသွယ် မမှန်ကန်ပါ - - "ကင်မရာ ပုံ မရယူနိုင်ပါ" - "သင်: " - "%s: " - "မူကြမ်း" - "စကားပြောစတင်သည်နှင့်၊ ဤစာရင်းတွင် ပေါ်လာသည်ကို သင်မြင်ရလိမ့်မည်။" - "ဤတွင်မြင်ရသည့် ပြောဆိုမှုများအား သိမ်းဆည်းရန်" - "ပြောဆိုမှုများကို တင်နေ..." - "ပုံ" - "အသံ ကလီပ်" - "ဗီဒီယို" - "အဆက်အသွယ် ကဒ်" - "MMS" - "ပြန်ဖျက်ရန်" - "ထပ်စမ်းရန်" - "စာတို အသစ် စပို့ရန် အမည် သို့မဟုတ် ဖုန်းနံပါတ် ရိုက်ထည့်ပါ" - "ပိတ်ဆို့ရန်" - "အဆက်အသွယ်ဖြတ်ရန် %s" - "အဆက်အသွယ်ပြန်လုပ် %s" - "ပိတ်ဆို့ရမလား%s?" - "ဤနံပါတ်မှ စာတိုများ သင် ဆက်လက်ရရှိနေမည်ဖြစ်သော်လည်း အကြောင်းကြားချက်ပေးပို့တော့မည် မဟုတ်ပါ။ ဤပြောဆိုမှုများအား သိုလှောင်သိမ်းဆည်းထားမည်ဖြစ်၏။" - "အဆက်အသွယ် ဖြတ်ပြီး" - "အဆက်အသွယ်ပြန်လုပ်" - "အဆက်အသွယ် ဖြတ်ပြီး" - "စာကြည့်တိုက်မှ ပုံရွေးရန်" - "စာတိုပို့နေသည်" - "စာတိုပို့ပြီးပါပြီ" - "ဆဲလူလာ ဒေတာ ပိတ်ထား၏။ ဆက်တင်တွင် စစ်ဆေးပါ။" - "လေယာဉ်ပျံပေါ်သုံးစနစ်တွင် စာများ မပို့နိုင်ပါ" - "စာများ ပို့၍မရပါ။" - "စာတိုအားဒေါင်းလုတ်လုပ်ပြီး" - "ဆဲလူလာ ဒေတာ ပိတ်ထား၏။ ဆက်တင်တွင် စစ်ဆေးပါ။" - "လေယာဉ်ပျံမုဒ်တွင် စာတိုများကို ဒေါင်းလုပ်လုပ်၍မရနိုင်ပါ" - "စာတိုအား ဒေါင်းလုပ်လုပ်၍မရပါ" - "သုည" - "တစ်" - "နှစ်" - "သုံး" - "လေး" - "ငါး" - "ခြောက်" - "ခုနစ်" - "ရှစ်" - "ကိုး" - "%1$sဖြင့် စာတိုမပို့နိုင်ပါ၊ အမှား %2$d" - "အမည်မသိသယ်ဆောင်သူဖြင့် စာတိုမပို့နိုင်ပါ၊ အမှား %1$d" - "တစ်ဆင့်ပို့: %s" - "စာတိုကို မပို့ခဲ့: ဝန်ဆောင်မှုကို ကွန်ရက် ပေါ်မှာ စဖွင့်မသုံးရသေး" - "စာမပို့နိုင်ပါ − လက်ခံယူမည့် လိပ်စာ မှားနေ၏" - "စာမပို့နိုင်ပါ− စာ မမှန်ကန်ပါ။" - "စာမပို့နိုင်ပါ − ထောက်ပံ့မထားသော အကြောင်းအရာဖြစ်၏" - "စာမပို့နိုင်ပါ− ထောက်ပံ့မထားသော စာဖြစ်၏" - "စာပိုဒ်အလွန်ရှည်ပါက မက်ဆေ့ချ် ပို့မရပါ။" - "စာ အသစ်" - "ကြည့်ရှုရန်" - "ပုံ" - "သင့်တော်သည့် အပလီကေးရှင်းကို ရှာမရခဲ့" - "အဝေးမှ လက်ခံရရှိသူ" - "စာ အသစ်" - "ဖျက်သိမ်းရန်" - "ဝင်ရောက်မည့်ပွိုင့်အား ပြင်ဆင်ရန်" - "မသတ်မှတ်ထားပါ" - "အမည်" - "APN" - "MMSC" - "MMS ပရော့က်စီ" - "MMS ပို့တ်နံပါတ်" - "MCC" - "MNC" - "APN အမျိုးအစား" - "APN ကိုဖျက်မည်" - "APN အသစ်" - "သိမ်းဆည်းရန်" - "ဖျက်သိမ်းရန်" - "အမည်အကွက်မှာ ကွက်လပ်ဖြစ်မနေစေရပါ" - "APN မှာ ကွက်လပ်ဖြစ်မနေစေရပါ" - "MNC အကွက်မှာ ဂဏန်း ၃ လုံးဖြစ်ရမည်" - "MNC အကွက်မှာ ဂဏန်း ၂ လုံး သို့မဟုတ် ၃ လုံးဖြစ်ရမည်" - "မူရင်း APN ဆက်တင်များကိုပြန်လည်ရယူစေမည်" - "စနစ်အား မူလစက်ရုံအခြအေနေထံ ပြန်လည်သတ်မှတ်ရန်" - "APN ၏မူရင်းအပြင်အဆင်များကို အစသို့ပြန်လည်စတင်မှုအား ပြီးဆုံး၏။" - "ခေါင်းစဉ်မဲ့" - "ချိတ်ဆက်သောပွိုင့်အမည်များ" - "APNs" - "APN အသစ်" - "ဤအသုံးပြုသူ အတွက် ချိတ်ဆက်သောပွိုင့်အမည်၏ ဆက်တင်များကို မရယူနိုင်" - "ကလီပ်ဘုတ်သို့ ကူးရမလား?" - "ကူးယူရန်" - "%sသို့" - "အထွေထွေ" - "အဆင့်မြင့်သော" - "အထွေထွေ ဆက်တင်များ" - "အဆင့်မြင့် ဆက်တင်များ" - "\"%s\" SIM" - "ရရှိသူ အားလုံးတို့ထံ တစ်ဦးချင်း SMS စာများကို ပို့ပါ။ သင်သည်သာလျှင် ​ဖြေကြားချက် မှန်သမျှကို ရရှိမည်။" - "ရရှိသူ အားလုံးထံသို့ တစ်ခုတည်းသော MMS ကို ပို့ပါ" - "မသိရ နံပါတ်" - "စာ အသစ်" - "စာ အသစ်" - "SIM ရွေးချယ်ပေးရေးလက္ခဏာရပ်" - "%1$s ရွေးချယ်ပြီး၊ SIM ရွေးချယ်ပေးသူ" - "အကြောင်းအရာကို တည်းဖြတ်ပါ။" - "SIM ကို‌ရွေးချယ်ပါ သို့မဟုတ် အကြောင်းအရာတည်းဖြတ်ခြင်းကိုရွေးချယ်ပါ။" - "အသံအား ဖမ်းယူထားရန် ထိပြီးဖိထားပါ" - "ပြောဆိုမှု အသစ် စတင်ရန်" - "စာတိုပို့ခြင်း" - "စာတိုပို့ခြင်း စာရင်း" - "စာတိုပို့ခြင်း" - "စာ အသစ်" - "ပြောဆိုမှုစာရင်း" - "စကားဝိုင်းများကို ဖွင့်နေသည်" - "စာများကို တင်နေ" - "စကားပြောဆက်သွယ်မှုများကို ထပ်ပြီး ကြည့်မည်" - "နောက်ထပ် မက်ဆေ့များကြည့်ရန်" - "စကားဝိုင်းကို ဖျက်လိုက်ပြီ" - "ပြောဆိုမှုကို ဖျက်ခဲ့သည်။ အခြား စာတိုပို့ခြင်း ပြောဆိုမှုကို ပြရန် တို့ထိပါ။" - "ပိတ်ဆို့ထား" - "ပြန်ဖွင့်ထားခြင်း" - "သိုလှောင်ထားသည့်နေရာ နည်းနေပါသည်။ ဒေတာတချို့ပျောက်ဆုံးပါလိမ့်မည်။" - "ချိတ်တွဲမှုများ ရွေးချယ်မည်" - "ရွေးချယ်မှုကို အတည်ပြုရန်" - "%d ခရွေးချယ်ပြီး" - "ကျေးဇူးပြု၍ တစ်ခု သို့မဟုတ် တစ်ခုထက်ပိုသော ချိတ်တွဲမှုများကို ဖယ်ရှားပြီး နောက်တစ်ကြိမ်ကြိုးစားပါ။" - "သင့်စာတိုကို ပေးပို့ရန် သင်ကြိုးစားနိုင်ပါသည်၊ သို့သော်သင်သည် တစ်ခုသို့မဟုတ် တစ်ခုထက်ပိုသော ချိတ်တွဲမှုများကို ဖယ်ရှားခြင်းမပြုပါက ၎င်းကို ပေးပို့မှုမရှိခြင်း ဖြစ်နိုင်သည်။" - "သင်သည် မက်ဆေ့ဂျ်တစ်ခုလျှင် ဗွီဒီယိုတကားသာ ပေးပို့နိုင်သည်။ အပိုဆောင်း ဗွီဒီယိုများအား ဖယ်ရှားပြီး ထပ်မံကြိုးစားပါ။" - "စာတိုကိုပို့စဉ် ပူးတွဲချက်ကို တင် မရခဲ့ပါ။" - "မည်သို့ပင်ဖြစ်စေ ပို့မည်" - "စကားဝိုင်းအား မစတင်နိုင်ပါ" - "%1$s (%2$s)" - "%s ရွေးချယ်ပြီး၏" - diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml deleted file mode 100644 index c9efc6f..0000000 --- a/res/values-nb/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "emne mangler" - "ikke noe emne" - - - "Ja" - "Nei" - "OK" - "Hehe" - "Takk" - "Jeg er enig" - "Bra" - "Jeg er på vei" - "OK, jeg tar kontakt med deg senere" - ":)" - ":(" - - diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml deleted file mode 100644 index 7736c84..0000000 --- a/res/values-nb/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Meldinger" - "Meldinger" - "Markér samtalen" - "Innstillinger" - "Send meldingen" - "Legg ved et vedlegg" - "Hjelp" - "Velkommen" - "Hopp over" - "Neste >" - "Neste" - "Avslutt" - "Innstillinger >" - "Innstillinger" - "Meldinger trenger tillatelse til å bruke SMS, Telefon og Kontakter." - "Du kan endre tillatelser i Innstillinger > Apper > Meldinger > Tillatelser." - "Du kan endre tillatelser i Innstillinger > Apper > Meldinger > Tillatelser." - "Ofte brukte" - "Alle kontakter" - "Send til %s" - "Ta bilder eller spill inn video" - "Velg bilder fra denne enheten" - "Spill inn lyd" - "Velg bilde" - "Mediene er valgt." - "Valget av mediene er fjernet." - "%d valgt" - "bilde %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "bilde" - "Spill inn lyd" - "Del" - "Nå nettopp" - "Nå" - - %d minutter - %d minutt - - - %d timer - %d time - - - dager - %d dag - - - %d uker - én uke - - - %d måneder - én måned - - - %d år - ett år - - "Klasse 0-melding" - "Lagre" - "Enheten har lite lagringsplass. Meldinger-appen sletter eldre meldinger automatisk for å frigjøre lagringsplass." - "Lite ledig lagringsplass" - "Du kan kanskje ikke sende eller motta SMS i Meldinger før mer plass er tilgjengelig på enheten din." - "Du har lite lagringsplass for SMS. Du må slette andre meldinger." - "Bekreft telefonnummeret ditt" - "Dette engangstrinnet sikrer at Meldinger leverer gruppemeldingene dine på riktig måte." - "Telefonnummer" - "Slett alle meldinger med medier" - "Slett meldinger som er eldre enn %s" - "Slett meldinger som er eldre enn %s automatisk" - "Ignorer" - "Vil du slette alle meldinger med media?" - "Vil du slette meldinger som er eldre enn %s?" - "Vil du slette meldinger som er eldre enn %s og slå av automatisk sletting?" - "%s sa" - "Du sa" - "Melding fra %s:" - "Du sendte en melding" - "Sender ..." - "Ikke sendt. Trykk for å prøve på nytt." - "Ikke sendt. Prøver på nytt …" - "Send på nytt eller slett" - "Ring til alarmsentralen. Tekstmeldingen kunne ikke leveres for øyeblikket." - "Mislykket" - "Ny MMS-melding for nedlasting" - "Ny MMS-melding" - "Kan ikke laste ned" - "Trykk for å prøve på nytt" - "Trykk for å laste ned" - "Last ned eller slett" - "Laster ned …" - "Meldingen er utløpt eller ikke tilgjengelig" - "størrelse: %1$s, utløper: %2$s" - "Kan ikke sende. Mottaker er ikke gyldig." - "Tjenesten er ikke aktivert på nettverket" - "Kunne ikke sende på grunn av et nettverksproblem" - "Meldingen er utløpt eller ikke tilgjengelig" - "(Mangler emne)" - "Ukjent avsender" - "Levert" - "Kunne ikke laste ned meldingen %1$s fra %2$s." - "Databasehandlingen kunne ikke fullføres på grunn av lite minne." - "Meldingen er ikke sendt" - "Enkelte meldinger er ikke sendt i Meldinger" - - %d meldinger i %d samtaler - %d meldinger i én samtale - - "Meldingen er ikke lastet ned" - "Enkelte meldinger er ikke lastet ned i Meldinger" - - %d meldinger i %d samtaler - %d meldinger i én samtale - - "Melding til %1$s ble ikke sendt" - "Ring til alarmsentralen. Tekstmeldingen til %1$s kunne ikke leveres for øyeblikket." - - %d nye meldinger - Ny melding - - "Start" - "Kameraet er ikke tilgjengelig" - "Kameraet er ikke tilgjengelig" - "Videoinnspilling er ikke tilgjengelig" - "Kan ikke lagre media" - "Kan ikke ta bilde" - "Tilbake" - "Arkivert" - "Slett" - "Arkivér" - "Fjern fra arkivet" - "Slå av varsler" - "Slå på varsler" - "Legg til kontakt" - "Last ned" - "Send" - "Slett" - "Vil du slette denne meldingen?" - "Denne handlingen kan ikke angres." - "Slett" - - Vil du slette disse samtalene? - Vil du slette denne samtalen? - - "Slett" - "Avbryt" - "Til" - "Velg flere bilder" - "Bekreft utvalget" - "+%d" - "Kan ikke ta opp lyd. Prøv på nytt." - "Kan ikke spille av lyd. Prøv på nytt." - "Kunne ikke lagre lydfil. Prøv på nytt." - "Berør og hold nede" - ", " - " " - ": " - " " - "Bilde" - "Lydklipp" - "Video" - "Kontaktkort" - "Last ned" - "Svar via SMS" - "Svar via MMS" - "Svar" - - %d deltakere - %d deltaker - - "Meg" - "Kontakten er blokkert og arkivert" - "Kontakt ublokkerte & uarkiverte" - "%d er arkivert" - "%d er fjernet fra arkivet" - "Varsler er slått av" - "Varsler er slått av" - "Alt klart. Trykk på Send én gang til." - "Meldinger er nå angitt som standard SMS-app." - - Forkast vedlegg - Forkast vedlegget - - "Lydvedlegg" - "Spill av lydvedlegg" - "Sett på pause" - "Melding fra %s: %s." - "Mislykket melding fra %s: %s. Tidspunkt: %s." - "Melding fra %s: %s. Tidspunkt: %s." - "Usendt melding til %s: %s. Tidspunkt: %s." - "Sender melding til %s: %s. Tidspunkt: %s." - "Mislykket melding til %s: %s. Tidspunkt: %s." - "Melding til %s: %s. Tidspunkt: %s." - "Mislykket melding fra %s: %s. Tidspunkt: %s. %s." - "Melding fra %s: %s. Tidspunkt: %s. %s." - "Usendt melding til %s: %s. Tidspunkt: %s." - "Sender melding til %s: %s. Tidspunkt: %s." - "Mislykket melding til %s: %s. Tidspunkt: %s." - "Melding til %s: %s. Tidspunkt: %s." - "Meldingen ble ikke sendt. Trykk for å prøve på nytt." - "Samtale med %s" - "Slett emnet" - "Ta video" - "Ta et stillbilde" - "Ta bilde" - "Start et videoopptak" - "Bytt til fullskjermkamera" - "Bytt mellom for- og baksidekamera" - "Stopp opptak og legg ved video" - "Slutt å filme" - "Bilder i Meldinger" - - %d bilder er lagret i albumet «%s» - %d bilde er lagret i albumet «%s» - - - %d videoer er lagret i albumet «%s» - %d video er lagret i albumet «%s» - - - %d tillegg er lagret i albumet «%s» - %d tillegg er lagret i albumet «%s» - - - %d tillegg er lagret i «Nedlastinger» - %d tillegg er lagret i «Nedlastinger» - - - %d vedlegg er lagret - %d vedlegg er lagret - - - Kunne ikke lagre %d vedlegg - Kunne ikke lagre %d vedlegg - - "MMS-vedlegget er lagret" - "Innstillinger" - "Arkivert" - "Lukk" - "MMS" - "Avansert" - "Feilsøk" - "Varsler" - "Lyd" - "Stille" - "Vibrer" - "Blokkert" - "Leveringsrapporter for SMS" - "Be om leveringsrapport for hver SMS du sender" - "Automatisk henting" - "Hent MMS-meldinger automatisk" - "Automatisk henting under roaming" - "Hent MMS automatisk ved romaning" - "Gruppemeldinger" - "Bruk MMS for å sende én melding med flere mottakere" - "Standard SMS-app" - "Standard SMS-app" - - - "Telefonnummeret ditt" - "Ukjent" - "Lyder for utgående meldinger" - "Dump SMS" - "Dump rådata fra mottatte SMS-er i en ekstern lagringsfil" - "Dump MMS" - "Dump rådata fra mottatte MMS-er i en ekstern lagringsfil" - "Trådløse varsler" - "Meldingsalternativer" - "Kopiér teksten" - "Se detaljer" - "Slett" - "Videresend" - "Meldingsinformasjon" - "Type: " - "SMS" - "Bildemelding" - "Fra: " - "Til: " - "Sendt: " - "Mottatt: " - "Emne: " - "Størrelse: " - "Prioritet: " - "SIM-kort: " - "Høy" - "Vanlig" - "Lav" - "SIM-kort %s" - "Skjult avsenderadresse" - "Meldingen kan ikke sendes mens vedleggene lastes inn." - "Kan ikke laste inn vedlegg. Prøv på nytt." - "Nettverket er ikke klart. Prøv på nytt." - "Slett teksten" - "Veksle mellom å skrive inn tekst og tall" - "Legg til flere deltakere" - "Bekreft deltakere" - "Start ny samtale" - "Velg dette elementet" - "Spill av videoen" - "Personer-appen og alternativer" - "Feilsøk" - "Personer-appen og alternativer" - "Generelt" - "Folk i denne samtalen" - "Start en samtale" - "Send melding" - "Send melding<br/><small>fra %s</small>" - - Send bilder - Send bilde - - - Send lydklipp - Send lydklipp - - - Send videoer - Send video - - - Send kontaktkort - Send kontaktkort - - - Send vedlegg - Send vedlegg - - - %d vedlegg er klare til å sendes - Ett vedlegg er klart til å sendes - - "Send tilbakemelding" - "Se i Google Play-butikken" - "Versjonsinformasjon" - "Versjon %1$s" - "Lisenser for åpen kildekode" - "Varsler" - "Vedleggsgrensen er nådd" - "Kunne ikke laste inn vedlegg." - "Vil du legge til nummeret i kontakter?" - "Legg til kontakten" - "Emne" - "Emne: " - "%s%s" - "Laster inn kontaktkortet" - "Kunne ikke laste kontaktkort" - "Se kontaktkortet" - - %d kontakter - %d kontakt - - "Kontaktkort" - "Fødselsdag" - "Notater" - "Videresend meldingen" - "Svar" - "1" - "+%d" - "SMS er deaktivert" - "For å sende meldinger, angi Meldinger som standard SMS-app" - "Angi Meldinger som standard SMS-app" - "Endre" - "For å motta meldinger, angi Meldinger som standard SMS-app" - "Ingen foretrukne SIM-kort er valgt for å sende SMS-meldinger" - "Denne appen er ikke tillatt av enhetseieren." - "OK" - "For mange deltakere i en samtale" - - Ugyldige kontakter - Ugyldig kontakt - - "Kunne ikke laste inn kamerabilde" - "Du: " - "%s: " - "Utkast" - "Når du starter en ny samtale, vises den her" - "Arkiverte samtaler vises her" - "Laster inn samtaler ..." - "Bilde" - "Lydklipp" - "Instream-video" - "Kontaktkort" - "MMS" - "Angre" - "Prøv på nytt" - "Skriv inn et kontaktnavn eller -telefonnummer for å starte en ny melding" - "Blokkér" - "Blokkér %s" - "Opphev blokkeringen av %s" - "Vil du blokkere %s?" - "Du fortsetter å motta meldinger fra dette nummeret, men blir ikke lenger varslet. Denne samtalen blir arkivert." - "Blokkerte kontakter" - "OPPHEV BLOKKERING" - "Blokkerte kontakter" - "Velg et bilde fra dokumentbiblioteket" - "Sender meldingen" - "Meldingen er sendt" - "Mobildata er slått av. Sjekk innstillingene dine." - "Kan ikke sende meldinger i flymodus" - "Kan ikke sende meldingen" - "Meldingen er nedlastet" - "Mobildata er slått av. Sjekk innstillingene dine." - "Kan ikke laste ned meldinger i flymodus" - "Meldingen kunne ikke lastes ned" - "Null" - "Én" - "To" - "Tre" - "Fire" - "Fem" - "Seks" - "Sju" - "Åtte" - "Ni" - "Kan ikke sende meldinger med %1$s – feil %2$d" - "Kan ikke sende meldinger med ukjent operatør – feil %1$d" - "V.send: %s" - "Meldingen er ikke sendt: Tjenesten er ikke aktivert på nettverket" - "Meldingen ble ikke sendt: ugyldig måladresse" - "Meldingen ble ikke sendt: ugyldig melding" - "Meldingen ble ikke sendt: innholdet støttes ikke" - "Meldingen ble ikke sendt: meldingen støttes ikke" - "Meldingen ble ikke sendt: Den var for stor." - "Ny melding" - "Se" - "Bilde" - "Kunne ikke finne en passende app" - "Fjern mottakeren" - "Ny melding" - "Avbryt" - "Rediger tilgangspunkt" - "Ikke angitt" - "Navn" - "APN-navn" - "MMSC" - "Proxy-tjener for MMS" - "MMS-port" - "MCC" - "MNC" - "APN-type" - "Slett APN" - "Ny APN" - "Lagre" - "Forkast" - "Navnefeltet kan ikke være tomt." - "APN kan ikke være tomt." - "MCC-feltet må bestå av tre siffer." - "MNC-feltet må bestå av to eller tre siffer." - "Gjenoppretter standard APN-innstillinger." - "Tilbakestill til standard" - "Tilbakestilling av standard APN-innstillinger er fullført." - "Uten navn" - "Navn på tilgangspunkt" - "APN-er" - "Ny APN" - "Innstillingene for navn på tilgangspunkt er ikke tilgjengelig for denne brukeren" - "Vil du kopiere til utklippstavlen?" - "Kopiér" - "til %s" - "Generelt" - "Avansert" - "Generelle innstillinger" - "Avanserte innstillinger" - %s»-SIM-kort" - "Send SMS-meldinger til alle mottakere. Bare du mottar svarene." - "Send én enkelt MMS til alle mottakere" - "Ukjent nummer" - "Ny melding" - "Ny melding." - "SIM-kortvelger" - "%1$s er valgt – SIM-kortvelger" - "Rediger emne" - "Velg SIM-kort eller rediger emnet" - "Trykk og hold inne for å ta opp lyd" - "Start ny samtale" - "Meldinger" - "Samtaleliste" - "Meldinger" - "Ny melding" - "Samtaleliste" - "Laster inn samtaler" - "Laster inn e-postmeldinger" - "Se flere samtaler" - "Se flere e-poster" - "Samtalen er slettet" - "Samtalen er slettet. Trykk for å se en annen samtale i Meldinger" - "Blokkert" - "Blokkeringen er opphevet" - "Lite lagringsplass ledig. Noen data kan gå tapt." - "Velg vedlegg" - "Bekreft utvalget" - "%d er valgt" - "Fjern ett eller flere vedlegg og prøv på nytt." - "Du kan prøve å sende meldingen din, men det er ikke sikkert at den kommer frem med mindre du fjerner ett eller flere vedlegg." - "Du kan bare sende én video per melding. Fjern overflødige videoer og prøv igjen." - "Meldinger kunne ikke laste inn vedlegget." - "Send likevel" - "Kunne ikke starte samtalen" - "%1$s (%2$s)" - "%s er valgt" - diff --git a/res/values-ne-rNP/arrays.xml b/res/values-ne-rNP/arrays.xml deleted file mode 100644 index 614436d..0000000 --- a/res/values-ne-rNP/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "शीर्षक नभएको" - "विषय छैन" - - - "हो" - "हैन" - "ठीक छ" - "हाहा" - "धन्यवाद" - "म सहमत छु" - "राम्रो" - "मेरो बाटोमा" - "ठीक छ, तपाईंलाई पछिको लागि मलाई अनुमति दिनुहोस्" - ":)" - ":(" - - diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml deleted file mode 100644 index c9e30e6..0000000 --- a/res/values-ne-rNP/strings.xml +++ /dev/null @@ -1,521 +0,0 @@ - - - - - "सन्देश पठाइदै" - "सन्देश पठाइदै" - "कुराकानी चयन गर्नुहोस्" - "सेटिङ्हरू" - "सन्देश पठाउनुहोस्" - "एक अनुलग्नक थप गर्नुहोस्" - "मद्दत" - "स्वागतम" - "छोड्नुहोस्" - "अर्को >" - "अर्को" - "बाहिर" - "सेटिङहरू >" - "सेटिङहरू" - "सन्देश प्रवाहलाई SMS, फोन र सम्पर्क ठेगानाहरूमा अनुमतिको आवश्यक पर्छ।" - "तपाईँ अनुमतिहरूलाई सेटिङ अनुप्रयोगहरू > सन्देश प्रवाह > अनुमतिहरूमा परिवर्तन गर्न सक्नु हुनेछ।" - "तपाईँ अनुमतिहरूलाई सेटिङ अनुप्रयोगहरू, सन्देश प्रवाह, अनुमतिहरूमा परिवर्तन गर्न सक्नु हुनेछ।" - "नियमित" - "सबै सम्पर्कहरू" - "%s मा पठाउनुहोस्" - "तस्बिर वा भिडियो लिनुहोस्" - "यस उपकरणबाट तस्बिरहरू छान्नुहोस्" - "ध्वनि रेकर्ड गर्नुहोस्" - "फोटो छान्नुहोस्" - "यस मिडिया चयन गरिएको छ।" - "यस मिडियाको चयन हटाइएको छ।" - "%d चयन गरियो" - "छवि %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "छवि" - "ध्वनि रेकर्ड गर्नुहोस्" - "साझेदारी गर्नुहोस्" - "अहिले भर्खर" - "अब" - - %d मिनेट - %d मिनेट - - - %d घन्टा - %d घन्टा - - - %d दिन - %d दिन - - - %d हप्ता - एक हप्ता - - - %d महिना - एक महिना - - - %d वर्ष - एक वर्ष - - "वर्ग ० सन्देश" - "सुरक्षित गर्नुहोस्" - "यन्त्रमा ठाउँ कम छ। ठाउँ खाली गर्न सन्देश प्रवाहले पुराना सन्देशहरूलाई स्वचालित रूपमा मेट्ने छ।" - "भण्डारण ठाउँ सकिँदै छ" - "तपाईँको यन्त्रमा थप ठाउँ उपलब्ध नभएसम्म सन्देश प्रवाहले सन्देश पठाउन वा प्राप्त नगर्न सक्छ।" - "SMS भण्डारणमा कमी। तपाईँले सन्देशहरु हटाउनु पर्नेहुन्छ।" - "आफ्नो फोन नम्बर पुष्टि गर्नुहोस्" - "यो एक पटके चरणले सन्देश प्रवाहले तपाईँको समूह सन्देशहरू उचित रूपमा डेलिभर गर्ने छ भन्ने सुनिश्चित गर्ने छ।" - "फोन नम्बर" - "मीडियाको साथमा सबै सन्देशहरु मेटाउनुहोस्" - "%s भन्दा पुराना सन्देशहरुलाई मेट्नुहोस्" - "%s भन्दा पुरानो सन्देशहरू स्वत: मेट्नुहोस्" - "बेवास्ता गर्नुहोस्" - "मिडिया भएको सन्देशहरू मेट्ने?" - "%s भन्दा पुरानो सन्देशहरू मेट्ने?" - "%s भन्दा पुरानो सन्देशहरू मेटाउनुहुन्छ र स्वचालित-मेट्ने सक्षम गर्नुहुन्छ?" - "%s ले भन्नुभयो" - "तपाईंले भन्नुभयो" - "%s बाट सन्देश" - "तपाईंले एक सन्देश पठाउनुभयो" - "पठाउँदै..." - "पठाइएको छैन। पुन: प्रयास गर्न छुनुहोस्।" - "पठाइएको छैन। पुन: प्रयास गर्दै ..." - "पुन: पठाउनुहोस् वा मेट्नुहोस्" - "कृपया आकस्मिक सेवाहरूमा आवाज कल गर्नुहोस्। तपाईंको पाठ सन्देश यो समयमा डेलिभर हुन सक्दैन।" - "विफल भयो" - "डाउनलोड गर्न नयाँ MMS सन्देश" - "नयाँ MMS सन्देश" - "डाउनलोड गर्न सकिएन" - "पुनः प्रयास गर्न छुनुहोस्" - "डाउनलोड गर्न छुनुहोस्" - "डाउनलोड वा मेट्नुहोस्" - "डाउनलोड हुँदै..." - "सन्देशको अवधि सकियो वा उपलब्ध छैन" - "आकार: %1$s, समाप्ति: %2$s" - "पठाउन सकिएन। प्रापक मान्य छैन।" - "नेटवर्कमा सेवा सक्रिय छैन" - "नेटवर्कको समस्याको कारण पठाउन सकिएन" - "सन्देशको अवधि सकियो वा उपलब्ध छैन" - "(कुनै विषय छैन)" - "अज्ञात प्रेषक" - "पुर्याइयो" - "%2$s बाट सन्देश %1$s डाउनलोड गर्न सकिएन।" - "कम मेमोरीका कारण डेटाबेस कार्य पुरा गर्न सकिएन।" - "सन्देश पठाइएन" - "केही सन्देशहरू सन्देश प्रवाहमा पठाइएनन्" - - %d वार्तालापमा %d सन्देशहरू - एउटा वार्तालापमा %d सन्देशहरू - - "सन्देश डाउनलोड भएन" - "सन्देशहरू सन्देश प्रवाहमा डाउनलोड भएनन्" - - %d वार्तालापमा %d सन्देशहरू - एउटा वार्तालापमा %d सन्देशहरू - - "%1$s मा सन्देश पठाइएन" - "कृपया आकस्मिक सेवाहरूमा आवाज कल गर्नुहोस्। %1$s मा तपाईंको पाठ सन्देश यो समयमा डेलिभर हुन सक्दैन।" - - %d नयाँ सन्देशहरू - नयाँ सन्देश - - "सुरु गर्नुहोस्" - "क्यामेरा उपलब्ध छैन" - "क्यामेरा उपलब्ध छैन" - "भिडियो क्याप्चर उपलब्ध छैन" - "मिडिया बचत गर्न सकिँदैन" - "तस्वीर लिन सक्दैन" - "पछाडि" - "अभिलेख बनाइयो" - "मेटाउनुहोस्" - "अभिलेख" - "अभिलेखबाट निकाल्नुहोस्" - "सूचनाहरू बन्द गर्नुहोस्" - "सूचनाहरू सुचारु गर्नुहोस्" - "सम्पर्क थप्नुहोस्" - "डाउनलोड गर्नुहोस्" - "पठाउनुहोस्" - "मेटाउनुहोस्" - "यस सन्देश मेटाउने हो?" - "यो कार्य पूर्ववत् पार्न सकिँदैन।" - "मेटाउनुहोस्" - - यी वार्तालापहरू मेटाउने हो? - यो वार्तालाप मेटाउने हो? - - "मेटाउनुहोस्" - "रद्द गर्नुहोस्" - "प्रापक" - "बहु तस्बिर चयन गर्नुहोस्" - "चयन यकिन गर्नुहोस्" - "+%d" - "अडियो रेकर्ड गर्न सकिँदैन। पुन: प्रयास गर्नुहोस्।" - "अडियो बजाउन सकिएन। पुन: प्रयास गर्नुहोस्।" - "अडियो सुरक्षित गर्न सकेन। फेरी प्रयास गर्नुहोस्।" - "छुनुहोस् र होल्ड गर्नुहोस्" - ", " - " " - ": " - " " - "चित्र" - "ध्वनि क्लिप" - "भिडियो" - "सम्पर्क कार्ड" - "डाउनलोड गर्नुहोस्" - "SMS मार्फत जवाफ दिनुहोस्" - "MMS मार्फत जवाफ दिनुहोस्" - "जवाफ पठाउनुहोस्" - - %d सहभागीहरू - %d सहभागी - - "म" - "सम्पर्क अवरुद्ध; अभिलेख राखियो" - "सम्पर्क खुल्ला गरियो &amp अभिलेख हटाइयो" - "%dअभिलेख गरियो" - "%d अभिलेख हटाउनुहोस्" - "सूचनाहरू निष्क्रिय पारिए" - "सूचनाहरू सक्रिय पारिए" - "सबै ठीक छ। पुन: पठाउन छुनुहोस्।" - "सन्देश प्रवाह सफलतापूर्वक पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट भयो।" - - संलग्नकहरू खारेज गर्नुहोस् - संलग्नक खारेज गर्नुहोस् - - "अडियो संलग्नक" - "अडियो संलग्नक प्ले गर्नुहोस्" - "पज गर्नुहोस्" - "%s बाट प्राप्त सन्देश: %s।" - "%s बाट विफल सन्देश: %s. समय: %s" - "%s बाट सन्देश: %s. समय: %s" - "%s मा नपठाइएको सन्देश: %s. समय: %s" - "%s मा सन्देश पठाउँदै: %s. समय: %s" - "%s मा विफल सन्देश: %s. समय: %s" - "%s मा सन्देश: %s. समय: %s" - "%s बाट विफल सन्देश: %s. समय: %s. %s" - "%s बाट सन्देश: %s. समय: %s. %s" - "%s मा नपठाइएको सन्देश: %s. समय: %s" - "%s मा सन्देश पठाउँदै: %s. समय: %s" - "%s मा विफल सन्देश: %s. समय: %s" - "%s मा सन्देश: %s. समय: %s" - "असफल सन्देश। पुन: प्रयास गर्न छुनुहोस्।" - "%s सँग कुराकानी" - "विषय मेट्नुहोस्" - "भिडियो खिच्नुहोस्" - "स्थिर छाँया लिदै" - "फोटो लिनुहोस्" - "भिडियो रेकर्ड सुरु गर्नुहोस्" - "पूरा पर्दा क्यामेरा खोल्नुहोस्" - "अगाडि र पछाडि क्यामेरा बीच बदल्नुहोस्" - "भिडियो रेकर्ड गर्न र संलग्न रोक्नुहोस्" - "भिडियो रेकर्ड रोक्नुहोस्" - "सन्देश प्रवाह तस्बिरहरू" - - %d तस्बिरहरू \"%s \" एल्बममा सुरक्षित गरियो - %d तस्बिर \"%s \" एल्बममा सुरक्षित गरियो - - - %d भिडियोहरू \"%s\" एल्बममा सुरक्षित गरियो - %d भिडियो \"%s\" एल्बममा सुरक्षित गरियो - - - %d संलग्नकहरू \" %s \" एल्बममा सुरक्षित गरियो - %d संलग्नक \" %s \" एल्बममा सुरक्षित गरियो - - - %d संलग्नकहरू \"डाउनलोडहरू\" मा सुरक्षित गरियो - %d संलग्नक \"डाउनलोडहरू\" मा सुरक्षित गरियो - - - %d संलग्नकहरू सुरक्षित गरियो - %d संलग्नक सुरक्षित गरियो - - - %d संलग्नकहरू सुरक्षित गर्न सकेन - %d संलग्नक सुरक्षित गर्न सकेन - - "संलग्न MMS सुरक्षित गरियो" - "सेटिङहरू" - "अभिलेख बनाइयो" - "बन्द गर्नुहोस्" - "MMS" - "उन्नत" - "डिबग" - "सूचनाहरू" - "आवाज" - "मौन" - "कम्पन गर्नुहोस्" - "अवरूद्ध गरियो" - "SMS वितरण प्रतिवेदनहरू" - "तपाईँले पठाउनु हुने हरेक SMS को लागि पुगेको खबर अनुरोध गर्नुहोस्" - "स्वत:प्राप्ति" - "MMS सन्देशहरु स्वत: रूपमा पुनः प्राप्त गर्नुहोस्" - "रोमिङ स्वतः पुनः प्राप्ति" - "रोमिङमा हुँदा MMS स्वचालित रूपमा प्राप्त गर्नुहोस्" - "समूहिक सन्देश पठाइँदै" - "जब त्यहाँ बहुप्राप्तकर्ताहरू हुन्छन् तब सन्देश पठाउनको लागि MMS प्रयोग गर्नुहोस्।" - "पूर्वनिर्धारित SMS अनुप्रयोग" - "पूर्वनिर्धारित SMS अनुप्रयोग" - - - "तपाईँको फोन नम्बर" - "अज्ञात" - "बाहिर जाने सन्देशका लागि ध्वनिहरू" - "SMS फाल्नुहोस्" - "प्राप्त भएको MMS अप्रशोधित डेटा बाह्य भण्डारण फाइलमा फाल्नुहोस्" - "MMS फाल्दिनुहोस्" - "प्राप्त भएको MMS अप्रशोधित डेटा बाह्य भण्डारण फाइलमा फाल्नुहोस्" - "ताररहित सतर्कताहरू" - "सन्देश विकल्पहरू" - "पाठ प्रतिलिपि गर्नुहोस्" - "विवरणहरू हेर्नुहोस्" - "मेटाउनुहोस्" - "फर्वार्ड गर्नुहोस्" - "सन्देश विवरणहरू" - "प्रकार: " - "पाठ सन्देश" - "मल्टिमिडिया सन्देश" - "प्रेषक: " - "प्रापक: " - "पठाइयो: " - "प्राप्त: " - "विषय: " - "आकार: " - "प्राथमिकता: " - "SIM: " - "उच्च" - "सामान्य" - "कम" - "SIM %s" - "प्रेषकको ठेगाना लुकेको" - "संलग्नहरू लोड गर्दा सन्देश पठाउन सक्दैन।" - "संलग्नक लोड गर्न सकिएन। पुन: प्रयास गर्नुहोस्।" - "नेटवर्क तयार छैन। पुन: प्रयास गर्नुहोस्।" - "पाठ मेट्नुहोस्" - "पाठ र संख्या बीच प्रविष्टि गर्न स्विच गर्नुहोस्" - "थप सहभागी थप्नुहोस्" - "सहभागीहरूको पुष्टि गर्नुहोस्" - "नयाँ कुराकानी सुरू गर्नुहोस्" - "यो वस्तु चयन गर्नुहोस्" - "भिडियो चलाउनुहोस्" - "मानिस र विकल्पहरू" - "डिबग" - "मानिसहरू र विकल्पहरु" - "सामान्य" - "यस कुराकानीमा मानिसहरू" - "कल गर्नुहोस्" - "सन्देश पठाउनुहोस्" - "सन्देश पठाउनुहोस्<br/><small>%s</small&gt बाट" - - तस्बिरहरू पठाउनुहोस् - तस्बिर पठाउनुहोस् - - - अडियोहरू पठाउनुहोस् - अडियो पठाउनुहोस् - - - भिडियोहरू पठाउनुहोस् - भिडियो पठाउनुहोस् - - - सम्पर्क कार्डहरू पठाउनुहोस् - सम्पर्क कार्ड पठाउनुहोस् - - - संलग्नक पठाउनुहोस् - संलग्नक पठाउनुहोस् - - - %d संलग्नकहरू पठाउनको लागि तयार - एउटा संलग्नक पठाउनको लागि तयार - - "प्रतिक्रिया पठाउनुहोस्" - "Google Play Store मा हेर्नुहोस्" - "संस्करण जानकारी" - - - - "खुला स्रोत इजाजतपत्रहरू" - "अधिसूचनाहरू" - "संलग्नकको सीमा पुग्यो" - "संलग्नक लोड गर्न असफल।" - "सम्पर्कहरूमा थप्नुहुन्छ?" - "सम्पर्क थप्नुहोस्" - "विषय" - "विषय: " - "%s%s" - "सम्पर्क कार्ड लोड गर्दै" - "सम्पर्क कार्ड लोड गर्न सकिएन" - "सम्पर्क कार्ड हेर्नुहोस्" - - %d सम्पर्कहरू - %d सम्पर्क - - "सम्पर्क कार्डहरू" - "जन्मदिन" - "टिप्पणीहरू" - "सन्देश फर्वार्ड गर्नुहोस्" - "जवाफ पठाउनुहोस्" - "‌+१" - "+%d" - "SMS असक्षम गरियो" - "पठाउनका लागि सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्" - "सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्" - "परिवर्तन गर्नुहोस्" - "सन्देशहरू प्राप्त गर्न, सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्" - "SMS सन्देशहरू पठाउनको लागि मान्यता प्राप्त SIM चयन गरिएको छैन।" - "यो अनुप्रयोग उपकरण मालिकद्वारा अनुमति छैन।" - "ठीक छ" - "कुराकानीमा धेरै सहभागीहरू" - - अमान्य सम्पर्कहरू - अमान्य सम्पर्क - - "क्यामेराको तस्बिर लोड गर्न सकिएन" - "तपाईँ: " - "%s: " - "मस्यौदा" - "तपाईँ नयाँ कुराकानी सुरु भएपछि, तपाईँ यसलाई यहाँ सूचीबद्ध देख्नुहुने छ" - "अभिलेख कुराकानी यहाँ देखा पर्नेछ" - "कुराकानीहरू लोड हुँदै..." - "तस्बिर" - "अडियो क्लिप" - "भिडियो" - "सम्पर्क कार्ड" - "MMS" - "अनडु गर्नुहोस्" - "पुनःप्रयास गर्नुहोस्" - "सम्पर्क नाम वा फोन नम्बर प्रविष्ट गरी नयाँ सन्देश सुरु गर्नुहोस्" - "रोक्नुहोस्" - "अवरुद्ध %s" - "अनब्लक %s" - "अवरुद्ध गर्नुस् %s?" - "तपाईँ यो नम्बरबाट सन्देश प्राप्त गरिरहनु हुनेछ तर तपाईँलाई सूचीत गरिने छैन। यो कुराकानीलाई संग्रह गरिने छ।" - "अवरुद्ध सम्पर्कहरू" - "UNBLOCK" - "अवरुद्ध सम्पर्कहरू" - "कागजात लाइब्रेरीबाट तस्बिर छान्नुहोस्" - "सन्देश पठाउँदै" - "सन्देश पठाइयो" - "सेलुलर डेटा बन्द छ। आफ्नो सेटिङ जाँच गर्नुहोस्।" - "हवाइजहाज मोडमा सन्देश पठाउन सक्नुहुन्‍न्" - "सन्देश पठाउन सकिएन" - "सन्देश डाउनलोड गरियो" - "सेलुलर डाटा बन्द गरिएको छ। आफ्नो सेटिङहरू जाँच्नुहोस्।" - "हवाइजहाज मोडमा सन्देशहरू डाउनलोड गर्न सक्दैन" - "सन्देश डाउनलोड गर्न सकिएन" - "शून्य" - "एक" - "दुई" - "तीन" - "चार" - "पाँच" - "छ" - "सात" - "आठ" - "नौ" - "%1$s मार्फत सन्देश पठाउन सक्दैन, त्रुटि %2$d" - "अज्ञात वाहकमार्फत सन्देश पठाउन सक्दैन, त्रुटि %1$d" - "फर्वाड गर्नुहोस्: %s" - "सन्देश पठाइएको छैन: सञ्जालमा सेवा सक्रिय छैन" - "सन्देश पठाइएको छैन: अवैध गन्तव्य ठेगाना" - "सन्देश पठाइएको छैन: अमान्य सन्देश" - "सन्देश पठाइएको छैन: असमर्थित सामाग्री" - "सन्देश पठाइएको छैन: असमर्थित सन्देश" - "सन्देश पठाइएन: निकै ठूलो छ" - "नयाँ सन्देश" - "हेर्नुहोस्" - "तस्बिर" - "उपयुक्त अनुप्रयोग फेला पार्न सकिएन" - "प्राप्तकर्ता हटाउनुहोस्" - "नयाँ सन्देश" - "रद्द गर्नुहोस्" - "पहुँच बिन्दु सम्पादन गर्नुहोस्" - "सेट गरेको छैन" - "नाम" - "APN" - "MMSC" - "MMS प्रोक्सी" - "MMS पोर्ट" - "MCC" - "MNC" - "APN प्रकार" - "APN मेटाउनुहोस्" - "नयाँ APN" - "सुरक्षित गर्नुहोस्" - "त्याग्नुहोस्" - "नाम क्षेत्र खाली हुन सक्दैन।" - "APN खाली हुन सक्दैन।" - "MCC क्षेत्र कम्तिमा ३ अङ्कको हुनु पर्छ।" - "MNC क्षेत्रमा २ वा ३ अंकहरू हुनु पर्दछ।" - "पूर्वनिर्धारित APN सेटिङ्हरू पुनःप्राप्त गर्दै।" - "पूर्वनिर्धारितमा पुनःसेट गर्नुहोस्" - "पूर्वनिर्धारित APN सेटिङ्हरू पुनः सेट गर्ने काम पूरा भयो।" - "शीर्षक विहिन" - "पहुँच बिन्दु नामहरू" - "APNs" - "नयाँ APN" - "पहुँच बिन्दु नामको सेटिङ्हरू यो प्रयोगकर्ताको लागि उपलब्ध छैन" - "क्लिपबोर्डमा प्रतिलिपि गर्ने?" - "प्रतिलिपि गर्नुहोस्" - "%s लाई" - "सामान्य" - "उन्नत" - "साधारण सेटिङ्हरू" - "उन्नत सेटिङ्हरू" - "\"%s\" SIM" - "सबै प्रापकलाई व्यक्तिगत SMS सन्देशहरू पठाउनुहोस्। केवल तपाईँले कुनै पनि उत्तर प्राप्त गर्नुहुने छ" - "सबै प्रापकलाई एउटा एकल MMS पठाउनुहोस्" - "अज्ञात संख्या" - "नयाँ सन्देश" - "नयाँ सन्देश।" - "सिम चयनकर्ता" - "%1$s चयन गरियो, SIM चयनकर्ता" - "विषय सम्पादन गर्नुहोस्" - "सिम चयन गर्नुहोस् वा विषय सम्पादन गर्नुहोस्" - "अडियो रेकर्ड गर्न छुनुहोस् र होल्ड गर्नुहोस्" - "नयाँ कुराकानी सुरू गर्नुहोस्" - "सन्देश प्रवाह" - "सन्देश प्रवाह सूची" - "सन्देश प्रवाह" - "नयाँ सन्देश" - "कुराकानी सूची" - "कुराकानीहरू लोड हुँदै..." - "सन्देशहरू लोड हुँदै" - "थप कुराकानीहरू हेर्नुहोस्" - "थप सन्देशहरू हेर्नुहोस्" - "कुराकानी मेटिएको छ" - "वार्तालाप मेटियो। एउटा फरक सन्देश प्रवाह वार्तालाप देखाउन छुनुहोस्" - "अवरूद्ध गरियो" - "अवरोध हटाइयो" - "भण्डारण ठाउँ कम छ। केही डाटा हराउन सक्छ।" - "संलग्नकहरू चयन गर्नुहोस्" - "चयन पुष्टि गर्नुहोस्" - "%d चयन गरियो" - "कृपया एक वा बढी संलग्नकहरू हटाउनुहोस् र फेरि प्रयास गर्नुहोस्।" - "तपाईं आफ्नो सन्देश पठाउन प्रयास गर्न सक्नुहुन्छ, तर तपाईंले एक वा बढी संलग्नकहरू नहटाएसम्म यो डेलिभर नहुन सक्छ।" - "तपाईँले प्रति सन्देश एउटा मात्र भिडियो पठाउन सक्नुहुन्छ। अतिरिक्त भिडियो हटाउनुहोस् र फेरि प्रयास गर्नुहोस्।" - "संलग्नक लोड गर्न सन्देश प्रवाह असफल भयो।" - "जसरी भए पनि पठाउनुहोस्" - "कुराकानी सुरु गर्न सकेन" - "%1$s (%2$s)" - "%s चयन गरियो" - diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml deleted file mode 100644 index adfdcbe..0000000 --- a/res/values-nl/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "geen onderwerp" - "geenonderwerp" - - - "Ja" - "Nee" - "OK" - "Haha" - "Hartelijk dank" - "Ik ga akkoord" - "Mooi" - "Ik ben onderweg" - "Hier kom ik later op terug" - ":)" - ":(" - - diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml deleted file mode 100644 index 4f1a53c..0000000 --- a/res/values-nl/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Berichten" - "Berichten" - "Gesprek selecteren" - "Instellingen" - "Bericht verzenden" - "Een bijlage toevoegen" - "Help" - "Welkom" - "Overslaan" - "Volgende >" - "Volgende" - "Sluiten" - "Instellingen >" - "Instellingen" - "Berichten heeft toegang tot Sms, Telefoon en Contacten nodig." - "U kunt rechten wijzigen via \'Instellingen\' > \'Apps\' > \'Berichten\' > \'Rechten\'." - "U kunt rechten wijzigen via \'Instellingen\' > \'Apps\' > \'Berichten\' > \'Rechten\'." - "Veelgebruikt" - "Alle contacten" - "Verzenden naar %s" - "Foto\'s of video vastleggen" - "Afbeeldingen kiezen op dit apparaat" - "Audio opnemen" - "Foto kiezen" - "Het medium is geselecteerd." - "Het medium is gedeselecteerd." - "%d geselecteerd" - "afbeelding %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "afbeelding" - "Audio opnemen" - "Delen" - "Zojuist" - "Nu" - - %d min. - %d min. - - - %d uur - %d uur - - - %d dagen - %d dag - - - %d weken - één week - - - %d maanden - één maand - - - %d jaar - één jaar - - "Berichtklasse 0" - "Opslaan" - "Er is weinig ruimte vrij op het apparaat. Oudere berichten worden automatisch verwijderd om ruimte vrij te maken." - "Opslagruimte is bijna vol" - "Berichten verzendt of ontvangt mogelijk geen berichten totdat er meer ruimte beschikbaar is op uw apparaat." - "Weinig opslagruimte voor sms-berichten beschikbaar. Je moet mogelijk berichten verwijderen." - "Uw telefoonnummer bevestigen" - "Deze eenmalige stap zorgt ervoor dat Berichten uw groepsberichten correct aflevert." - "Telefoonnummer" - "Alle berichten met media verwijderen" - "Berichten ouder dan %s verwijderen" - "Berichten ouder dan %s automatisch verwijderen" - "Negeren" - "Alle berichten met media verwijderen?" - "Berichten ouder dan %s verwijderen?" - "Berichten ouder dan %s verwijderen en automatisch verwijderen inschakelen?" - "%s zei" - "U zei" - "Bericht van %s:" - "U heeft een bericht verzonden" - "Verzenden…" - "Niet verzonden. Tik om het opnieuw te proberen." - "Niet verzonden. Nieuwe poging…" - "Opnieuw verzenden of verwijderen" - "Bel de hulpdiensten. Uw sms kan momenteel niet worden afgeleverd." - "Mislukt" - "Nieuw mms-bericht downloaden" - "Nieuw mms-bericht" - "Kan niet downloaden" - "Tik voor nieuwe poging" - "Tik om te downloaden" - "Downloaden of verwijderen" - "Downloaden..." - "Bericht verlopen of niet beschikbaar" - "grootte: %1$s, verloopt: %2$s" - "Kan niet verzenden. Ontvanger niet geldig." - "Service niet geactiveerd in netwerk" - "Kan niet verzenden wegens een netwerkprobleem" - "Bericht verlopen of niet beschikbaar" - "(Geen onderwerp)" - "Onbekende afzender" - "Bezorgd" - "Kan bericht %1$s niet downloaden van %2$s." - "Kan databasebewerking niet voltooien wegens onvoldoende geheugen" - "Bericht niet verzonden" - "Sommige berichten niet verzonden in Berichten" - - %d berichten in %d gesprekken - %d berichten in één gesprek - - "Bericht niet gedownload" - "Sommige berichten zijn niet gedownload in Berichten" - - %d berichten in %d gesprekken - %d berichten in één gesprek - - "Bericht naar %1$s niet verzonden" - "Bel de hulpdiensten. Uw sms aan %1$s kan momenteel niet worden afgeleverd." - - %d nieuwe berichten - Nieuw bericht - - "Starten" - "Camera niet beschikbaar" - "Camera niet beschikbaar" - "Video-opname niet beschikbaar" - "Kan media niet opslaan" - "Kan geen foto maken" - "Terug" - "Gearchiveerd" - "Verwijderen" - "Archiveren" - "Terugzetten uit archief" - "Meldingen uitschakelen" - "Meldingen inschakelen" - "Contact toevoegen" - "Downloaden" - "Verzenden" - "Verwijderen" - "Dit bericht verwijderen?" - "Deze actie kan niet ongedaan worden gemaakt." - "Verwijderen" - - Deze gesprekken verwijderen? - Dit gesprek verwijderen? - - "Verwijderen" - "Annuleren" - "Aan" - "Meerdere afbeeldingen selecteren" - "Selectie bevestigen" - "+%d" - "Kan audio niet opnemen. Probeer het opnieuw." - "Kan audio niet afspelen. Probeer het opnieuw." - "Kan audio niet opslaan. Probeer het opnieuw." - "Blijven aanraken" - ", " - " " - ": " - " " - "Afbeelding" - "Audioclip" - "Video" - "Visitekaartje" - "Downloaden" - "Reageren via sms" - "Reageren via mms" - "Beantwoorden" - - %d deelnemers - %d deelnemer - - "Ik" - "Contact geblokkeerd en gearchiveerd" - "Blokkering van contact opgeheven en teruggezet uit archief" - "%d gearchiveerd" - "%d teruggezet uit archief" - "Meldingen uitgeschakeld" - "Meldingen ingeschakeld" - "Alles is ingesteld. Tik nogmaals op \'Verzenden\'." - "Berichten is ingesteld als de standaard sms-app" - - Bijlagen verwijderen - Bijlage verwijderen - - "Audiobijlage" - "Audiobijlage afspelen" - "Onderbreken" - "Bericht van %s: %s." - "Mislukt bericht van %s: %s. Tijd: %s." - "Bericht van %s: %s. Tijd: %s." - "Niet-verzonden bericht aan %s: %s. Tijd: %s." - "Bericht verzenden naar %s: %s. Tijd: %s." - "Mislukt bericht aan %s: %s. Tijd: %s." - "Bericht aan %s: %s. Tijd: %s." - "Mislukt bericht van %s: %s. Tijd: %s. %s." - "Bericht van %s: %s. Tijd: %s. %s." - "Niet-verzonden bericht aan %s: %s. Tijd: %s." - "Bericht verzenden naar %s: %s. Tijd: %s." - "Mislukt bericht aan %s: %s. Tijd: %s." - "Bericht aan %s: %s. Tijd: %s." - "Bericht mislukt. Tik om het opnieuw te proberen." - "Gesprek met %s" - "Onderwerp verwijderen" - "Video opnemen" - "Een stilstaand beeld opnemen" - "Foto maken" - "Video opnemen" - "Overschakelen naar camera op volledig scherm" - "Schakelen tussen camera aan voorzijde en camera aan achterzijde" - "Opname stoppen en video bijvoegen" - "Video-opname stoppen" - "Foto\'s in Berichten" - - %d foto\'s opgeslagen in het album \"%s\" - %d foto opgeslagen in het album \"%s\" - - - %d video\'s opgeslagen in het album \"%s\" - %d video opgeslagen in het album \"%s\" - - - %d bijlagen opgeslagen in het album \"%s\" - %d bijlage opgeslagen in het album \"%s\" - - - %d bijlagen opgeslagen in \"Downloads\" - %d bijlage opgeslagen in \"Downloads\" - - - %d bijlagen opgeslagen - %d bijlage opgeslagen - - - Kan %d bijlagen niet opslaan - Kan %d bijlage niet opslaan - - "Opgeslagen mms-bijlage" - "Instellingen" - "Gearchiveerd" - "Sluiten" - "Mms" - "Geavanceerd" - "Fouten opsporen" - "Meldingen" - "Geluid" - "Stil" - "Trillen" - "Geblokkeerd" - "Bezorgingsrapporten voor sms" - "Ontvangstbevestiging vragen voor elke verzonden sms" - "Automatisch ophalen" - "Mms-berichten automatisch ophalen" - "Automatisch ophalen tijdens roaming" - "Automatisch MMS ophalen tijdens roaming" - "Groepsberichten" - "Mms gebruiken om één bericht te verzenden wanneer er meerdere ontvangers zijn" - "Standaard sms-app" - "Standaard sms-app" - - - "Uw telefoonnummer" - "Onbekend" - "Geluiden voor uitgaand bericht" - "Sms dumpen" - "Onbewerkte gegevens voor ontvangen sms\'jes in een extern opslagbestand dumpen" - "Mms dumpen" - "Onbewerkte gegevens voor ontvangen mms\'jes in een extern opslagbestand dumpen" - "Waarschuwingen voor draadloos" - "Berichtopties" - "Tekst kopiëren" - "Details weergeven" - "Verwijderen" - "Doorsturen" - "Berichtdetails" - "Type: " - "Sms-bericht" - "Mms-bericht" - "Van: " - "Aan: " - "Verzonden: " - "Ontvangen: " - "Onderwerp: " - "Grootte: " - "Prioriteit: " - "Simkaart: " - "Hoog" - "Normaal" - "Laag" - "Simkaart %s" - "Verborgen adres verzender" - "Kan bericht niet verzenden terwijl bijlagen worden geladen." - "Kan bijlage niet laden. Probeer het opnieuw." - "Netwerk is niet gereed. Probeer het opnieuw." - "Tekst verwijderen" - "Schakelen tussen letters en cijfers invoeren" - "Meer deelnemers toevoegen" - "Deelnemers bevestigen" - "Nieuw gesprek starten" - "Dit item selecteren" - "Video afspelen" - "Mensen en opties" - "Fouten opsporen" - "Mensen en opties" - "Algemeen" - "Mensen in dit gesprek" - "Bellen" - "Bericht verzenden" - "Bericht verzenden<br/><small>via %s</small>" - - Foto\'s verzenden - Foto verzenden - - - Audio-opnamen verzenden - Audio-opname verzenden - - - Video\'s verzenden - Video verzenden - - - Contactkaarten verzenden - Contactkaart verzenden - - - Bijlagen verzenden - Bijlage verzenden - - - %d bijlagen klaar voor verzenden - Eén bijlage klaar voor verzenden - - "Feedback verzenden" - "Bekijken in de Google Play Store" - "Versie-informatie" - "Versie %1$s" - "Open-sourcelicenties" - "Meldingen" - "Bijlagelimiet bereikt" - "Kan bijlage niet laden." - "Toevoegen aan contacten?" - "Contact toevoegen" - "Onderwerp" - "Onderwerp: " - "%s%s" - "Contactkaart laden" - "Kan visitekaartje niet laden" - "Contactkaart weergeven" - - %d contacten - %d contact - - "Visitekaartjes" - "Verjaardag" - "Notities" - "Bericht doorsturen" - "Beantwoorden" - "+1" - "+%d" - "Sms uitgeschakeld" - "Stel Berichten in als standaard sms-app om berichten te verzenden" - "Berichten instellen als standaard sms-app" - "Wijzigen" - "Stel Berichten in als standaard sms-app om berichten te ontvangen" - "Geen voorkeurs-simkaart geselecteerd voor het verzenden van sms\'jes" - "Deze app is niet toegestaan door de eigenaar van het apparaat." - "OK" - "Te veel deelnemers in een gesprek" - - Ongeldige contacten - Ongeldig contact - - "Kan camera-afbeelding niet laden" - "U: " - "%s: " - "Concept" - "Zodra u een nieuw gesprek start, wordt dit hier vermeld" - "Gearchiveerde gesprekken worden hier weergegeven" - "Gesprekken laden..." - "Afbeelding" - "Audioclip" - "Video" - "Visitekaartje" - "Mms" - "Ongedaan maken" - "Opnieuw proberen" - "Geef de naam of het telefoonnummer van een contact op om een nieuw bericht te starten" - "Blokkeren" - "%s blokkeren" - "Blokkering van %s opheffen" - "%s blokkeren?" - "U blijft berichten van dit nummer ontvangen, maar krijgt geen meldingen meer. Dit gesprek wordt gearchiveerd." - "Geblokkeerde contacten" - "BLOKKERING OPHEFFEN" - "Geblokkeerde contacten" - "Afbeelding kiezen uit documentbibliotheek" - "Bericht wordt verzonden" - "Bericht verzonden" - "Mobiele gegevens zijn ingeschakeld. Controleer uw instellingen." - "Kan geen berichten verzenden in vliegtuigmodus" - "Kan bericht niet verzenden" - "Bericht gedownload" - "Mobiele gegevens zijn uitgeschakeld. Controleer uw instellingen." - "Kan geen berichten downloaden in de vliegtuigmodus" - "Bericht kan niet worden gedownload" - "Nul" - "Eén" - "Twee" - "Drie" - "Vier" - "Vijf" - "Zes" - "Zeven" - "Acht" - "Negen" - "Kan bericht niet verzenden via %1$s, fout %2$d" - "Kan bericht niet verzenden via onbekende provider, fout %1$d" - "Fwd: %s" - "Bericht niet verzonden: service niet geactiveerd in netwerk" - "Bericht niet verzonden: ongeldig bestemmingsadres" - "Bericht niet verzonden: ongeldig bericht" - "Bericht niet verzonden: niet-ondersteunde inhoud" - "Bericht niet verzonden: niet-ondersteund bericht" - "Bericht niet verzonden: te groot" - "Nieuw bericht" - "Weergeven" - "Afbeelding" - "Kan geen geschikte applicatie vinden" - "Ontvanger verwijderen" - "Nieuw bericht" - "Annuleren" - "Toegangspunt bewerken" - "Niet ingesteld" - "Naam" - "APN" - "MMSC" - "MMS-proxy" - "MMS-poort" - "MCC" - "MNC" - "APN-type" - "APN verwijderen" - "Nieuwe APN" - "Opslaan" - "Verwijderen" - "Het veld \'Naam\' mag niet leeg zijn." - "De APN mag niet leeg zijn." - "MCC-veld moet drie cijfers bevatten." - "MNC-veld moet twee of drie cijfers bevatten." - "Standaard-APN-instellingen herstellen." - "Standaardinstellingen herstellen" - "Herstellen van standaard-APN-instellingen voltooid." - "Naamloos" - "Namen toegangspunten" - "APN\'s" - "Nieuwe APN" - "Instellingen voor toegangspuntnamen zijn niet beschikbaar voor deze gebruiker" - "Kopiëren naar klembord?" - "Kopiëren" - "naar %s" - "Algemeen" - "Geavanceerd" - "Algemene instellingen" - "Geavanceerde instellingen" - "Sim van \'%s\'" - "Afzonderlijke sms\'jes verzenden naar alle ontvangers. U bent de enige die eventuele reacties ontvangt" - "Eén mms verzenden naar alle ontvangers" - "Onbekend nummer" - "Nieuw bericht" - "Nieuw bericht." - "Simkaart selecteren" - "%1$s geselecteerd, simkaartkiezer" - "Onderwerp bewerken" - "Simkaart selecteren of onderwerp bewerken" - "Blijf aanraken om audio op te nemen" - "Nieuw gesprek starten" - "Berichten" - "Lijst in Berichten" - "Berichten" - "Nieuw bericht" - "Gesprekslijst" - "Gesprekken laden" - "Berichten laden" - "Meer gesprekken bekijken" - "Meer berichten bekijken" - "Gesprek verwijderd" - "Gesprek verwijderd. Tik om een ander Berichten-gesprek weer te geven" - "Geblokkeerd" - "Blokkering opgeheven" - "Er is weinig opslagruimte beschikbaar. Er kunnen gegevens verloren gaan." - "Bijlagen selecteren" - "Selectie bevestigen" - "%d geselecteerd" - "Verwijder een of meer bijlagen en probeer het opnieuw." - "U kunt proberen uw bericht te verzenden, maar het bericht wordt mogelijk niet bezorgd, tenzij u een of meer bijlagen verwijdert." - "U kunt slechts één video verzenden per bericht. Verwijder extra video\'s en probeer het opnieuw." - "Berichten kan de bijlage niet laden." - "Toch verzenden" - "Kan gesprek niet starten" - "%1$s (%2$s)" - "%s geselecteerd" - diff --git a/res/values-pa-rIN/arrays.xml b/res/values-pa-rIN/arrays.xml deleted file mode 100644 index 1cf23e9..0000000 --- a/res/values-pa-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ" - "ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ" - - - "ਹਾਂ" - "ਨਹੀਂ" - "ਠੀਕ" - "Hehe" - "ਧੰਨਵਾਦ" - "ਮੈਂ ਸਹਿਮਤ ਹਾਂ" - "ਵਧੀਆ" - "ਰਸਤੇ ਵਿੱਚ ਹਾਂ" - "ਠੀਕ, ਮੈਂ ਤੁਹਾਨੂੰ ਬਾਅਦ ਵਿੱਚ ਸੰਪਰਕ ਕਰਾਂਗਾ" - ":)" - ":(" - - diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml deleted file mode 100644 index 3be69e1..0000000 --- a/res/values-pa-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Messaging" - "Messaging" - "ਗੱਲਬਾਤ ਚੁਣੋ" - "ਸੈਟਿੰਗਾਂ" - "ਸੁਨੇਹਾ ਭੇਜੋ" - "ਇੱਕ ਅਟੈਚਮੈਂਟ ਜੋੜੋ" - "ਸਹਾਇਤਾ" - "ਸੁਆਗਤ ਹੈ" - "ਛੱਡੋ" - "ਅਗਲਾ >" - "ਅਗਲਾ" - "ਬਾਹਰ ਜਾਓ" - "ਸੈਟਿੰਗਾਂ >" - "ਸੈਟਿੰਗਾਂ" - "Messaging ਨੂੰ SMS, ਫੋਨ ਅਤੇ ਸੰਪਰਕਾਂ ਲਈ ਅਨੁਮਤੀ ਦੀ ਲੋੜ ਹੈ।" - "ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ > ਐਪਸ > Messaging > ਅਨੁਮਤੀਆਂ ਵਿੱਚ ਅਨੁਮਤੀਆਂ ਬਦਲ ਸਕਦੇ ਹੋ।" - "ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ, ਐਪਸ, Messaging, ਅਨੁਮਤੀਆਂ ਵਿੱਚ ਅਨੁਮਤੀਆਂ ਬਦਲ ਸਕਦੇ ਹੋ।" - "ਫ੍ਰੀਕਵੈਂਟਸ" - "ਸਾਰੇ ਸੰਪਰਕ" - "%s ਨੂੰ ਭੇਜੋ" - "ਤਸਵੀਰਾਂ ਜਾ ਵੀਡੀਓ ਕੈਪਚਰ ਕਰੋ" - "ਇਸ ਡਿਵਾਈਸ ਤੋਂ ਚਿੱਤਰ ਚੁਣੋ" - "ਔਡੀਓ ਰਿਕਾਰਡ ਕਰੋ" - "ਫੋਟੋ ਚੁਣੋ" - "ਮੀਡੀਆ ਚੁਣਿਆ ਗਿਆ ਹੈ।" - "ਮੀਡੀਆ ਅਚੋਣਵਾਂ ਕੀਤਾ ਗਿਆ ਹੈ।" - "%d ਚੁਣਿਆ ਗਿਆ" - "ਚਿੱਤਰ %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ਚਿੱਤਰ" - "ਔਡੀਓ ਰਿਕਾਰਡ ਕਰੋ" - "ਸ਼ੇਅਰ ਕਰੋ" - "ਬਿਲਕੁਲ ਹੁਣੇ" - "ਹੁਣ" - - %d ਮਿੰਟ - %d ਮਿੰਟ - - - %d ਘੰਟੇ - %d ਘੰਟੇ - - - %d ਦਿਨ - %d ਦਿਨ - - - %d ਹਫ਼ਤੇ - %d ਹਫ਼ਤੇ - - - %d ਮਹੀਨੇ - %d ਮਹੀਨੇ - - - %d ਸਾਲ - %d ਸਾਲ - - "ਕਲਾਸ 0 ਸੁਨੇਹਾ" - "ਸੁਰੱਖਿਅਤ ਕਰੋ" - "ਡਿਵਾਈਸ ਵਿੱਚ ਘੱਟ ਸਪੇਸ ਹੈ। Messaging ਆਟੋਮੈਟਿਕਲੀ ਸਪੇਸ ਖਾਲੀ ਕਰਨ ਲਈ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾ ਦੇਵੇਗਾ।" - "ਸਟੋਰੇਜ ਸਪੇਸ ਖ਼ਤਮ ਹੋ ਰਿਹਾ ਹੈ" - "Messaging ਸੁਨੇਹੇ ਭੇਜ ਜਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕਰ ਸਕਦਾ ਜਦੋਂ ਤੱਕ ਕਿ ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਤੇ ਹੋਰ ਸਪੇਸ ਉਪਲਬਧ ਨਾ ਹੋਵੇ।" - "ਘੱਟ SMS ਸਟੋਰੇਜ। ਤੁਹਾਨੂੰ ਸੁਨੇਹੇ ਮਿਟਾਉਣ ਦੀ ਲੋੜ ਪੈ ਸਕਦੀ ਹੈ।" - "ਆਪਣੇ ਫੋਨ ਨੰਬਰ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ" - "ਇਹ ਵਨ-ਟਾਈਮ ਸਟੈਪ ਯਕੀਨੀ ਬਣਾਏਗਾ ਕਿ Messaging ਤੁਹਾਡੇ ਸਮੂਹ ਦੇ ਸੁਨੇਹੇ ਸਹੀ ਢੰਗ ਨਾਲ ਡਿਲੀਵਰ ਕਰੇਗਾ।" - "ਫੋਨ ਨੰਬਰ" - "ਮੀਡੀਆ ਵਾਲੇ ਸਾਰੇ ਸੁਨੇਹੇ ਮਿਟਾਓ" - "%s ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਓ" - "%s ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਆਟੋ-ਮਿਟਾਓ" - "ਅਣਡਿੱਠ ਕਰੋ" - "ਕੀ ਮੀਡੀਆ ਵਾਲੇ ਸਾਰੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ?" - "ਕੀ %s ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ?" - "ਕੀ %s ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ ਅਤੇ ਆਟੋ-ਮਿਟਾਓ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?" - "%s ਨੇ ਕਿਹਾ" - "ਤੁਸੀਂ ਕਿਹਾ" - "%s ਦਾ ਸੁਨੇਹਾ" - "ਤੁਸੀਂ ਇੱਕ ਸੁਨੇਹਾ ਭੇਜਿਆ" - "ਭੇਜ ਰਿਹਾ ਹੈ..." - "ਨਹੀਂ ਭੇਜਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ।" - "ਨਹੀਂ ਭੇਜਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਿਹਾ ਹੈ..." - "ਦੁਬਾਰਾ ਭੇਜੋ ਜਾਂ ਮਿਟਾਓ" - "ਕਿਰਪਾ ਕਰਕੇ ਐਮਰਜੈਂਸੀ ਸੇਵਾਵਾਂ ਨੂੰ ਇੱਕ ਵੌਇਸ ਕਾਲ ਕਰੋ। ਤੁਹਾਡਾ ਟੈਕਸਟ ਸੁਨੇਹਾ ਇਸ ਵੇਲੇ ਡਿਲੀਵਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ।" - "ਅਸਫਲ" - "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਨਵਾਂ MMS ਸੁਨੇਹਾ" - "ਨਵਾਂ MMS ਸੁਨੇਹਾ" - "ਡਾਊਨਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ" - "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ" - "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਛੋਹਵੋ" - "ਡਾਊਨਲੋਡ ਕਰੋ ਜਾਂ ਮਿਟਾਓ" - "ਡਾਊਨਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..." - "ਸੁਨੇਹੇ ਦੀ ਮਿਆਦ ਪੁੱਗੀ ਜਾਂ ਉਪਲਬਧ ਨਹੀਂ" - "ਆਕਾਰ: %1$s, ਮਿਆਦ ਪੁੱਗਣ ਦਾ ਸਮਾਂ: %2$s" - "ਭੇਜ ਨਹੀਂ ਸਕਦਾ। ਪ੍ਰਾਪਤਕਰਤਾ ਪ੍ਰਮਾਣਿਕ ਨਹੀਂ ਹੈ।" - "ਨੈਟਵਰਕ ਤੇ ਸੇਵਾ ਸਕਿਰਿਆ ਨਹੀਂ ਕੀਤੀ" - "ਨੈਟਵਰਕ ਸਮੱਸਿਆ ਦੇ ਕਾਰਨ ਨਹੀਂ ਭੇਜ ਸਕਿਆ" - "ਸੁਨੇਹੇ ਦੀ ਮਿਆਦ ਪੁੱਗੀ ਜਾਂ ਉਪਲਬਧ ਨਹੀਂ" - "(ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ)" - "ਅਗਿਆਤ ਭੇਜਣ ਵਾਲਾ" - "ਡਿਲੀਵਰ ਕੀਤਾ" - "%2$s ਦਾ %1$s ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ।" - "ਘੱਟ ਮੈਮਰੀ ਦੇ ਕਾਰਨ ਡਾਟਾਬੇਸ ਓਪਰੇਸ਼ਨ ਪੂਰਾ ਨਹੀਂ ਕਰ ਸਕਿਆ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ" - "ਕੁਝ ਸੁਨੇਹੇ Messaging ਵਿੱਚ ਨਹੀਂ ਭੇਜੇ ਗਏ ਹਨ" - - %d ਗੱਲਬਾਤਾਂ ਵਿੱਚ %d ਸੁਨੇਹੇ - %d ਗੱਲਬਾਤਾਂ ਵਿੱਚ %d ਸੁਨੇਹੇ - - "ਸੁਨੇਹਾ ਡਾਉਨਲੋਡ ਨਹੀਂ ਕੀਤਾ ਗਿਆ" - "ਕੁਝ ਸੁਨੇਹੇ Messaging ਵਿੱਚ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੇ ਗਏ ਹਨ" - - %d ਗੱਲਬਾਤਾਂ ਵਿੱਚ %d ਸੁਨੇਹੇ - %d ਗੱਲਬਾਤਾਂ ਵਿੱਚ %d ਸੁਨੇਹੇ - - "%1$s ਨੂੰ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ" - "ਕਿਰਪਾ ਕਰਕੇ ਐਮਰਜੈਂਸੀ ਸੇਵਾਵਾਂ ਨੂੰ ਇੱਕ ਵੌਇਸ ਕਾਲ ਕਰੋ। ਤੁਹਾਡਾ ਟੈਕਸਟ ਸੁਨੇਹਾ ਇਸ ਵੇਲੇ %1$s ਨੂੰ ਡਿਲੀਵਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ।" - - %d ਨਵੇਂ ਸੁੁਨੇਹੇ - %d ਨਵੇਂ ਸੁੁਨੇਹੇ - - "ਚਾਲੂ ਕਰੋ" - "ਕੈਮਰਾ ਉਪਲਬਧ ਨਹੀਂ" - "ਕੈਮਰਾ ਉਪਲਬਧ ਨਹੀਂ" - "ਵੀਡੀਓ ਕੈਪਚਰ ਉਪਲਬਧ ਨਹੀਂ" - "ਮੀਡੀਆ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਦਾ" - "ਤਸਵੀਰ ਨਹੀਂ ਲੈ ਸਕਦਾ" - "ਪਿੱਛੇ" - "ਆਰਕਾਈਵ ਕੀਤਾ" - "ਮਿਟਾਓ" - "ਆਰਕਾਈਵ ਕਰੋ" - "ਅਨਆਰਕਾਈਵ ਕਰੋ" - "ਸੂਚਨਾਵਾਂ ਬੰਦ ਕਰੋ" - "ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ" - "ਸੰਪਰਕ ਜੋੜੋ" - "ਡਾਊਨਲੋਡ ਕਰੋ" - "ਭੇਜੋ" - "ਮਿਟਾਓ" - "ਕੀ ਇਹ ਸੁਨੇਹਾ ਮਿਟਾਉਣਾ ਹੈ?" - "ਇਹ ਕਿਰਿਆ ਨਸ਼ਟ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।" - "ਮਿਟਾਓ" - - ਕੀ ਇਹ ਗੱਲਬਾਤਾਂ ਮਿਟਾਉਣੀਆਂ ਹਨ? - ਕੀ ਇਹ ਗੱਲਬਾਤਾਂ ਮਿਟਾਉਣੀਆਂ ਹਨ? - - "ਮਿਟਾਓ" - "ਰੱਦ ਕਰੋ" - "ਵੱਲ" - "ਮਲਟੀਪਲ ਚਿੱਤਰ ਚੁਣੋ" - "ਚੋਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ" - "+%d" - "ਔਡੀਓ ਰਿਕਾਰਡ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਔਡੀਓ ਪਲੇ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਔਡੀਓ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਛੋਹਵੋ & ਹੋਲਡ ਕਰੋ" - ", " - " " - ": " - " " - "ਤਸਵੀਰ" - "ਔਡੀਓ ਕਲਿਪ" - "ਵੀਡੀਓ" - "ਸੰਪਰਕ ਕਾਰਡ" - "ਡਾਊਨਲੋਡ ਕਰੋ" - "SMS ਰਾਹੀਂ ਜਵਾਬ" - "MMS ਰਾਹੀਂ ਜਵਾਬ" - "ਜਵਾਬ" - - %d ਭਾਗੀਦਾਰ - %d ਭਾਗੀਦਾਰ - - "ਮੈਂ" - "ਬਲੌਕ ਕੀਤਾ & ਆਰਕਾਈਵ ਕੀਤਾ ਸੰਪਰਕ" - "ਸੰਪਰਕ ਅਨਬਲੌਕ ਕੀਤਾ & ਅਨਆਰਕਾਈਵ ਕੀਤਾ" - "%d ਆਰਕਾਈਵ ਕੀਤੀਆਂ" - "%d ਅਨਆਰਕਾਈਵ ਕੀਤੀਆਂ" - "ਸੂਚਨਾਵਾਂ ਬੰਦ ਕੀਤੀਆਂ" - "ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ" - "ਸਾਰੇ ਸੈਟ ਕੀਤੇ। ਭੇਜੋ ਨੂੰ ਦੁਬਾਰਾ ਛੋਹਵੋ।" - "Messaging ਡਿਫੌਲਟ SMS ਐਪਸ ਦੇ ਤੌਰ ਤੇ ਸਫਲਤਾਪੂਰਵਕ ਸੈਟ ਕੀਤਾ ਗਿਆ।" - - ਅਟੈਚਮੈਂਟਾਂ ਬਰਖਾਸਤ ਕਰੋ - ਅਟੈਚਮੈਂਟਾਂ ਬਰਖਾਸਤ ਕਰੋ - - "ਔਡੀਓ ਅਟੈਚਮੈਂਟ" - "ਔਡੀਓ ਅਟੈਚਮੈਂਟ ਪਲੇ ਕਰੋ" - "ਰੋਕੋ" - "%s ਦਾ ਸੁਨੇਹਾ: %s।" - "%s ਦਾ ਸੁਨੇਹਾ ਅਸਫਲ: %s। ਸਮਾਂ: %s।" - "%s ਦਾ ਸੁਨੇਹਾ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਨਾ ਭੇਜਿਆ ਗਿਆ ਸੁਨੇਹਾ: %s। Time: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ: %s। ਸਮਾਂ: %s।" - "%s ਦਾ ਸੁਨੇਹਾ ਅਸਫਲ: %s। ਸਮਾਂ: %s%s।" - "%s ਦਾ ਸੁਨੇਹਾ: %s. ਸਮਾਂ: %s%s।" - "%s ਨੂੰ ਨਾ ਭੇਜਿਆ ਗਿਆ ਸੁਨੇਹਾ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ: %s। ਸਮਾਂ: %s।" - "%s ਨੂੰ ਸੁਨੇਹਾ: %s। ਸਮਾਂ: %s।" - "ਸੁਨੇਹਾ ਅਸਫਲ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ।" - "%s ਨਾਲ ਗੱਲਬਾਤ ਕਰੋ" - "ਵਿਸ਼ਾ ਹਟਾਓ" - "ਵੀਡੀਓ ਕੈਪਚਰ ਕਰੋ" - "ਇੱਕ ਸਟਿਲ ਚਿੱਤਰ ਕੈਪਚਰ ਕਰੋ" - "ਤਸਵੀਰ ਲਓ" - "ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰਨਾ ਚਾਲੂ ਕਰੋ" - "ਪੂਰੀ ਸਕ੍ਰੀਨ ਕੈਮਰਾ ਤੇ ਸਵਿਚ ਕਰੋ" - "ਫ੍ਰੰਟ ਅਤੇ ਬੈਕ ਕੈਮਰਾ ਵਿਚਕਾਰ ਸਵਿਚ ਕਰੋ" - "ਰਿਕਾਰਡਿੰਗ ਰੋਕੋ ਅਤੇ ਵੀਡੀਓ ਅਟੈਚ ਕਰੋ" - "ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰਨਾ ਰੋਕੋ" - "Messaging ਫੋਟੋਆਂ" - - %d ਫੋਟੋਆਂ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - %d ਫੋਟੋਆਂ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - - - %d ਵੀਡਿਓ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - %d ਵੀਡਿਓ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - - - %d ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - %d ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ \"%s\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ - - - %d ਅਟੈਚਮੈਂਟਾਂ ਡਾਊਨਲੋਡਸ\" ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ ਗਈਆਂ - %d ਅਟੈਚਮੈਂਟਾਂ ਡਾਊਨਲੋਡਸ\" ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ ਗਈਆਂ - - - %d ਅਟੈਚਮੈਂਟਾਂ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ - %d ਅਟੈਚਮੈਂਟਾਂ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ - - - %d ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ ਸੁੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ - %d ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ ਸੁੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ - - "ਸੁਰੱਖਿਅਤ ਕੀਤੀ MMS ਅਟੈਚਮੈਂਟ" - "ਸੈਟਿੰਗਾਂ" - "ਆਰਕਾਈਵ ਕੀਤਾ" - "ਬੰਦ ਕਰੋ" - "MMS" - "ਉੱਨਤ" - "ਡੀਬਗ ਕਰੋ" - "ਸੂਚਨਾਵਾਂ" - "ਅਵਾਜ਼" - "ਸਾਈਲੈਂਟ" - "ਵਾਈਬ੍ਰੇਟ" - "ਬਲੌਕ ਕੀਤਾ" - "SMS ਡਿਲੀਵਰੀ ਰਿਪੋਰਟਾਂ" - "ਤੁਸੀਂ ਜੋ ਵੀ SMS ਭੇਜੋ ਉਸ ਹਰੇਕ ਲਈ ਇੱਕ ਡਿਲੀਵਰੀ ਰਿਪੋਰਟ ਦੀ ਬੇਨਤੀ ਕਰੋ" - "ਆਟੋ-ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ" - "ਆਟੋਮੈਟਿਕਲੀ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰੋ" - "ਰੋਮਿੰਗ ਆਟੋ-ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ" - "ਰੋਮਿੰਗ ਵੇਲੇ MMS ਆਟੋਮੈਟਿਕਲੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ" - "ਸਮੂਹ ਮੈਸੇਂਜ਼ਿੰਗ" - "ਜਦੋਂ ਮਲਟੀਪਲ ਪ੍ਰਾਪਤਕਰਤਾ ਹੋਣ ਤਾਂ ਇੱਕ ਸਿੰਗਲ ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ MMS ਵਰਤੋ" - "ਡਿਫੌਲਟ SMS ਐਪ" - "ਡਿਫੌਲਟ SMS ਐਪ" - - - "ਤੁਹਾਡਾ ਫੋਨ ਨੰਬਰ" - "ਅਗਿਆਤ" - "ਆਊਟਗੋਇੰਗ ਸੁਨੇਹਾ ਅਵਾਜ਼ਾਂ" - "SMS ਡੰਪ ਕਰੋ" - "ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਫਾਈਲ ਵਿੱਚ ਪ੍ਰਾਪਤ ਕੀਤਾ SMS ਅਧੂਰਾ ਡਾਟਾ ਡੰਪ ਕਰੋ" - "MMS ਡੰਪ ਕਰੋ" - "ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਫਾਈਲ ਵਿੱਚ ਪ੍ਰਾਪਤ ਕੀਤਾ MMS ਅਧੂਰਾ ਡਾਟਾ ਡੰਪ ਕਰੋ" - "ਵਾਇਰਲੈਸ ਚਿਤਾਵਨੀਆਂ" - "ਸੁਨੇਹਾ ਚੋਣਾਂ" - "ਟੈਕਸਟ ਕਾਪੀ ਕਰੋ" - "ਵੇਰਵੇ ਦੇਖੋ" - "ਮਿਟਾਓ" - "ਅੱਗੇ ਭੇਜੋ" - "ਸੁਨੇਹਾ ਵੇਰਵੇ" - "ਟਾਈਪ: " - "ਟੈਕਸਟ ਸੁਨੇਹਾ" - "ਮਲਟੀਮੀਡੀਆ ਸੁਨੇਹਾ" - "ਇਸ ਤੋਂ: " - "ਵੱਲ: " - "ਭੇਜਿਆ: " - "ਪ੍ਰਾਪਤ ਕੀਤਾ: " - "ਵਿਸ਼ਾ: " - "ਆਕਾਰ: " - "ਤਰਜੀਹ: " - "SIM: " - "ਉੱਚ" - "ਸਧਾਰਨ" - "ਘੱਟ" - "SIM %s" - "ਭੇਜਣ ਵਾਲੇ ਦਾ ਪਤਾ ਲੁਕਾਇਆ ਗਿਆ" - "ਅਟੈਚਮੈਂਟਾਂ ਲੋਡ ਕਰਨ ਵੇਲੇ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ।" - "ਅਟੈਚਮੈਂਟ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਨੈੱਟਵਰਕ ਤਿਆਰ ਨਹੀਂ ਹੈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਟੈਕਸਟ ਮਿਟਾਓ" - "ਟੈਕਸਟ ਅਤੇ ਨੰਬਰ ਦਰਜ ਕਰਨ ਵਿਚਕਾਰ ਸਵਿਚ ਕਰੋ" - "ਹੋਰ ਭਾਗੀਦਾਰ ਜੋੜੋ" - "ਭਾਗੀਦਾਰਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ" - "ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰੋ" - "ਇਹ ਆਈਟਮ ਚੁਣੋ" - "ਵੀਡੀਓ ਪਲੇ ਕਰੋ" - "ਲੋਕ & ਚੋਣਾਂ" - "ਡੀਬਗ ਕਰੋ" - "ਲੋਕ & ਚੋਣਾਂ" - "ਸਧਾਰਨ" - "ਇਸ ਗੱਲਬਾਤ ਵਿੱਚ ਲੋਕ" - "ਇੱਕ ਕਾਲ ਕਰੋ" - "ਸੁਨੇਹਾ ਭੇਜੋ" - "ਸੁਨੇਹਾ ਭੇਜੋ<br/><ਛੋਟਾ>%s</ਤੋਂ ਛੋਟਾ>" - - ਫੋਟੋਆਂ ਭੇਜੋ - ਫੋਟੋਆਂ ਭੇਜੋ - - - ਔਡੀਓ ਭੇਜੋ - ਔਡੀਓ ਭੇਜੋ - - - ਵੀਡਿਓ ਭੇਜੋ - ਵੀਡਿਓ ਭੇਜੋ - - - ਸੰਪਰਕ ਕਾਰਡ ਭੇਜੋ - ਸੰਪਰਕ ਕਾਰਡ ਭੇਜੋ - - - ਅਟੈਚਮੈਂਟਾਂ ਭੇਜੋ - ਅਟੈਚਮੈਂਟਾਂ ਭੇਜੋ - - - %d ਅਟੈਚਮੈਂਟਾਂ ਭੇਜਣ ਲਈ ਤਿਆਰ - %d ਅਟੈਚਮੈਂਟਾਂ ਭੇਜਣ ਲਈ ਤਿਆਰ - - "ਫੀਡਬੈਕ ਭੇਜੋ" - "Google Play ਸਟੋਰ ਵਿੱਚ ਦੇਖੋ" - "ਵਰਜਨ ਜਾਣਕਾਰੀ" - "ਵਰਜਨ %1$s" - "ਓਪਨ ਸ੍ਰੋਤ ਲਸੰਸ" - "ਸੂਚਨਾਵਾਂ" - "ਅਟੈਚਮੈਂਟ ਸੀਮਾ ਪੂਰੀ ਹੋਈ" - "ਅਟੈਚਮੈਂਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ।" - "ਕੀ ਸੰਪਰਕ ਜੋੜਨੇ ਹਨ?" - "ਸੰਪਰਕ ਜੋੜੋ" - "ਵਿਸ਼ਾ" - "ਵਿਸ਼ਾ: " - "%s%s" - "ਸੰਪਰਕ ਕਾਰਡ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ" - "ਸੰਪਰਕ ਕਾਰਡ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।" - "ਸੰਪਰਕ ਕਾਰਡ ਦੇਖੋ" - - %d ਸੰਪਰਕ - %d ਸੰਪਰਕ - - "ਸੰਪਰਕ ਕਾਰਡ" - "ਜਨਮਦਿਨ" - "ਸੂਚਨਾਵਾਂ" - "ਸੁਨੇਹਾ ਅੱਗੇ ਭੇਜੋ" - "ਜਵਾਬ ਦਿਓ" - "+1" - "+%d" - "SMS ਅਸਮਰਥਿਤ" - "ਭੇਜਣ ਲਈ, Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ" - "Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ" - "ਬਦਲੋ" - "ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ, Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪਸ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ" - "SMS ਸੁਨੇਹੇ ਭੇਜਣ ਲਈ ਕੋਈ ਤਰਜੀਹੀ SIM ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ" - "ਡਿਵਾਈਸ ਮਾਲਕ ਵੱਲੋਂ ਇਸ ਐਪ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ।" - "ਠੀਕ" - "ਇੱਕ ਗੱਲਬਾਤਾ ਵਿੱਚ ਬਹੁਤ ਜ਼ਿਆਦਾ ਪ੍ਰਾਪਤਕਰਤਾ" - - ਅਪ੍ਰਮਾਣਿਕ ਸੰਪਰਕ - ਅਪ੍ਰਮਾਣਿਕ ਸੰਪਰਕ - - "ਕੈਮਰਾ ਚਿੱਤਰ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ" - "ਤੁਸੀਂ: " - "%s: " - "ਡ੍ਰਾਫਟ" - "ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਇਸਨੂੰ ਇੱਥੇ ਸੂਚੀਬੱਧ ਦੇਖੋਗੇ" - "ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਬਾਤਾਂ ਇੱਥੇ ਪ੍ਰਗਟ ਹੋਣਗੀਆਂ" - "ਗੱਲਬਾਤਾਂ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ…" - "ਤਸਵੀਰ" - "ਔਡੀਓ ਕਲਿਪ" - "ਵੀਡੀਓ" - "ਸੰਪਰਕ ਕਾਰਡ" - "MMS" - "ਪਹਿਲਾਂ ਵਰਗਾ ਕਰੋ" - "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ" - "ਇੱਕ ਨਵਾਂ ਸੁਨੇਹਾ ਚਾਲੂ ਕਰਨ ਲਈ ਇੱਕ ਸੰਪਰਕ ਨਾਮ ਜਾਂ ਫੋਨ ਨੰਬਰ ਦਰਜ ਕਰੋ" - "ਬਲੌਕ ਕਰੋ" - "%s ਨੂੰ ਬਲੌਕ ਕਰੋ" - "%s ਨੂੰ ਅਨਬਲੌਕ ਕਰੋ" - "ਕੀ %s ਨੂੰ ਬਲੌਕ ਕਰਨਾ ਹੈ?" - "ਤੁਸੀਂ ਇਸ ਨੰਬਰ ਤੋਂ ਲਗਾਤਾਰ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰੋਗੇ ਪਰੰਤੂ ਹੁਣ ਤੁਹਾਨੂੰ ਸੂਚਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਏਗਾ। ਇਹ ਗੱਲਬਾਤ ਆਰਕਾਈਵ ਕੀਤੀ ਜਾਏਗੀ।" - "ਬਲੌਕ ਕੀਤੇ ਸੰਪਰਕ" - "ਅਨਬਲੌਕ ਕਰੋ" - "ਬਲੌਕ ਕੀਤੇ ਸੰਪਰਕ" - "ਦਸਤਾਵੇਜ਼ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚੋਂ ਚਿੱਤਰ ਚੁਣੋ" - "ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ" - "ਸੁਨੇਹਾ ਭੇਜਿਆ ਗਿਆ" - "ਸੈਲਿਊਲਰ ਡਾਟਾ ਬੰਦ ਹੈ। ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ।" - "ਏਅਰਪਲੇਨ ਮੋਡ ਵਿੱਚ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਦਾ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਜਾ ਸਕੇਗਾ" - "ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਕੀਤਾ" - "ਸੈਲਿਊਲਰ ਡਾਟਾ ਬੰਦ ਹੈ। ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ।" - "ਏਅਰਪਲੇਨ ਮੋਡ ਵਿੱਚ ਸੁਨੇਹੇ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ" - "ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ" - "ਸਿਫਰ" - "ਇੱਕ" - "ਦੋ" - "ਤਿੰਨ" - "ਚਾਰ" - "ਪੰਜ" - "ਛੇ" - "ਸੱਤ" - "ਅੱਠ" - "ਨੌ" - "%1$s ਨਾਲ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ, ਅਸ਼ੁੱਧੀ %2$d" - "ਅਗਿਆਤ ਕੈਰੀਅਰ ਨਾਲ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ, ਅਸ਼ੁੱਧੀ %1$d" - "Fwd: %s" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਨੈਟਵਰਕ ਤੇ ਸੇਵਾ ਸਕਿਰਿਆ ਨਹੀਂ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਪ੍ਰਮਾਣਿਕ ਡੈਸਟੀਨੇਸ਼ਨ ਪਤਾ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਪ੍ਰਮਾਣਿਤ ਸੁਨੇਹਾ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਸਮਰਥਿਤ ਸਮੱਗਰੀ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਸਮਰਥਿਤ ਸੁਨੇਹਾ" - "ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ: ਬਹੁਤ ਜ਼ਿਆਦਾ ਵੱਡਾ" - "ਨਵਾਂ ਸੁਨੇਹਾ" - "ਦੇਖੋ" - "ਚਿੱਤਰ" - "ਇੱਕ ਅਨੁੁਕੂਲ ਐਪਲੀਕੇਸ਼ਨ ਨਹੀਂ ਲੱਭ ਸਕਿਆ" - "ਪ੍ਰਾਪਤਕਰਤਾ ਹਟਾਓ" - "ਨਵਾਂ ਸੁਨੇਹਾ" - "ਰੱਦ ਕਰੋ" - "ਪਹੁੰਚ ਬਿੰਦੂ ਸੰਪਾਦਿਤ ਕਰੋ" - "ਸੈਟ ਨਹੀਂ ਕੀਤਾ" - "ਨਾਮ" - "APN" - "MMSC" - "MMS ਪ੍ਰੌਕਸੀ" - "MMS ਪੋਰਟ" - "MCC" - "MNC" - "APN ਪ੍ਰਕਾਰ" - "APN ਮਿਟਾਓ" - "ਨਵਾਂ APN" - "ਸੁਰੱਖਿਅਤ ਕਰੋ" - "ਅਣਡਿੱਠਾ" - "ਨਾਮ ਖੇਤਰ ਨੂੰ ਖਾਲੀ ਨਹੀਂ ਛੱਡਿਆ ਜਾ ਸਕਦਾ।" - "APN ਨੂੰ ਖਾਲੀ ਨਹੀਂ ਛੱਡਿਆ ਜਾ ਸਕਦਾ।" - "MCC ਖੇਤਰ 3 ਅੰਕਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।" - "MNC ਖੇਤਰ 2 ਜਾਂ 3 ਅੰਕਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।" - "ਡਿਫੌਲਟ APN ਸੈਟਿੰਗਾਂ ਰੀਸਟੋਰ ਕਰ ਰਿਹਾ ਹੈ।" - "ਡਿਫੌਲਟਸ ਤੇ ਰੀਸੈਟ ਕਰੋ" - "ਡਿਫੌਲਟ APN ਸੈਟਿੰਗਾਂ ਰੀਸੈਟ ਕਰਨਾ ਪੂਰਾ ਹੋਇਆ।" - "ਬਿਨਾਂ ਨਾਮ" - "ਪਹੁੰਚ ਬਿੰਦੂ ਨਾਮ" - "APNs" - "ਨਵਾਂ APN" - "ਪਹੁੰਚ ਬਿੰਦੂ ਨਾਮ ਸੈਟਿੰਗਾਂ ਇਸ ਉਪਭੋਗਤਾ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹਨ" - "ਕੀ ਕਲਿਪਬੋਰਡ ਤੇ ਕਾਪੀ ਕਰਨਾ ਹੈ?" - "ਕਾਪੀ ਕਰੋ" - "%s ਨੂੰ" - "ਸਧਾਰਨ" - "ਉੱਨਤ" - "ਸਧਾਰਨ ਸੈਟਿੰਗਾਂ" - "ਉੱਨਤ ਸੈਟਿੰਗਾਂ" - "\"%s\" SIM" - "ਸਾਰੇ ਪ੍ਰਾਪਤਕਰਤਾਵਾਂ ਨੂੰ ਵੱਖ-ਵੱਖ SMS ਸੁਨੇਹੇ ਭੇਜੋ। ਕੇਵਲ ਤੁਸੀਂ ਹੀ ਕੋਈ ਜਵਾਬ ਪ੍ਰਾਪਤ ਕਰੋਗੇ" - "ਸਾਰੇ ਪ੍ਰਾਪਤਕਰਤਾਵਾਂ ਨੂੰ ਇੱਕ ਸਿੰਗ MMS ਭੇਜੋ।" - "ਅਗਿਆਤ ਨੰਬਰ" - "ਨਵਾਂ ਸੁਨੇਹਾ" - "ਨਵਾਂ ਸੁਨੇਹਾ।" - "SIM ਚੋਣਕਾਰ" - "%1$s ਚੁਣਿਆ ਗਿਆ, SIM ਚੋਣਕਰਤਾ" - "ਵਿਸ਼ਾ ਸੰਪਾਦਿਤ ਕਰੋ" - "SIM ਚੁਣੋ ਜਾਂ ਵਿਸ਼ਾ ਸੰਪਾਦਿਤ ਕਰੋ" - "ਔਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ" - "ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰੋ" - "Messaging" - "Messaging ਸੂਚੀ" - "Messaging" - "ਨਵਾਂ ਸੁਨੇਹਾ" - "ਗੱਲਬਾਤ ਸੂਚੀ" - "ਗੱਲਬਾਤਾਂ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ" - "ਸੁਨੇਹੇ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ" - "ਹੋਰ ਗੱਲਬਾਤਾਂ ਦੇਖੋ" - "ਹੋਰ ਸੁਨੇਹੇ ਦੇਖੋ" - "ਗੱਲਬਾਤ ਮਿਟਾਈ ਗਈ" - "ਗੱਲਬਾਤ ਮਿਟਾਈ ਗਈ। ਇੱਕ ਵੱਖਰੀ Messaging ਗੱਲਬਾਤ ਦਿਖਾਉਣ ਲਈ ਛੋਹਵੋ" - "ਬਲੌਕ ਕੀਤਾ" - "ਅਨਬਲੌਕ ਕੀਤਾ" - "ਸਟੋਰੇਜ ਸਪੇਸ ਘੱਟ ਹੈ। ਕੁਝ ਡਾਟਾ ਨਸ਼ਟ ਹੋ ਸਕਦਾ ਹੈ।" - "ਅਟੈਚਮੈਂਟਾਂ ਚੁਣੋ" - "ਚੋਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ" - "%d ਚੁਣਿਆ ਗਿਆ" - "ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਜਾਂ ਵੱਧ ਅਟੈਚਮੈਂਟਾਂ ਹਟਾਓ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "ਤੁਸੀਂ ਆਪਣਾ ਸੁਨੇਹਾ ਭੇਜਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹੋ, ਪਰੰਤੂ ਇਹ ਸ਼ਾਇਦ ਨਾ ਭੇਜਿਆ ਜਾ ਸਕੇ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇੱਕ ਜਾਂ ਵੱਧ ਅਟੈਚਮੈਂਟਾਂ ਨਹੀਂ ਹਟਾਉਂਦੇ।" - "ਤੁਸੀਂ ਪ੍ਰਤੀ ਸੁਨੇਹਾ ਕੇਵਲ ਇੱਕ ਵੀਡੀਓ ਭੇਜ ਸਕਦੇ ਹੋ। ਕਿਰਪਾ ਕਰਕੇ ਵਾਧੂ ਵੀਡੀਓ ਹਟਾਓ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - "Messaging ਅਟੈਚਮੈਂਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ।" - "ਫੇਰ ਵੀ ਭੇਜੋ" - "ਗੱਲਬਾਤ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਿਆ" - "%1$s (%2$s)" - "%s ਚੁਣਿਆ ਗਿਆ" - diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml deleted file mode 100644 index 2bd82e4..0000000 --- a/res/values-pl/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "brak tematu" - "braktematu" - - - "Tak" - "Nie" - "OK" - "Ha ha" - "Dzięki" - "Zgadzam się" - "Super" - "Jestem w drodze" - "Skontaktuję się z Tobą później" - ":)" - ":(" - - diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml deleted file mode 100644 index 0ee2511..0000000 --- a/res/values-pl/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "Wiadomości" - "Wiadomości" - "Wybierz wątek" - "Ustawienia" - "Wyślij wiadomość" - "Dodaj załącznik" - "Pomoc" - "Witamy" - "Pomiń" - "Dalej >" - "Dalej" - "Zakończ" - "Ustawienia >" - "Ustawienia" - "Aplikacja SMS/MMS potrzebuje uprawnień do SMS-ów, telefonu i kontaktów." - "Uprawnienia możesz zmienić, klikając Ustawienia > Aplikacje > SMS/MMS > Uprawnienia." - "Uprawnienia możesz zmienić, klikając Ustawienia, Aplikacje, SMS/MMS, Uprawnienia." - "Częste kontakty" - "Wszystkie kontakty" - "Wyślij pod numer %s" - "Zrób zdjęcia lub nagraj film" - "Wybierz obrazy z tego urządzenia" - "Nagraj dźwięk" - "Wybierz zdjęcie" - "Plik multimedialny został wybrany." - "Plik multimedialny nie został wybrany." - "Wybrano: %d" - "obraz – %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "obraz" - "Nagraj dźwięk" - "Udostępnij" - "W tej chwili" - "Teraz" - - %d min - %d min - %d min - %d min - - - %d godziny - %d godzin - %d godziny - %d godzina - - - %d dni - %d dni - %d dnia - %d dzień - - - %d tygodnie - %d tygodni - %d tygodnia - tydzień - - - %d miesiące - %d miesięcy - %d miesiąca - miesiąc - - - %d lata - %d lat - %d roku - rok - - "Wiadomość klasy 0" - "Zapisz" - "Na urządzeniu jest mało miejsca. Aplikacja SMS/MMS automatycznie usunie starsze SMS-y, by zwolnić miejsce." - "Kończy się miejsce" - "Aplikacja SMS/MMS może nie wysyłać i nie odbierać wiadomości do momentu zwolnienia miejsca na urządzeniu." - "Mało miejsca na SMS-y. Może usuniesz niektóre wiadomości?" - "Potwierdź swój numer telefonu" - "Dzięki tej jednorazowej czynności aplikacja SMS/MMS będzie poprawnie dostarczać Twoje wiadomości grupowe." - "Numer telefonu" - "Usuń wszystkie MMS-y" - "Usuń wiadomości starsze niż %s" - "Automatycznie usuwaj wiadomości starsze niż %s" - "Ignoruj" - "Usunąć wszystkie MMS-y?" - "Usunąć wiadomości starsze niż %s?" - "Usunąć wiadomości starsze niż %s i włączyć automatyczne usuwanie?" - "%s pisze" - "Treść Twojej wiadomości" - "Wiadomość od użytkownika %s" - "Wysłałeś wiadomość" - "Wysyłam…" - "Nie wysłano. Kliknij, by spróbować ponownie." - "Nie wysłano. Próbuję ponownie…" - "Wyślij ponownie lub usuń" - "Zadzwoń do służb ratunkowych. Tym razem nie udało się dostarczyć SMS-a." - "Błąd" - "Nowa wiadomość MMS do pobrania" - "Nowa wiadomość MMS" - "Nie udało się pobrać" - "Kliknij, by spróbować ponownie" - "Kliknij, by pobrać" - "Pobierz lub usuń" - "Pobieram…" - "Wiadomość wygasła lub jest niedostępna" - "rozmiar: %1$s, termin ważności: %2$s" - "Nie można wysłać. Nieprawidłowy adresat." - "Usługa nie została aktywowana w sieci" - "Nie udało się wysłać z powodu problemu z siecią" - "Wiadomość wygasła lub jest niedostępna" - "(Brak tematu)" - "Nieznany nadawca" - "Dostarczono" - "Nie udało się pobrać wiadomości %1$s od użytkownika %2$s." - "Nie można dokończyć operacji na bazie danych z powodu zbyt małej ilości pamięci" - "Wiadomość nie została wysłana" - "Niektóre wiadomości nie zostały wysłane w aplikacji SMS/MMS" - - %d wiadomości w %d wątkach - %d wiadomości w %d wątkach - %d wiadomości w %d wątku - %d wiadomości w jednym wątku - - "Wiadomość nie została pobrana" - "Niektóre wiadomości nie zostały pobrane w aplikacji SMS/MMS" - - %d wiadomości w %d wątkach - %d wiadomości w %d wątkach - %d wiadomości w %d wątku - %d wiadomości w jednym wątku - - "Wiadomość do %1$s nie została wysłana" - "Zadzwoń do służb ratunkowych. Tym razem nie udało się dostarczyć SMS-a pod numer %1$s." - - %d nowe wiadomości - %d nowych wiadomości - %d nowej wiadomości - Nowa wiadomość - - "Rozpocznij" - "Aparat jest niedostępny" - "Aparat jest niedostępny" - "Nagrywanie filmu jest niedostępne" - "Nie można zapisać pliku multimedialnego" - "Nie można zrobić zdjęcia" - "Wstecz" - "Archiwum" - "Usuń" - "Archiwizuj" - "Przywróć z archiwum" - "Wyłącz powiadomienia" - "Włącz powiadomienia" - "Dodaj kontakt" - "Pobierz" - "Wyślij" - "Usuń" - "Usunąć tę wiadomość?" - "Nie można tego cofnąć." - "Usuń" - - Usunąć te wątki? - Usunąć te wątki? - Usunąć te wątki? - Usunąć ten wątek? - - "Usuń" - "Anuluj" - "Do" - "Wybierz wiele obrazów" - "Potwierdź wybór" - "Jeszcze %d" - "Nie można nagrać dźwięku. Spróbuj ponownie." - "Nie można odtworzyć dźwięku. Spróbuj ponownie." - "Nie można zapisać dźwięku. Spróbuj ponownie." - "Kliknij i przytrzymaj" - ", " - " " - ": " - " " - "Zdjęcie" - "Klip dźwiękowy" - "Film" - "Wizytówka" - "Pobierz" - "Odpowiedz SMS-em" - "Wyślij MMS-a" - "Odpowiedz" - - %d uczestników - %d uczestników - %d uczestnika - %d uczestnik - - "Ja" - "Kontakt został zablokowany i zarchiwizowany" - "Kontakt odblokowany i rozpakowany" - "Zarchiwizowano: %d" - "Przywrócone z archiwum: %d" - "Powiadomienia wyłączone" - "Powiadomienia włączone" - "Wszystko gotowe. Ponownie kliknij Wyślij." - "SMS/MMS to teraz domyślna aplikacja do SMS-ów." - - Odrzuć załączniki - Odrzuć załączniki - Odrzuć załączniki - Odrzuć załącznik - - "Załącznik audio" - "Odtwórz załącznik audio" - "Pauza" - "Wiadomość od użytkownika %s: %s." - "Nieudane odbieranie wiadomości od: %s: %s. Godzina: %s." - "Wiadomość od: %s: %s. Godzina: %s." - "Niewysłana wiadomość do: %s: %s. Godzina: %s." - "Wysyłanie wiadomości do: %s: %s. Godzina: %s." - "Nieudane wysyłanie wiadomości do: %s: %s. Godzina: %s." - "Wiadomość do: %s: %s. Godzina: %s." - "Nieudane odbieranie wiadomości od: %s: %s. Godzina: %s. %s." - "Wiadomość od: %s: %s. Godzina: %s. %s." - "Niewysłana wiadomość do: %s: %s. Godzina: %s." - "Wysyłanie wiadomości do: %s: %s. Godzina: %s." - "Nieudane wysyłanie wiadomości do: %s: %s. Godzina: %s." - "Wiadomość do: %s: %s. Godzina: %s." - "Niedostarczona wiadomość. Kliknij, aby ponowić próbę." - "Rozmowa z: %s" - "Usuń temat" - "Nagraj film" - "Zrób zdjęcie" - "Zrób zdjęcie" - "Zacznij nagrywać film" - "Włącz aparat pełnoekranowy" - "Przełącz między kamerą z przodu i z tyłu" - "Zatrzymaj nagrywanie i dołącz film" - "Zatrzymaj nagrywanie filmu" - "Zdjęcia z aplikacji SMS/MMS" - - %d zdjęcia zapisane w albumie „%s - %d zdjęć zapisanych w albumie „%s - %d zdjęcia zapisane w albumie „%s - %d zdjęcie zapisane w albumie „%s - - - %d filmy zapisane w albumie „%s - %d filmów zapisanych w albumie „%s - %d filmu zapisane w albumie „%s - %d film zapisany w albumie „%s - - - %d załączniki zapisane w albumie „%s - %d załączników zapisanych w albumie „%s - %d załącznika zapisane w albumie „%s - %d załącznik zapisany w albumie „%s - - - %d załączniki zapisane w folderze „Pobrane pliki” - %d załączników zapisanych w folderze „Pobrane pliki” - %d załącznika zapisane w folderze „Pobrane pliki” - %d załącznik zapisany w folderze „Pobrane pliki” - - - %d załączniki zapisane - %d załączników zapisanych - %d załącznika zapisane - %d załącznik zapisany - - - Nie udało się zapisać %d załączników - Nie udało się zapisać %d załączników - Nie udało się zapisać %d załącznika - Nie udało się zapisać %d załącznika - - "Zapisany załącznik MMS-a" - "Ustawienia" - "Zarchiwizowane" - "Zamknij" - "MMS" - "Zaawansowane" - "Debugowanie" - "Powiadomienia" - "Dźwięk" - "Cichy" - "Wibracje" - "Zablokowany" - "Raporty doręczeń SMS-ów" - "Otrzymuj raport o dostarczeniu każdego wysłanego SMS-a" - "Autopobieranie" - "Automatycznie pobieraj MMS-y" - "Autopobieranie w roamingu" - "Automatycznie pobieraj MMS-y w roamingu" - "Wiadomości grupowe" - "Używaj MMS-ów, gdy chcę wysłać jedną wiadomość do wielu odbiorców" - "Domyślna aplikacja do SMS-ów" - "Domyślna aplikacja do SMS-ów" - - - "Twój numer telefonu" - "Nieznany" - "Dźwięki przy wysyłaniu wiadomości" - "Zrzuć SMS-y" - "Zrzuć odebrane nieprzetworzone dane SMS-ów do pliku w pamięci zewnętrznej" - "Zrzuć MMS-y" - "Zrzuć odebrane nieprzetworzone dane MMS-ów do pliku w pamięci zewnętrznej" - "Alerty o zagrożeniach" - "Opcje wiadomości" - "Skopiuj tekst" - "Wyświetl szczegóły" - "Usuń" - "Przekaż dalej" - "Szczegóły wiadomości" - "Typ: " - "SMS" - "Wiadomość multimedialna" - "Od: " - "Do: " - "Wysłano: " - "Odebrane: " - "Temat: " - "Rozmiar: " - "Priorytet: " - "SIM: " - "Wysoki" - "Normalny" - "Niski" - "SIM %s" - "Ukryty adres nadawcy" - "Nie można wysłać wiadomości w czasie ładowania załączników." - "Nie można załadować załącznika. Spróbuj ponownie." - "Sieć nie jest gotowa. Spróbuj ponownie." - "Usuń tekst" - "Przełącz się między wpisywaniem tekstu i cyfr" - "Dodaj uczestników" - "Potwierdź uczestników" - "Rozpocznij nowy wątek" - "Wybierz ten element" - "Odtwórz film" - "Osoby i opcje" - "Debuguj" - "Osoby i opcje" - "Ogólne" - "Uczestnicy tej rozmowy" - "Zadzwoń" - "Wyślij wiadomość" - "Wyślij wiadomość<br/><small>z %s</small>" - - Wyślij zdjęcia - Wyślij zdjęcia - Wyślij zdjęcia - Wyślij zdjęcie - - - Wyślij pliki audio - Wyślij pliki audio - Wyślij pliki audio - Wyślij plik audio - - - Wyślij filmy - Wyślij filmy - Wyślij filmy - Wyślij film - - - Wyślij wizytówki - Wyślij wizytówki - Wyślij wizytówki - Wyślij wizytówkę - - - Wyślij załączniki - Wyślij załączniki - Wyślij załączniki - Wyślij załącznik - - - %d załączniki gotowe do wysłania - %d załączników gotowych do wysłania - %d załącznika gotowe do wysłania - Jeden załącznik gotowy do wysłania - - "Prześlij opinię" - "Zobacz w Sklepie Google Play" - "Informacje o wersji" - "Wersja %1$s" - "Licencje open source" - "Powiadomienia" - "Osiągnięto limit załączników" - "Nie udało się załadować załącznika." - "Dodać do kontaktów?" - "Dodaj kontakt" - "Temat" - "Temat: " - "%s%s" - "Ładowanie wizytówki" - "Nie można załadować wizytówki" - "Wyświetl wizytówkę" - - %d kontakty - %d kontaktów - %d kontaktu - %d kontakt - - "Wizytówki" - "Urodziny" - "Notatki" - "Przekaż wiadomość dalej" - "Odpowiedz" - "+1" - "+%d" - "SMS-y wyłączone" - "Aby wysyłać wiadomości, ustaw SMS/MMS jako domyślną aplikację do SMS-ów" - "Ustaw SMS/MMS jako domyślną aplikację do SMS-ów" - "Zmień" - "Aby odbierać wiadomości, ustaw SMS/MMS jako domyślną aplikację do SMS-ów" - "Nie wybrano preferowanej karty SIM do wysyłania SMS-ów" - "Właściciel urządzenia nie zezwolił na działanie tej aplikacji." - "OK" - "Zbyt wielu uczestników rozmowy" - - Nieprawidłowe kontakty - Nieprawidłowe kontakty - Nieprawidłowe kontakty - Nieprawidłowy kontakt - - "Nie udało się załadować zdjęcia z aparatu" - "Ty: " - "%s: " - "Wersja robocza" - "Po rozpoczęciu nowej rozmowy zobaczysz ją tutaj" - "Tutaj będą widoczne zarchiwizowane wątki" - "Ładuję wątki…" - "Zdjęcie" - "Klip dźwiękowy" - "Film" - "Wizytówka" - "MMS" - "Cofnij" - "Ponów" - "Wpisz nazwę kontaktu lub numer telefonu, by rozpocząć nową wiadomość" - "Zablokuj" - "Zablokuj: %s" - "Odblokuj: %s" - "Zablokować %s?" - "Będziesz nadal otrzymywać wiadomości z tego numeru, ale nie będzie już powiadomień o nich. Ten wątek zostanie zarchiwizowany." - "Zablokowane kontakty" - "ODBLOKUJ" - "Zablokowane kontakty" - "Wybierz obraz z biblioteki dokumentów" - "Wysyłam wiadomość" - "Wiadomość wysłana" - "Komórkowa transmisja danych jest wyłączona. Sprawdź ustawienia." - "W trybie samolotowym nie można wysyłać wiadomości" - "Nie można wysłać wiadomości" - "Wiadomość pobrana" - "Komórkowa transmisja danych jest wyłączona. Sprawdź ustawienia." - "Wiadomości nie można pobierać w trybie samolotowym" - "Nie udało się pobrać wiadomości" - "Zero" - "Jeden" - "Dwa" - "Trzy" - "Cztery" - "Pięć" - "Sześć" - "Siedem" - "Osiem" - "Dziewięć" - "Nie można wysłać wiadomości w sieci %1$s (błąd %2$d)" - "Nie można wysłać wiadomości w sieci nieznanego operatora (błąd %1$d)" - "PD: %s" - "Nie wysłano wiadomości: usługa nie została aktywowana w sieci" - "Wiadomość nie została wysłana: nieprawidłowy adres docelowy" - "Wiadomość nie została wysłana: nieprawidłowa wiadomość" - "Wiadomość nie została wysłana: nieobsługiwane treści" - "Wiadomość nie została wysłana: nieobsługiwana wiadomość" - "Wiadomość nie została wysłana: jest za duża" - "Nowa wiadomość" - "Wyświetl" - "Obraz" - "Nie udało się znaleźć stosownej aplikacji" - "Usuń odbiorcę" - "Nowa wiadomość" - "Anuluj" - "Edytuj punkt dostępu" - "Nie ustawiono" - "Nazwa" - "APN" - "MMSC" - "Proxy dla MMS" - "Port MMS" - "MCC" - "MNC" - "Typ APN" - "Usuń APN" - "Nowy APN" - "Zapisz" - "Odrzuć" - "Pole Nazwa nie może być puste." - "Nazwa APN nie może być pusta." - "Pole MCC musi zawierać 3 cyfry." - "Pole MNC musi zawierać 2 lub 3 cyfry." - "Przywracam domyślne ustawienia APN." - "Przywróć domyślne" - "Przywrócono domyślne ustawienia APN." - "Bez nazwy" - "Nazwy punktów dostępu" - "APN-y" - "Nowy APN" - "Ustawienia nazwy punktu dostępu są niedostępne dla tego użytkownika" - "Skopiować do schowka?" - "Kopiuj" - "do %s" - "Ogólne" - "Zaawansowane" - "Ustawienia ogólne" - "Ustawienia zaawansowane" - "Karta SIM „%s”" - "Wyślij osobne SMS-y do wszystkich odbiorców. Odpowiedzi trafią tylko do Ciebie" - "Wysyłaj jednego MMS-a do wszystkich odbiorców" - "Nieznany numer" - "Nowa wiadomość" - "Nowa wiadomość." - "Przełącznik SIM" - "Przełącznik SIM, wybrana karta: %1$s" - "Edytuj temat" - "Wybierz SIM lub edytuj temat" - "Dotknij i przytrzymaj, aby nagrać dźwięk" - "Zacznij nową rozmowę" - "Wiadomości" - "Lista wiadomości" - "Wiadomości" - "Nowa wiadomość" - "Lista wątków" - "Ładuję wątki" - "Wczytuję wiadomości" - "Wyświetl więcej wątków" - "Wyświetl więcej wiadomości" - "Wątek został usunięty" - "Rozmowa usunięta. Kliknij, by zobaczyć inną rozmowę w aplikacji SMS/MMS" - "Zablokowano" - "Odblokowano" - "Mała ilość miejsca. Część danych może zostać utracona." - "Wybierz załączniki" - "Potwierdź wybór" - "Wybrano %d" - "Usuń co najmniej jeden załącznik i spróbuj ponownie." - "Możesz spróbować wysłać wiadomość, ale może ona nie zostać dostarczona, dopóki nie usuniesz przynajmniej jednego załącznika." - "W jednej wiadomości możesz wysłać tylko jeden film. Usuń dodatkowe filmy i spróbuj ponownie." - "Nie udało się załadować załącznika w aplikacji SMS/MMS." - "Wyślij mimo wszystko" - "Nie udało się rozpocząć rozmowy" - "%1$s (%2$s)" - "Wybrano %s" - diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml deleted file mode 100644 index 3f99811..0000000 --- a/res/values-pt-rPT/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sem assunto" - "sem assunto" - - - "Sim" - "Não" - "OK" - "Eh eh" - "Obrigado" - "Aceito" - "Boa" - "Estou a caminho" - "OK, ligo mais tarde" - ":)" - ":(" - - diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml deleted file mode 100644 index e546031..0000000 --- a/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mensagens" - "Mensagens" - "Selecionar conversa" - "Definições" - "Enviar mensagem" - "Adicionar um anexo" - "Ajuda" - "Bem-vindo" - "Ignorar" - "Seguinte >" - "Seguinte" - "Sair" - "Definições >" - "Definições" - "A aplicação Mensagens necessita de autorização para SMS, Telefone e Contactos." - "Pode alterar as autorizações em Definições > Aplicações > Mensagens > Autorizações." - "Pode alterar as autorizações em Definições, Aplicações, Mensagens, Autorizações." - "Frequentes" - "Todos os contactos" - "Enviar para %s" - "Capturar imagens ou vídeo" - "Escolher imagens deste dispositivo" - "Gravar áudio" - "Escolher fotografia" - "O ficheiro multimédia está selecionado." - "O ficheiro multimédia está desselecionado." - "%d selecionado(s)" - "imagem de %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imagem" - "Gravar áudio" - "Partilhar" - "Agora mesmo" - "Agora" - - %d min - %d min - - - %d horas - %d hora - - - %d dias - %d dia - - - %d semanas - uma semana - - - %d meses - um mês - - - %d anos - um ano - - "Mensagem de classe 0" - "Guardar" - "O dispositivo tem pouco espaço disponível. A aplicação Mensagens elimina automaticamente mensagens mais antigas para libertar espaço." - "Está quase sem espaço de armazenamento" - "A aplicação Mensagens pode não enviar ou receber mensagens até que exista mais espaço disponível no dispositivo." - "Armazenamento SMS reduzido. Pode ter de eliminar mensagens." - "Confirmar número de telefone" - "Este passo único assegura que a aplicação Mensagens fornece as suas mensagens de grupo corretamente." - "Número de telefone" - "Eliminar todas as mensagens com multimédia" - "Eliminar mensagens com mais de %s" - "Eliminar automaticamente mensagens com mais de %s" - "Ignorar" - "Eliminar todas as mensagens com multimédia?" - "Eliminar mensagens com mais de %s?" - "Eliminar mensagens com mais de %s e ativar eliminação automática?" - "%s disse" - "Você disse" - "Mensagem de %s" - "Enviou uma mensagem" - "A enviar..." - "Não enviado. Toque para tentar novamente." - "Não enviado. A tentar novamente..." - "Reenviar ou eliminar" - "Faça uma chamada de voz para os serviços de emergência. Neste momento, não foi possível entregar a sua mensagem de texto." - "Falha" - "Nova mensagem MMS para transferir" - "Nova mensagem MMS" - "Não foi possível transferir" - "Toque para tentar novamente" - "Toque para transferir" - "Transferir ou eliminar" - "A transferir..." - "Mensagem expirada ou não disponível" - "tamanho: %1$s, data de validade: %2$s" - "Não é possível enviar. O destinatário não é válido." - "Serviço não ativado na rede" - "Não foi possível enviar devido a um problema de rede" - "Mensagem expirada ou não disponível" - "(Sem assunto)" - "Remetente desconhecido" - "Entregue" - "Não foi possível transferir a mensagem %1$s de %2$s." - "Não foi possível concluir a operação da base de dados devido a pouca memória" - "Mensagem não enviada" - "Algumas mensagens não enviadas na aplicação Mensagens" - - %d mensagens em %d conversas - %d mensagens numa conversa - - "Mensagem não transferida" - "Algumas mensagens não transferidas na aplicação Mensagens" - - %d mensagens em %d conversas - %d mensagens numa conversa - - "Mensagem para %1$s não enviada" - "Faça uma chamada de voz para os serviços de emergência. Neste momento, não foi possível entregar a sua mensagem de texto para o número %1$s." - - %d novas mensagens - Nova mensagem - - "Iniciar" - "Câmara não disponível" - "Câmara não disponível" - "Captura de vídeo não disponível" - "Não é possível guardar multimédia" - "Não é possível tirar a fotografia" - "Anterior" - "Arquivadas" - "Eliminar" - "Arquivar" - "Retirar do arquivo" - "Desativar as notificações" - "Ativar notificações" - "Adicionar contacto" - "Transferir" - "Enviar" - "Eliminar" - "Pretende eliminar esta mensagem?" - "Esta ação não pode ser anulada." - "Eliminar" - - Pretende eliminar estas conversas? - Pretende eliminar esta conversa? - - "Eliminar" - "Cancelar" - "Para" - "Selecionar várias imagens" - "Confirmar seleção" - "+%d" - "Não é possível gravar áudio. Tente novamente." - "Não é possível reproduzir áudio. Tente novamente." - "Não foi possível guardar áudio. Tente novamente." - "Toque sem largar" - ", " - " " - ": " - " " - "Imagem" - "Clipe de áudio" - "Vídeo" - "Cartão de contacto" - "Transferir" - "Responder por SMS" - "Responder por MMS" - "Responder" - - %d participantes - %d participante - - "Eu" - "Contacto bloqueado e arquivado" - "Contacto desbloqueado e não arquivado" - "%d arquivada(s)" - "%d não arquivada(s)" - "Notificações desativadas" - "Notificações ativadas" - "Tudo definido. Toque novamente em Enviar." - "A aplicação Mensagens foi definida com êxito como a aplicação de SMS predefinida." - - Rejeitar anexos - Rejeitar anexo - - "Anexo de áudio" - "Reproduzir anexo de áudio" - "Interromper" - "Mensagem de %s: %s." - "Mensagem falhada de %s: %s. Hora: %s." - "Mensagem de %s: %s. Hora: %s." - "Mensagem não enviada para %s: %s. Hora: %s." - "A enviar mensagem para %s: %s. Hora: %s." - "Mensagem falhada para %s: %s. Hora: %s." - "Mensagem para %s: %s. Hora: %s." - "Mensagem falhada de %s: %s. Hora: %s. %s." - "Mensagem de %s: %s. Hora: %s. %s." - "Mensagem não enviada para %s: %s. Hora: %s." - "A enviar mensagem para %s: %s. Hora: %s." - "Mensagem falhada para %s: %s. Hora: %s." - "Mensagem para %s: %s. Hora: %s." - "Mensagem falhada. Toque para tentar novamente." - "Conversa com %s" - "Eliminar assunto" - "Capturar vídeo" - "Capturar uma imagem fixa" - "Tirar fotografia" - "Começar a gravar vídeo" - "Mudar para a câmara de ecrã completo" - "Alternar entre as câmaras frontal e traseira" - "Parar gravação e anexar vídeo" - "Parar a gravação de vídeo" - "Fotos das Mensagens" - - %d fotos guardadas no álbum \"%s\" - %d foto guardada no álbum \"%s\" - - - %d vídeos guardados no álbum \"%s\" - %d vídeo guardado no álbum \"%s\" - - - %d anexos guardados no álbum \"%s\" - %d anexo guardado no álbum \"%s\" - - - %d anexos guardados em \"Transferências\" - %d anexo guardado em \"Transferências\" - - - %d anexos guardados - %d anexo guardado - - - Não foi possível guardar %d anexos - Não foi possível guardar %d anexo - - "Anexo de MMS guardado" - "Definições" - "Arquivadas" - "Fechar" - "MMS" - "Avançadas" - "Depurar" - "Notificações" - "Som" - "Silencioso" - "Vibrar" - "Bloqueado" - "Relatórios de entrega de SMS" - "Solicitar um relatório de entrega para cada SMS enviada" - "Recuperação automática" - "Recuperar automaticamente mensagens MMS" - "Recuperar automat. em roaming" - "Obter automaticamente MMS em roaming" - "Mensagens de grupo" - "Utilizar MMS para enviar uma única mensagem quando houver vários destinatários" - "Aplicação de SMS predefinida" - "Aplicação de SMS predefinida" - - - "O seu número de telefone" - "Desconhecido" - "Sons de mensagem enviada" - "Criar cópia de segurança de SMS" - "A cópia de segurança recebeu dados SMS não processados no ficheiro de armazenamento externo" - "Criar cópia de segurança de MMS" - "A cópia de segurança recebeu dados MMS não processados no ficheiro de armazenamento externo" - "Alertas sem fios" - "Opções da mensagem" - "Copiar texto" - "Ver detalhes" - "Eliminar" - "Encaminhar" - "Detalhes da mensagem" - "Tipo: " - "Mensagem de texto" - "Mensagem multimédia" - "De: " - "Para: " - "Enviadas: " - "Recebidas: " - "Assunto: " - "Tamanho: " - "Prioridade: " - "SIM: " - "Alta" - "Normal" - "Baixa" - "SIM %s" - "Endereço do remetente oculto" - "Não é possível enviar a mensagem enquanto os anexos estiverem a ser carregados." - "Não é possível carregar o anexo. Tente novamente." - "A rede não está pronta. Tente novamente." - "Eliminar texto" - "Alternar entre a introdução de texto e números" - "Adicionar mais participantes" - "Confirmar participantes" - "Iniciar nova conversa" - "Selecionar este item" - "Reproduzir vídeo" - "Pessoas e opções" - "Depurar" - "Pessoas e opções" - "Geral" - "Pessoas nesta conversa" - "Efetuar uma chamada" - "Enviar mensagem" - "Enviar mensagem<br/><small>de %s</small>" - - Enviar fotos - Enviar foto - - - Enviar ficheiros de áudio - Enviar ficheiro de áudio - - - Enviar vídeos - Enviar vídeo - - - Enviar cartões de contacto - Enviar cartão de contacto - - - Enviar anexos - Enviar anexo - - - %d anexos prontos a enviar - Um anexo pronto a enviar - - "Enviar comentários" - "Ver na Google Play Store" - "Informações da versão" - "Versão %1$s" - "Licenças de código aberto" - "Notificações" - "Foi atingido o limite de anexos" - "Falha ao carregar o anexo." - "Adicionar a Contactos?" - "Adicionar contacto" - "Assunto" - "Assunto: " - "%s%s" - "A carregar o cartão de contacto" - "Não foi possível carregar o cartão de contacto" - "Ver cartão de contacto" - - %d contactos - %d contacto - - "Cartões de contacto" - "Aniversário" - "Notas" - "Encaminhar mensagem" - "Responder" - "+1" - "+%d" - "SMS desativado" - "Para enviar, defina a aplicação Mensagens como a aplicação de SMS predefinida" - "Defina a aplicação Mensagens como a aplicação de SMS predefinida" - "Alterar" - "Para receber mensagens, defina a aplicação Mensagens como a aplicação de SMS predefinida" - "Nenhum SIM preferido selecionado para o envio de mensagens SMS" - "Esta aplicação não é permitida pelo proprietário do dispositivo." - "OK" - "Demasiados participantes numa conversa" - - Contactos inválidos - Contacto inválido - - "Não foi possível carregar a imagem da câmara" - "Utilizador: " - "%s: " - "Rascunho" - "Ao iniciar uma nova conversa, esta será listada aqui" - "As conversas arquivadas aparecem aqui" - "A carregar conversações..." - "Imagem" - "Clipe de áudio" - "Vídeo" - "Cartão de contacto" - "MMS" - "Anular" - "Tentar novamente" - "Introduza um nome ou número de telefone do contacto para iniciar uma nova mensagem" - "Bloquear" - "Bloquear %s" - "Desbloquear %s" - "Pretende bloquear %s?" - "O utilizador continuará a receber mensagens deste número, mas deixará de ser notificado. Esta conversa será arquivada." - "Contactos bloqueados" - "DESBLOQUEAR" - "Contactos bloqueados" - "Escolher imagem da biblioteca de documentos" - "A enviar mensagem" - "Mensagem enviada" - "Os dados celulares estão desligados. Verifique as suas definições." - "Não é possível enviar mensagens no modo de avião" - "Não foi possível enviar a mensagem" - "Mensagem transferida" - "Os dados celulares estão desligados. Verifique as suas definições." - "Não é possível transferir mensagens no modo de avião" - "Não foi possível transferir a mensagem" - "Zero" - "Um" - "Dois" - "Três" - "Quatro" - "Cinco" - "Seis" - "Sete" - "Oito" - "Nove" - "Não é possível enviar a mensagem com %1$s, erro %2$d" - "Não é possível enviar a mensagem com operador desconhecido, erro %1$d" - "Enc: %s" - "Mensagem não enviada: serviço não ativado na rede" - "Mensagem não enviada: endereço de destino inválido" - "Mensagem não enviada: mensagem inválida" - "Mensagem não enviada: conteúdo não suportado" - "Mensagem não enviada: a mensagem não é compatível" - "Mensagem não enviada: demasiado grande" - "Nova mensagem" - "Ver" - "Imagem" - "Não foi possível encontrar uma aplicação adequada" - "Remover o destinatário" - "Nova mensagem" - "Cancelar" - "Editar ponto de acesso" - "Não definido" - "Nome" - "APN" - "MMSC" - "Proxy de MMS" - "Porta MMS" - "MCC" - "MNC" - "Tipo de APN" - "Eliminar APN" - "Novo APN" - "Guardar" - "Rejeitar" - "O campo Nome não pode estar vazio." - "O APN não pode estar vazio." - "O campo MCC tem de ter 3 dígitos." - "O campo MNC tem de ter 2 ou 3 dígitos." - "A restaurar as predefinições do APN" - "Repor as predefinições" - "Reposição das predefinições do APN concluída." - "Sem nome" - "Nomes dos Pontos de Acesso" - "APNs" - "Novo APN" - "As definições de Nome do Ponto de Acesso não estão disponíveis para este utilizador" - "Pretende copiar para a área de transferência?" - "Copiar" - "em %s" - "Geral" - "Avançadas" - "Definições gerais" - "Definições avançadas" - "SIM \"%s\"" - "Enviar mensagens SMS individuais para todos os destinatários. Apenas o utilizador recebe as respostas" - "Enviar uma única MMS para todos os destinatários" - "Número desconhecido" - "Nova mensagem" - "Nova mensagem." - "Seletor de SIM" - "%1$s selecionado, seletor de SIM" - "Editar assunto" - "Selecionar SIM ou editar assunto" - "Tocar sem soltar para gravar áudio" - "Iniciar nova conversa" - "Mensagens" - "Lista de mensagens" - "Mensagens" - "Nova mensagem" - "Lista de conversas" - "A carregar conversas" - "A carregar mensagens" - "Ver mais conversas" - "Ver mais mensagens" - "Conversa eliminada" - "Conversa eliminada. Toque para mostrar uma conversa das Mensagens diferente" - "Bloqueado" - "Desbloqueado" - "O espaço de armazenamento é reduzido. Alguns dados podem ser perdidos." - "Selecionar anexos" - "Confirmar seleção" - "%d selecionado(s)" - "Remova um ou mais anexos e tente novamente." - "Pode tentar enviar a sua mensagem, mas esta pode não ser entregue se não remover um ou mais anexos." - "Apenas pode enviar um vídeo por mensagem. Remova os vídeos adicionais e tente novamente." - "Falha da aplicação Mensagens ao carregar o anexo." - "Enviar mesmo assim" - "Não foi possível iniciar a conversa" - "%1$s (%2$s)" - "%s selecionado" - diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml deleted file mode 100644 index 0ab0334..0000000 --- a/res/values-pt/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "sem assunto" - "nosubject" - - - "Sim" - "Não" - "Ok" - "Hehe" - "Obrigado" - "Concordo" - "Legal" - "Estou a caminho" - "OK, falo com você mais tarde" - ":)" - ":(" - - diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml deleted file mode 100644 index c9fdfad..0000000 --- a/res/values-pt/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mensagens" - "Mensagens" - "Selecionar uma conversa" - "Configurações" - "Enviar mensagem" - "Adicionar um anexo" - "Ajuda" - "Bem-vindo" - "Pular" - "Próxima >" - "Próximo" - "Sair" - "Configurações >" - "Configurações" - "O app Mensagens precisa de permissão para SMS, telefone e contatos." - "É possível alterar as permissões em \"Config. > Apps > Mensagens > Permissões\"." - "É possível alterar as permissões em \"Config. > Apps > Mensagens > Permissões\"." - "Frequentes" - "Todos os contatos" - "Enviar para %s" - "Capturar fotos ou vídeos" - "Escolher imagens do dispositivo" - "Gravar áudio" - "Escolher foto" - "A mídia foi selecionada." - "A mídia foi desmarcada." - "%d selecionado" - "imagem %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imagem" - "Gravar áudio" - "Compartilhar" - "Há pouco" - "Agora" - - %d min - %d min - - - %d horas - %d horas - - - %d dias - %d dias - - - %d semanas - %d semanas - - - %d meses - %d meses - - - %d anos - %d anos - - "Mensagem classe 0" - "Salvar" - "O dispositivo está com pouco espaço. As mensagens mais antigas serão excluídas automaticamente para liberar espaço." - "Pouco espaço de armazenamento" - "O app Mensagens talvez não envie ou receba mensagens até que mais espaço esteja disponível no seu dispositivo." - "Pouco espaço para SMS. Pode ser necessário excluir mensagens." - "Confirme seu número de telefone" - "Esta etapa única garantirá que o app Mensagens entregue suas mensagens em grupo corretamente." - "Número de telefone" - "Excluir todas as mensagens com mídia" - "Excluir mensagens com mais de %s" - "Excluir automaticamente mensagens com mais de %s" - "Ignorar" - "Excluir todas as mensagens com mídia?" - "Excluir mensagens com mais de %s?" - "Excluir mensagens com mais de %s e ativar a exclusão automática?" - "%s disse" - "Você disse" - "Mensagem de %s" - "Você enviou uma mensagem" - "Enviando…" - "Não enviado. Toque para tentar novamente." - "Não enviado. Tentando novamente…" - "Reenviar ou excluir" - "Faça uma chamada de voz para os serviços de emergência. Não foi possível entregar sua mensagem de texto no momento." - "Falhou" - "Nova mensagem MMS para fazer o download" - "Nova mensagem MMS" - "Falha no download" - "Tocar para tentar novamente" - "Tocar para fazer o download" - "Fazer o download ou excluir" - "Fazendo o download…" - "A mensagem expirou ou não está disponível" - "tamanho: %1$s, validade: %2$s" - "Falha ao enviar. Destinatário inválido." - "Serviço não ativado na rede" - "Falha ao enviar devido a um problema de rede" - "A mensagem expirou ou não está disponível" - "(Sem assunto)" - "Remetente desconhecido" - "Entregue" - "Não foi possível fazer o download da mensagem %1$s de %2$s." - "Não foi possível concluir a operação do banco de dados devido a memória insuficiente" - "Mensagem não enviada" - "Algumas mensagens não foram enviadas no app Mensagens" - - %d mensagens em %d conversas - %d mensagens em %d conversas - - "Download da mensagem não realizado" - "Não foi feito o download de algumas mensagens no app Mensagens" - - %d mensagens em %d conversas - %d mensagens em %d conversas - - "Mensagem para %1$s não enviada" - "Faça uma chamada de voz para os serviços de emergência. Não foi possível entregar sua mensagem de texto para %1$s no momento." - - %d novas mensagens - %d novas mensagens - - "Iniciar" - "Câmera não disponível" - "Câmera não disponível" - "Captura de vídeo não disponível" - "Falha ao salvar a mídia" - "Não é possível tirar uma foto" - "Voltar" - "Arquivadas" - "Excluir" - "Arquivar" - "Desarquivar" - "Desativar notificações" - "Ativar notificações" - "Adicionar contato" - "Fazer o download" - "Enviar" - "Excluir" - "Excluir esta mensagem?" - "Esta ação não pode ser desfeita." - "Excluir" - - Excluir estas conversas? - Excluir estas conversas? - - "Excluir" - "Cancelar" - "Para" - "Selecionar várias imagens" - "Confirmar seleção" - "+%d" - "Falha ao gravar o áudio. Tente novamente." - "Falha ao reproduzir o áudio. Tente novamente." - "Falha ao salvar o áudio. Tente novamente." - "Toque e mantenha pressionado" - ", " - " " - ": " - " " - "Imagem" - "Clipe de áudio" - "Vídeo" - "Cartão de visita" - "Download" - "Responder via SMS" - "Responder via MMS" - "Responder" - - %d participantes - %d participantes - - "Eu" - "Contato bloqueado e arquivado" - "Contato desbloqueado e desarquivado" - "%d arquivada(s)" - "%d desarquivada(s)" - "Notificações desativadas" - "Notificações ativadas" - "Tudo pronto. Toque em \"Enviar\" novamente." - "O app Mensagens foi definido como o app padrão de SMS." - - Descartar anexos - Descartar anexos - - "Anexo de áudio" - "Reproduzir anexo de áudio" - "Pausa" - "Mensagem de %s: %s." - "Falha na mensagem de %s: %s. Hora: %s." - "Mensagem de %s: %s. Hora: %s." - "Mensagem não enviada para %s: %s. Hora: %s." - "Enviando mensagem para %s: %s. Hora: %s." - "Falha na mensagem para %s: %s. Hora: %s." - "Mensagem para %s: %s. Hora: %s." - "Falha na mensagem de %s: %s. Hora: %s. %s." - "Mensagem de %s: %s. Hora: %s. %s." - "Mensagem não enviada para %s: %s. Hora: %s." - "Enviando mensagem para %s: %s. Hora: %s." - "Falha na mensagem para %s: %s. Hora: %s." - "Mensagem para %s: %s. Hora: %s." - "Falha na mensagem. Toque para tentar novamente." - "Conversa com %s" - "Excluir assunto" - "Capturar vídeo" - "Capturar uma imagem estática" - "Tirar foto" - "Iniciar gravação de vídeo" - "Alternar para câmera em tela cheia" - "Alternar entre câmera frontal e traseira" - "Parar a gravação e anexar vídeo" - "Parar gravação de vídeo" - "Fotos do app Mensagens" - - %d fotos salvas no álbum \"%s\" - %d fotos salvas no álbum \"%s\" - - - %d vídeos salvos no álbum \"%s\" - %d vídeos salvos no álbum \"%s\" - - - %d anexos salvos no álbum \"%s\" - %d anexos salvos no álbum \"%s\" - - - %d anexos salvos em \"Downloads\" - %d anexos salvos em \"Downloads\" - - - %d anexos salvos - %d anexos salvos - - - Não foi possível salvar %d anexos - Não foi possível salvar %d anexos - - "Anexo MMS salvo" - "Configurações" - "Arquivadas" - "Fechar" - "MMS" - "Avançadas" - "Depurar" - "Notificações" - "Som" - "Silencioso" - "Vibração" - "Bloqueado" - "Relat. de entrega de SMS" - "Solicitar um relatório de entrega para cada SMS enviado" - "Recuperação automática" - "Recuperar mensagens MMS automaticamente" - "Recup. autom. em roaming" - "Recuperar MMS automaticamente durante roaming" - "Mensagens em grupo" - "Usar MMS para enviar uma única mensagem para vários destinatários" - "App de SMS padrão" - "App de SMS padrão" - - - "Seu número de telefone" - "Desconhecido" - "Sons de mensagem de saída" - "Descartar SMS" - "Descartar dados brutos de SMS recebidos no arquivo de armazenamento externo" - "Descartar MMS" - "Descartar dados brutos de MMS recebidos no arquivo de armazenamento externo" - "Alertas sem fio" - "Opções de mensagem" - "Copiar texto" - "Ver detalhes" - "Excluir" - "Avançar" - "Detalhes da mensagem" - "Tipo: " - "Mensagem de texto" - "Mensagem multimídia" - "De: " - "Para: " - "Enviada: " - "Recebida: " - "Assunto: " - "Tamanho: " - "Prioridade: " - "SIM: " - "Alta" - "Normal" - "Baixa" - "SIM %s" - "Endereço do remetente oculto" - "Não é possível enviar mensagens ao carregar anexos." - "Falha ao carregar o anexo. Tente novamente." - "A rede não está pronta. Tente novamente." - "Excluir texto" - "Alternar entre entrada de texto ou números" - "Adicionar mais participantes" - "Confirmar participantes" - "Iniciar nova conversa" - "Selecione este item" - "Reproduzir vídeo" - "Pessoas e opções" - "Depurar" - "Pessoas e opções" - "Gerais" - "Pessoas nesta conversa" - "Fazer uma chamada" - "Enviar mensagem" - "Enviar mensagem<br/><small>de %s</small>" - - Enviar fotos - Enviar fotos - - - Enviar áudios - Enviar áudios - - - Enviar vídeos - Enviar vídeos - - - Enviar cards de contato - Enviar cards de contato - - - Enviar anexos - Enviar anexos - - - %d anexos prontos para envio - %d anexos prontos para envio - - "Enviar feedback" - "Ver na Google Play Store" - "Informações da versão" - "Versão %1$s" - "Licenças de código aberto" - "Notificações" - "Limite de anexos alcançado" - "Falha ao carregar anexo." - "Adicionar a Contatos?" - "Adicionar contato" - "Assunto" - "Assunto: " - "%s%s" - "Carregando cartão de visita" - "Falha ao carregar o cartão de visita" - "Ver cartão de visita" - - %d contatos - %d contatos - - "Cartões de visita" - "Aniversário" - "Notas" - "Encaminhar mensagem" - "Responder" - "+1" - "+%d" - "SMS desativado" - "Para enviar, defina o Mensagens como app padrão de SMS" - "Definir o Mensagens como o app padrão de SMS" - "Alterar" - "Para receber mensagens, defina o Mensagens como app padrão de SMS" - "Nenhum SIM preferido para o envio de mensagens SMS selecionado" - "Este app não é permitido pelo proprietário do dispositivo." - "Ok" - "Muitos participantes em uma conversa" - - Contatos inválidos - Contatos inválidos - - "Falha ao carregar a imagem da câmera" - "Você: " - "%s: " - "Rascunho" - "Quando uma nova conversa for iniciada, será listada aqui" - "As conversas arquivadas aparecem aqui" - "Carregando conversas…" - "Imagem" - "Clipe de áudio" - "Vídeo" - "Cartão de visita" - "MMS" - "Desfazer" - "Repetir" - "Digite o nome ou telefone de um contato para iniciar uma nova mensagem" - "Bloquear" - "Bloquear %s" - "Desbloquear %s" - "Bloquear %s?" - "Você continuará recebendo mensagens deste número, mas não será notificado. Esta conversa será ativada." - "Contatos bloqueados" - "DESBLOQUEAR" - "Contatos bloqueados" - "Escolher imagem da biblioteca de documentos" - "Enviando mensagem" - "Mensagem enviada" - "Dados da rede celular desativados. Verifique suas configurações." - "Não é possível enviar mensagens no modo avião" - "Não foi possível enviar a mensagem" - "Mensagem baixada" - "Dados da rede celular desativados. Verifique suas configurações." - "Não é possível fazer o download de mensagens no modo Avião" - "Não foi possível fazer o download da mensagem" - "Zero" - "Um" - "Dois" - "Três" - "Quatro" - "Cinco" - "Seis" - "Sete" - "Oito" - "Nove" - "Não é possível enviar a mensagem com %1$s. Erro: %2$d" - "Não é possível enviar a mensagem com operadora desconhecida. Erro: %1$d" - "Enc: %s" - "Mensagem não enviada: serviço não ativado na rede" - "Mensagem não enviada: endereço de destino inválido" - "Mensagem não enviada: mensagem inválida" - "Mensagem não enviada: conteúdo não compatível" - "Mensagem não enviada: mensagem não compatível" - "Mensagem não enviada: muito grande" - "Nova mensagem" - "Visualizar" - "Imagem" - "Não foi possível encontrar um app adequado" - "Remover destinatário" - "Nova mensagem" - "Cancelar" - "Editar ponto de acesso" - "Não definido" - "Nome" - "APN" - "MMSC" - "Proxy MMS" - "Porta MMS" - "MCC" - "MNC" - "Tipo de APN" - "Excluir APN" - "Novo APN" - "Salvar" - "Descartar" - "O campo Nome não pode ficar vazio." - "O APN não pode estar vazio." - "O campo MCC deve ter 3 dígitos." - "O campo MNC deve ter 2 ou 3 dígitos." - "Restaurando as configurações APN padrão." - "Redefinir para o padrão" - "Redefinição das configurações padrão do APN concluída." - "Sem título" - "Nomes dos pontos de acesso" - "APNs" - "Novo APN" - "As configurações do Nome do ponto de acesso não estão disponíveis para este usuário" - "Copiar para área de transferência?" - "Copiar" - "para %s" - "Gerais" - "Avançadas" - "Configurações gerais" - "Configurações avançadas" - "SIM \"%s\"" - "Enviar mensagens SMS individuais para todos os destinatários. Só você receberá respostas" - "Enviar uma só mensagem MMS para todos os destinatários" - "Número desconhecido" - "Nova mensagem" - "Nova mensagem." - "Seletor de SIM" - "%1$s selecionado, seletor de SIM" - "Editar assunto" - "Selecione o SIM ou edite o assunto" - "Toque e segure para gravar áudio" - "Iniciar nova conversa" - "Mensagens" - "Lista de mensagens" - "Mensagens" - "Nova mensagem" - "Lista de conversas" - "Carregando conversas" - "Carregando mensagens" - "Ver mais conversas" - "Ver mais mensagens" - "Conversa excluída" - "Conversa excluída. Toque para mostrar outra conversa do app Mensagens" - "Contato bloqueado" - "Contato desbloqueado" - "O espaço de armazenamento está baixo. Alguns dados podem ser perdidos." - "Selecionar anexos" - "Confirmar seleção" - "%d selecionados" - "Remova um ou mais anexos e tente novamente." - "Tente enviar sua mensagem. Se ela não for entregue, remova um ou mais anexos." - "Você só pode enviar um vídeo por mensagem. Remova os vídeos adicionais e tente novamente." - "Ocorreu uma falha no app Mensagens ao carregar o anexo." - "Enviar mesmo assim" - "Não foi possível iniciar a conversa" - "%1$s (%2$s)" - "%s selecionado" - diff --git a/res/values-ro/arrays.xml b/res/values-ro/arrays.xml deleted file mode 100644 index dc5e3fe..0000000 --- a/res/values-ro/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "fără subiect" - "nosubject" - - - "Da" - "Nu" - "OK" - "Hehe" - "Mulțumesc" - "Sunt de acord" - "Frumos" - "Sunt pe drum" - "OK, revin mai târziu" - ":)" - ":(" - - diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml deleted file mode 100644 index 25b2d8c..0000000 --- a/res/values-ro/strings.xml +++ /dev/null @@ -1,545 +0,0 @@ - - - - - "Mesaje" - "Mesaje" - "Selectați conversația" - "Setări" - "Trimiteți mesajul" - "Adăugați un atașament" - "Ajutor" - "Bun venit" - "Omiteți" - "Înainte >" - "Înainte" - "Ieșiți" - "Setări >" - "Setări" - "Mesageria necesită permisiuni la SMS, Telefon și Agendă." - "Puteți să schimbați permisiunile din Setări > Aplicații > Mesagerie > Permisiuni." - "Puteți să schimbați permisiunile din Setări, Aplicații, Mesagerie, Permisiuni." - "Folosite frecvent" - "Toată agenda" - "Trimiteți la %s" - "Faceți fotografii sau înregistrați videoclipuri" - "Alegeți imagini de pe acest dispozitiv" - "Înregistrați conținut audio" - "Alegeți o fotografie" - "Conținutul media este selectat." - "Conținutul media este deselectat." - "Ați selectat %d" - "imagine din %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imagine" - "Înregistrați conținut audio" - "Trimiteți" - "Adineauri" - "Acum" - - %d minute - %d de minute - %d minut - - - %d ore - %d de ore - %d oră - - - %d zile - %d de zile - %d zi - - - %d săptămâni - %d de săptămâni - o săptămână - - - %d luni - %d de luni - o lună - - - %d ani - %d de ani - un an - - "Mesaje clasa 0" - "Salvați" - "Spațiul de stocare pe dispozitiv este redus. Mesageria va șterge automat mesaje mai vechi pentru a elibera spațiul." - "Spațiu de stocare limitat" - "Este posibil ca aplicația Mesagerie să nu trimită și să nu primească mesaje până când nu este disponibil mai mult spațiu pe dispozitiv." - "Spațiu de stocare scăzut pentru SMS-uri. Poate fi necesar să ștergeți unele mesaje." - "Confirmați numărul de telefon" - "Prin acest pas efectuat o singură dată vă asigurați că Mesageria va livra în mod corespunzător mesajele de grup." - "Număr de telefon" - "Ștergeți toate mesajele multimedia" - "Ștergeți mesajele mai vechi de %s" - "Ștergeți automat mesajele mai vechi de %s" - "Ignorați" - "Ștergeți toate mesajele multimedia?" - "Ștergeți mesajele mai vechi de %s?" - "Ștergeți mesajele mai vechi de %s și activați ștergerea automată?" - "%s a spus" - "Ați spus" - "Mesaj de la %s" - "Ați trimis un mesaj" - "Se trimite..." - "Nu s-a trimis. Atingeți pentru a încerca din nou." - "Nu s-a trimis. Se încearcă din nou..." - "Trimiteți din nou sau ștergeți" - "Inițiați un apel vocal către serviciile de urgență. Momentan, mesajul text nu a putut fi livrat." - "Eroare" - "Mesaj MMS nou de descărcat" - "Mesaj MMS nou" - "Descărcarea nu a reușit" - "Atingeți pentru a reîncerca" - "Atingeți pentru a descărca" - "Descărcați sau ștergeți" - "Se descarcă..." - "Mesajul a expirat sau nu este disponibil" - "dimensiune: %1$s, expirare: %2$s" - "Nu se poate trimite. Destinatar nevalid." - "Serviciul nu este activat în rețea" - "Mesajul nu s-a putut trimite din cauza unei probleme de rețea" - "Mesajul a expirat sau nu este disponibil." - "(Fără subiect)" - "Expeditor necunoscut" - "Livrat" - "Nu s-a putut descărca mesajul %1$s de la %2$s." - "Nu s-a putut finaliza operațiunea în baza de date din cauza memoriei reduse" - "Mesajul nu a fost trimis." - "Unele mesaje nu au fost trimise în Mesagerie" - - %d (de) mesaje în %d conversații - %d (de) mesaje în %d de conversații - %d (de) mesaje într-o conversație - - "Mesajul nu a fost descărcat" - "Unele mesaje nu au fost descărcate în Mesagerie" - - %d (de) mesaje în %d conversații - %d (de) mesaje în %d de conversații - %d (de) mesaje într-o conversație - - "Mesajul către %1$s nu a fost trimis" - "Inițiați un apel vocal către serviciile de urgență. Momentan, mesajul text către %1$s nu a putut fi livrat." - - %d mesaje noi - %d de mesaje noi - Mesaj nou - - "Începeți" - "Camera foto nu este disponibilă" - "Camera foto nu este disponibilă" - "Filmarea video nu este disponibilă" - "Nu se poate salva fișierul multimedia" - "Nu se poate fotografia" - "Înapoi" - "Arhivate" - "Ștergeți" - "Arhivați" - "Dezarhivați" - "Dezactivați notificările" - "Activați notificările" - "Adăugați o persoană" - "Descărcați" - "Trimiteți" - "Ștergeți" - "Ștergeți acest mesaj?" - "Această acțiune nu poate fi anulată." - "Ștergeți" - - Ștergeți aceste conversații? - Ștergeți aceste conversații? - Ștergeți această conversație? - - "Ștergeți" - "Anulați" - "Către" - "Selectați mai multe imagini" - "Confirmați selecția" - "+%d" - "Mesajul audio nu poate fi înregistrat. Încercați din nou." - "Mesajul audio nu poate fi redat. Încercați din nou." - "Mesajul audio nu a putut fi salvat. Încercați din nou." - "Apăsați lung" - ", " - " " - ": " - " " - "Imagine" - "Clip audio" - "Videoclip" - "Carte de vizită" - "Descărcați" - "Răspundeți prin SMS" - "Răsp. prin MMS" - "Răspundeți" - - %d participanți - %d de participanți - %d participant - - "Eu" - "Persoana de contact este blocată și arhivată" - "Persoană de contact deblocată și dezarhivată" - "%d arhivate" - "%d dezarhivate" - "Notificări dezactivate" - "Notificări activate" - "Gata. Atingeți din nou Trimiteți." - "Mesageria a fost setată ca aplicație prestabilită pentru SMS-uri." - - Eliminați atașamentele - Eliminați atașamentele - Eliminați atașamentul - - "Atașament audio" - "Redați atașamentul audio" - "Pauză" - "Mesaj de la %s: %s." - "Mesaj neprimit de la %s: %s. Ora: %s." - "Mesaj de la %s: %s. Ora: %s." - "Mesaj netrimis către %s: %s. Ora: %s." - "Se trimite mesajul către %s: %s. Ora: %s." - "Mesaj netrimis către %s: %s. Ora: %s." - "Mesaj către %s: %s. Ora: %s." - "Mesaj neprimit de la %s: %s. Ora: %s. %s." - "Mesaj de la %s: %s. Ora: %s. %s." - "Mesaj netrimis către %s: %s. Ora: %s." - "Se trimite mesajul către %s: %s. Ora: %s." - "Mesaj netrimis către %s: %s. Ora: %s." - "Mesaj către %s: %s. Ora: %s." - "Mesaj nelivrat. Atingeți pentru a reîncerca." - "Conversație cu %s" - "Ștergeți subiectul" - "Înregistrați conținut video" - "Fotografiați o imagine statică" - "Fotografiaţi" - "Începeți să înregistrați conținut video" - "Comutați la camera pe ecran complet" - "Comutați între camera foto frontală și camera foto posterioară" - "Opriți înregistrarea și atașați videoclipul" - "Opriți înregistrarea video" - "Fotografii din Mesagerie" - - %d fotografii salvate în albumul „%s - %d de fotografii salvate în albumul „%s - %d fotografie salvată în albumul „%s - - - %d videoclipuri salvate în albumul „%s - %d de videoclipuri salvate în albumul „%s - %d videoclip salvat în albumul „%s - - - %d atașamente salvate în albumul „%s - %d de atașamente salvate în albumul „%s - %d atașament salvat în albumul „%s - - - %d atașamente salvate în „Descărcări” - %d de atașamente salvate în „Descărcări” - %d atașament salvat în „Descărcări” - - - %d atașamente au fost salvate - %d de atașamente au fost salvate - %d atașament a fost salvat - - - Nu s-au putut salva %d atașamente - Nu s-au putut salva %d de atașamente - Nu s-a putut salva %d atașament - - "Atașamentul MMS a fost salvat" - "Setări" - "Arhivate" - "Închideți" - "MMS" - "Avansate" - "Remedierea erorilor" - "Notificări" - "Sunet" - "Silențios" - "Vibrații" - "Blocat" - "Rapoarte de trimitere a SMS-urilor" - "Solicitați un raport de expediere pentru fiecare mesaj SMS trimis" - "Preluare automată" - "Preia automat mesajele MMS" - "Preluare automată în roaming" - "Preia automat mesajele MMS în roaming" - "Mesagerie de grup" - "Utilizați serviciul MMS pentru a trimite un singur mesaj, atunci când există mai mulți destinatari" - "Aplicație SMS prestabilită" - "Aplicație SMS prestabilită" - - - "Numărul dvs. de telefon" - "Necunoscut" - "Sunete pentru mesaje trimise" - "Copiați mesajele SMS" - "Copiați datele brute din mesajele SMS primite în fișierul de stocare externă" - "Copiați mesajele MMS" - "Copiați datele brute din mesajele MMS primite în fișierul de stocare externă" - "Alerte wireless" - "Opţiuni mesaj" - "Copiați textul" - "Vedeți detaliile" - "Ștergeți" - "Redirecționați" - "Detalii mesaj" - "Tip: " - "Mesaj text" - "Mesaj multimedia" - "De la: " - "Către: " - "Trimis: " - "Primit: " - "Subiect: " - "Dimensiune: " - "Prioritate: " - "SIM: " - "Ridicată" - "Normală" - "Scăzută" - "SIM %s" - "Adresa expeditorului este ascunsă" - "Nu se poate trimite mesajul în timp ce se încarcă atașamente." - "Nu se poate încărca atașamentul. Încercați din nou." - "Rețeaua nu este gata. Încercați din nou." - "Ștergeți textul" - "Comutați între introducere de text și numere" - "Adăugați mai mulți participanți" - "Confirmați participanții" - "Începeți o conversație nouă" - "Selectați acest articol" - "Redați videoclipul" - "Persoane și opțiuni" - "Remediați erorile" - "Persoane și opțiuni" - "Generale" - "Persoane participante la conversație" - "Efectuați un apel" - "Trimiteți mesajul" - "Trimiteți mesajul<br/><small>de pe cardul %s</small>" - - Trimiteți fotografiile - Trimiteți fotografiile - Trimiteți fotografia - - - Trimiteți fișierele audio - Trimiteți fișierele audio - Trimiteți fișierul audio - - - Trimiteți videoclipurile - Trimiteți videoclipurile - Trimiteți videoclipul - - - Trimiteți cărțile de vizită - Trimiteți cărțile de vizită - Trimiteți cartea de vizită - - - Trimiteți atașamentele - Trimiteți atașamentele - Trimiteți atașamentul - - - %d atașamente gata de trimis - %d de atașamente gata de trimis - Un atașament gata de trimis - - "Trimiteți feedback" - "Vedeți în Magazinul Google Play" - "Informații despre versiune" - "Versiunea %1$s" - "Licențe open source" - "Notificări" - "S-a atins limita pentru atașamente" - "Atașamentul nu a putut fi încărcat." - "Adăugați în Agendă?" - "Adăugați o persoană" - "Subiect" - "Subiect: " - "%s%s" - "Se încarcă cartea de vizită" - "Cartea de vizită nu a putut fi încărcată" - "Vedeți cartea de vizită" - - %d persoane de contact - %d de persoane de contact - %d persoană de contact - - "Cărți de vizită" - "Ziua de naștere" - "Note" - "Redirecționați mesajul" - "Răspundeți" - "+1" - "+%d" - "SMS-urile sunt dezactivate" - "Pentru a trimite mesaje, setați Mesageria ca aplicație prestabilită pentru SMS-uri" - "Setați Mesageria ca aplicație prestabilită pentru SMS-uri" - "Modificați" - "Pentru a primi mesaje, setați Mesageria ca aplicație prestabilită pentru SMS-uri" - "Pentru trimiterea mesajelor SMS nu este selectat niciun card SIM preferat" - "Această aplicație nu este acceptată de proprietarul dispozitivului." - "OK" - "Prea mulți participanți la o conversație" - - Persoane de contact nevalide - Persoane de contact nevalide - Persoană de contact nevalidă - - "Nu s-a putut încărca imaginea de la camera foto" - "Dvs.: " - "%s: " - "Mesaj nefinalizat" - "Când începeți o nouă conversație, o veți vedea aici" - "Conversațiile arhivate apar aici" - "Conversațiile sunt în curs de încărcare..." - "Imagine" - "Clip audio" - "Videoclip" - "Carte de vizită" - "MMS" - "Anulați" - "Reîncercați" - "Introduceți numele unei persoane de contact sau un număr de telefon pentru a începe un nou mesaj" - "Blocați" - "Blocați %s" - "Deblocați-l(o) pe %s" - "Blocați %s?" - "Veți primi, în continuare, mesaje de la acest număr, dar nu veți mai primi notificări. Această conversație va fi arhivată." - "Persoane din Agendă blocate" - "DEBLOCAȚI" - "Persoane din Agendă blocate" - "Alegeți imaginea din biblioteca de documente" - "Se trimite mesajul" - "Mesaj trimis" - "Datele mobile sunt dezactivate. Verificați setările." - "Nu se pot trimite mesaje în modul Avion" - "Mesajul nu a putut fi trimis" - "Mesaj descărcat" - "Datele mobile sunt dezactivate. Verificați setările." - "Nu se pot descărca mesaje în modul Avion" - "Mesajul nu a putut fi descărcat" - "Zero" - "Unu" - "Doi" - "Trei" - "Patru" - "Cinci" - "Șase" - "Șapte" - "Opt" - "Nouă" - "Nu se poate trimite mesaj prin %1$s, eroare %2$d" - "Nu se poate trimite mesaj printr-un operator necunoscut, eroare %1$d" - "Redir.: %s" - "Mesajul nu a fost trimis: serviciul nu este activat în rețea" - "Mesajul nu a fost trimis: adresă de destinație nevalidă" - "Mesajul nu a fost trimis: mesaj nevalid" - "Mesajul nu a fost trimis: conținut neacceptat" - "Mesajul nu a fost trimis: mesaj neacceptat" - "Mesajul nu a fost trimis: mesaj prea mare" - "Mesaj nou" - "Afișați" - "Imagine" - "Nu am găsit o aplicație adecvată" - "Eliminați un destinatar" - "Mesaj nou" - "Anulați" - "Modificaţi punctul de acces" - "Nesetat" - "Nume" - "APN" - "MMSC" - "Proxy MMS" - "Port MMS" - "MCC" - "MNC" - "Tip APN" - "Ștergeți APN" - "APN nou" - "Salvați" - "Renunțați" - "Câmpul Nume trebuie completat." - "APN-ul trebuie completat." - "Câmpul MCC trebuie să conțină 3 cifre." - "Câmpul MNC trebuie să conțină 2 sau 3 cifre." - "Se restabilesc setările APN prestabilite." - "Resetați la valorile prestabilite" - "Resetarea setărilor APN prestabilite a fost finalizată." - "Fără titlu" - "Numele punctelor de acces" - "APN-uri" - "APN nou" - "Setările pentru „Nume puncte de acces” nu sunt disponibile pentru acest utilizator" - "Copiați în clipboard?" - "Copiați" - "pe cardul %s" - "Generale" - "Avansate" - "Setări generale" - "Setări avansate" - "Cartela SIM „%s”" - "Trimiteți mesaje SMS individuale către toți destinatarii. Numai dvs. veți primi răspunsurile." - "Trimiteți un singur mesaj MMS către toți destinatarii" - "Număr necunoscut" - "Mesaj nou" - "Mesaj nou." - "Selector de SIM" - "%1$s a fost selectat, selector de SIM" - "Editați subiectul" - "Selectați cardul SIM sau editați subiectul" - "Atingeți lung pentru a înregistra conținutul audio" - "Începeți o conversație nouă" - "Mesagerie" - "Listă Mesagerie" - "Mesagerie" - "Mesaj nou" - "Lista de conversații" - "Se încarcă conversațiile" - "Se încarcă mesajele" - "Vedeți mai multe conversații" - "Vedeți mai multe mesaje" - "Conversație ștearsă" - "Conversația a fost ștearsă. Atingeți pentru a afișa altă conversație din Mesagerie" - "Blocat" - "Deblocat" - "Spațiul de stocare este scăzut. Se pot pierde unele date." - "Selectați atașamentele" - "Confirmați selecția" - "Ați selectat %d" - "Eliminați unul sau mai multe atașamente și încercați din nou." - "Puteți încerca să trimiteți mesajul, însă dacă nu eliminați unul sau mai multe atașamente, este posibil ca acesta să nu fie livrat." - "Puteți trimite doar un videoclip per mesaj. Eliminați videoclipurile suplimentare și încercați din nou." - "Mesageria nu a putut încărca atașamentul." - "Trimiteți oricum" - "Conversația nu a putut fi începută" - "%1$s (%2$s)" - "%s selectat" - diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml deleted file mode 100644 index 8af4db2..0000000 --- a/res/values-ru/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "без темы" - "без темы" - - - "Да" - "Нет" - "ОК" - "Ха-ха" - "Спасибо!" - "Хорошо" - "Отлично" - "Скоро буду" - "ОК, отвечу позже" - ":)" - ":(" - - diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml deleted file mode 100644 index 0683b2b..0000000 --- a/res/values-ru/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "SMS/MMS" - "SMS/MMS" - "Выберите чат" - "Настройки" - "Отправить сообщение" - "Прикрепить файл" - "Справка" - "Добро пожаловать!" - "Пропустить" - "Далее >" - "Далее" - "Закрыть" - "Настройки >" - "Настройки" - "Приложению SMS/MMS требуются разрешения категорий SMS, \"Телефон\" и \"Контакты\"." - "Чтобы изменить разрешения, нажмите \"Настройки > Приложения > SMS/MMS > Разрешения\"." - "Чтобы изменить разрешения, нажмите \"Настройки > Приложения > SMS/MMS > Разрешения\"." - "Частые" - "Все контакты" - "Отправить на номер %s" - "Снять фото или видео" - "Выбрать изображения на устройстве" - "Записать аудио" - "Выбрать фотографию" - "Медиафайл выбран" - "Выбор отменен" - "Выбрано: %d" - "изображение %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "изображение" - "Запись аудио" - "Поделиться" - "Только что" - "Только что" - - %d минута - %d минуты - %d минут - %d минуты - - - %d час - %d часа - %d часов - %d часа - - - %d день - %d дня - %d дней - %d дня - - - %d неделя - %d недели - %d недель - %d недели - - - %d месяц - %d месяца - %d месяцев - %d месяца - - - %d год - %d года - %d лет - %d года - - "Сообщение класса 0" - "Сохранить" - "Свободное место в памяти устройства заканчивается. Чтобы освободить его, приложение удалит старые сообщения." - "Недостаточно памяти" - "Недостаточно свободного места, чтобы отправлять и получать сообщения в SMS/MMS." - "Для хранения SMS осталось мало места. Удалите старые сообщения." - "Подтверждение номера телефона" - "Номер требуется для отправки групповых сообщений в SMS/MMS. Его нужно ввести только один раз." - "Номер телефона" - "Удалить сообщения с медиафайлами" - "Удалить сообщения старше %s" - "Автоматически удалять сообщения старше %s" - "Игнорировать" - "Удалить сообщения с медиафайлами?" - "Удалить сообщения старше %s?" - "Удалить сообщения старше %s и включить автоматическое удаление?" - "%s пишет:" - "Вы сказали:" - "Сообщение от пользователя %s" - "Вы отправили сообщение" - "Отправка…" - "Сообщение не отправлено. Нажмите, чтобы повторить попытку." - "Сообщение не отправлено. Повторная попытка…" - "Переслать или удалить" - "Не удалось доставить сообщение. Чтобы связаться с экстренными службами, позвоните по этому номеру." - "Не удалось отправить" - "Скачайте новое MMS-сообщение" - "Новое сообщение MMS" - "Не удалось скачать" - "Нажмите, чтобы повторить попытку" - "Нажмите, чтобы скачать" - "Скачать или удалить" - "Скачивание…" - "Срок действия сообщения истек, или оно недоступно" - "размер: %1$s, срок действия: %2$s" - "Не удалось отправить сообщение: укажите действительного получателя." - "Услуга не активирована в сети" - "Не удалось отправить сообщение из-за ошибки сети" - "Срок действия сообщения истек, или оно недоступно" - "Без темы" - "Неизвестный отправитель" - "Доставлено" - "Не удалось загрузить сообщение \"%1$s\" от пользователя %2$s" - "Не удалось выполнить операцию с базой данных: недостаточно памяти" - "Сообщение не отправлено" - "Некоторые сообщения в SMS/MMS не отправлены" - - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - - "Не удалось загрузить сообщение" - "Некоторые сообщения в SMS/MMS не скачаны" - - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - Сообщений: %d, чатов: %d - - "SMS на номер %1$s не отправлено" - "Не удалось доставить сообщение на номер %1$s. Позвоните в экстренную службу по телефону." - - %d новое сообщение - %d новых сообщения - %d новых сообщений - %d нового сообщения - - "Начать беседу" - "Камера недоступна" - "Камера недоступна" - "Видеозапись недоступна" - "Не удалось сохранить" - "Не удалось сфотографировать" - "Назад" - "Архив" - "Удалить" - "Архив" - "Вернуть из архива" - "Отключить оповещения" - "Включить оповещения" - "Добавить контакт" - "Скачать" - "Отправить" - "Удалить" - "Удалить сообщение?" - "Это действие нельзя будет отменить." - "Удалить" - - Удалить эти чаты? - Удалить эти чаты? - Удалить эти чаты? - Удалить эти чаты? - - "Удалить" - "Отмена" - "Кому" - "Выбрать несколько изображений" - "Подтверждение выбора" - "+%d" - "Не удалось записать. Повторите попытку." - "Не удалось воспроизвести. Повторите попытку." - "Не удалось сохранить. Повторите попытку." - "Нажмите и удерживайте" - ", " - " " - ": " - " " - "Изображение" - "Аудиоклип" - "Видео" - "Карточка контакта" - "Скачать" - "Отправить SMS" - "Ответить MMS" - "Ответить" - - %d участник - %d участника - %d участников - %d участника - - "Вы" - "Контакт заблокирован, переписка помещена в архив" - "Контакт разблокирован, чат возвращен из архива" - "Добавлено в архив: %d" - "Возвращено из архива: %d" - "Уведомления отключены" - "Уведомления включены" - "Все готово. Нажмите \"Отправить\" ещё раз." - "Вы назначили SMS/MMS приложением для SMS по умолчанию" - - Убрать прикрепленные файлы - Убрать прикрепленные файлы - Убрать прикрепленные файлы - Убрать прикрепленные файлы - - "Аудиофайл" - "Воспроизвести прикрепленный аудиофайл" - "Пауза" - "Сообщение от пользователя %s: %s." - "Не удалось отправить сообщение от пользователя %s: %s. Время: %s." - "Сообщение от пользователя %s: %s. Время: %s." - "Неотправленное сообщение для пользователя %s: %s. Время: %s." - "Отправляется сообщение для пользователя %s: %s. Время: %s." - "Не удалось отправить сообщение для пользователя %s: %s. Время: %s." - "Сообщение для пользователя %s: %s. Время: %s." - "Не удалось отправить сообщение от пользователя %s: %s. Время: %s. %s." - "Сообщение от пользователя %s: %s. Время: %s. %s." - "Неотправленное сообщение для группы %s: %s. Время: %s." - "Отправляется сообщение для группы %s: %s. Время: %s." - "Не удалось отправить сообщение для группы %s: %s. Время: %s." - "Сообщение для группы %s: %s. Время: %s." - "Не удалось отправить сообщение. Нажмите, чтобы повторить попытку." - "Чат с пользователями %s" - "Удалить тему." - "Снять видео" - "Сделать стоп-кадр" - "Сделать снимок" - "Начать запись видео" - "Перейти в полноэкранный режим" - "Переключение между основной и фронтальной камерами" - "Остановить запись и прикрепить видео" - "Остановить запись видео" - "Фото из SMS/MMS" - - %d фото сохранено в альбоме \"%s\" - %d фото сохранено в альбоме \"%s\" - %d фото сохранено в альбоме \"%s\" - %d фото сохранено в альбоме \"%s\" - - - %d видео сохранено в альбоме \"%s\" - %d видео сохранено в альбоме \"%s\" - %d видео сохранено в альбоме \"%s\" - %d видео сохранено в альбоме \"%s\" - - - %d прикрепленный файл сохранен в альбоме \"%s\" - %d прикрепленных файла сохранено в альбоме \"%s\" - %d прикрепленных файлов сохранено в альбоме \"%s\" - %d прикрепленных файла сохранено в альбоме \"%s\" - - - %d прикрепленный файл сохранен в Загрузках - %d прикрепленных файла сохранено в Загрузках - %d прикрепленных файлов сохранено в Загрузках - %d прикрепленных файла сохранено в Загрузках - - - %d прикрепленный файл сохранен - %d прикрепленных файла сохранено - %d прикрепленных файлов сохранено - %d прикрепленного файла сохранено - - - Не удалось сохранить %d прикрепленный файл - Не удалось сохранить %d прикрепленных файла - Не удалось сохранить %d прикрепленных файлов - Не удалось сохранить %d прикрепленного файла - - "Сохраненный файл из MMS" - "Настройки" - "Архив" - "Закрыть" - "MMS" - "Дополнительные настройки" - "Выполнить отладку" - "Уведомления" - "Звуковой сигнал" - "Без звука" - "Вибросигнал" - "Заблокировано" - "Отчеты о доставке SMS" - "Запрашивать отчет о доставке для всех отправляемых SMS" - "Автополучение" - "Автоматически загружать MMS-сообщения" - "Автополучение в роуминге" - "Автоматически загружать MMS-сообщения в роуминге" - "Групповые сообщения" - "Использовать MMS для отправки одного сообщения нескольким получателям" - "Приложение для обмена SMS" - "Приложение для обмена SMS" - - - "Ваш номер телефона" - "Неизвестный" - "Сигнал исходящего сообщения" - "Передача SMS" - "Передать необработанные данные полученных SMS во внешнее хранилище" - "Передача MMS" - "Передать необработанные данные полученных MMS во внешнее хранилище" - "Оповещение населения" - "Параметры сообщения" - "Копировать текст" - "Сведения" - "Удалить" - "Переслать" - "Сведения о сообщении" - "Тип: " - "SMS" - "MMS" - "От: " - "Кому: " - "Отправлено: " - "Получено: " - "Тема: " - "Размер: " - "Приоритет: " - "SIM-карта: " - "Высокий" - "Обычный" - "Низкий" - "SIM-карта %s" - "Адрес отправителя скрыт" - "Не удается отправить сообщение. Дождитесь завершения загрузки прикрепленных файлов." - "Не удалось загрузить прикрепленный файл. Повторите попытку." - "Сеть не готова. Повторите попытку." - "Удалить текст" - "Переключение между вводом букв и цифр" - "Добавить участников" - "Подтвердить участников" - "Начать чат" - "Выбрать этот вариант" - "Воспроизвести видео" - "Участники и параметры" - "Выполнить отладку" - "Участники и параметры" - "Общие" - "Участники чата" - "Позвонить" - "Отправить сообщение" - "Отправить сообщение<br/><small>с %s</small>" - - Отправить фото - Отправить фото - Отправить фото - Отправить фото - - - Отправить аудио - Отправить аудио - Отправить аудио - Отправить аудио - - - Отправить видео - Отправить видео - Отправить видео - Отправить видео - - - Отправить карточки контактов - Отправить карточки контактов - Отправить карточки контактов - Отправить карточки контактов - - - Отправить прикрепленные файлы - Отправить прикрепленные файлы - Отправить прикрепленные файлы - Отправить прикрепленные файлы - - - %d прикрепленный файл готов к отправке - %d прикрепленных файла готовы к отправке - %d прикрепленных файлов готовы к отправке - %d прикрепленного файла готовы к отправке - - "Отправить отзыв" - "Искать в Play Маркете" - "Версия" - "Версия %1$s" - "Лицензии открытого ПО" - "Уведомления" - "Прикреплено максимальное количество файлов" - "Не удалось загрузить прикрепленный файл" - "Добавить в контакты?" - "Добавить" - "Тема" - "Тема: " - "%s%s" - "Загрузка карточки контакта…" - "Не удалось загрузить карточку контакта" - "Посмотреть карточку контакта" - - %d контакт - %d контакта - %d контактов - %d контакта - - "Карточки контактов" - "День рождения" - "Примечания" - "Пересылка сообщения" - "Ответ" - "и ещё 1" - "и ещё %d" - "SMS-служба отключена" - "Чтобы отправлять сообщения, назначьте SMS/MMS приложением для SMS по умолчанию" - "Назначьте SMS/MMS приложением для SMS по умолчанию" - "Изменить" - "Чтобы получать сообщения, сделайте SMS/MMS приложением для SMS по умолчанию" - "Не выбрана SIM-карта для отправки SMS-сообщений" - "Владелец устройства запретил использовать это приложение." - "ОК" - "Слишком много участников" - - Контакты не найдены - Контакты не найдены - Контакты не найдены - Контакты не найдены - - "Не удалось загрузить изображение с камеры" - "Вы: " - "%s: " - "Черновик" - "Когда вы начнете переписку, она появится здесь" - "Здесь появятся архивированные чаты" - "Загрузка цепочек писем…" - "Изображение" - "Аудиоклип" - "Видео" - "Карточка контакта" - "MMS" - "Отменить" - "Повторить" - "Введите имя пользователя или номер телефона" - "Заблокировать" - "Заблокировать пользователя %s" - "Разблокировать пользователя %s" - "Заблокировать %s?" - "Вы по-прежнему будете получать сообщения с этого номера, но не увидите оповещений о них. Этот чат будет архивирован." - "Заблокированные контакты" - "РАЗБЛОКИРОВАТЬ" - "Заблокированные контакты" - "Выбрать изображение в библиотеке документов" - "Отправка сообщения" - "Сообщение отправлено" - "Передача данных по мобильной сети отключена в настройках устройства" - "В режиме полета нельзя отправлять сообщения" - "Не удалось отправить сообщение" - "Сообщение скачано" - "Передача данных по мобильной сети отключена в настройках устройства" - "Невозможно скачать сообщения в режиме полета" - "Невозможно скачать сообщение" - "Ноль" - "Один" - "Два" - "Три" - "Четыре" - "Пять" - "Шесть" - "Семь" - "Восемь" - "Девять" - "Не удалось отправить сообщение с помощью оператора \"%1$s\" (ошибка %2$d)" - "Не удалось отправить сообщение с помощью неизвестного оператора (ошибка %1$d)" - "Fwd: %s" - "Сообщение не отправлено: услуга не активирована в сети" - "Сообщение не отправлено: недопустимый адрес получателя" - "Ошибка: недопустимое сообщение" - "Сообщение не отправлено, поскольку содержит неподдерживаемый контент" - "Ошибка: неподдерживаемое сообщение" - "Не удалось отправить сообщение из-за его размера" - "Новое сообщение" - "Посмотреть" - "Изображение" - "Подходящее приложение не найдено" - "Удалить получателя." - "Новое сообщение" - "Отмена" - "Настройки точки доступа" - "Не настроено" - "Название" - "Точка доступа" - "MMSC" - "Прокси-сервер MMS" - "Порт MMS" - "MCC" - "MNC" - "Тип точки доступа" - "Удалить точку доступа" - "Создать точку доступа" - "Сохранить" - "Отменить" - "Укажите название." - "Укажите точку доступа." - "Поле MCC должно содержать 3 цифры." - "Поле MNC должно содержать 2 или 3 цифры." - "Восстановление настроек по умолчанию…" - "Восстановить настройки по умолчанию" - "Настройки по умолчанию восстановлены" - "Без названия" - "Точки доступа" - "Точки доступа" - "Создать точку доступа" - "Этот пользователь не может изменять настройки точки доступа" - "Скопировать в буфер обмена?" - "Да" - "На %s" - "Общие" - "Дополнительные настройки" - "Общие настройки" - "Расширенные настройки" - "SIM-карта \"%s\"" - "Отправлять всем получателям отдельные SMS-сообщения. Ответы на них будут отправлены только вам." - "Отправлять всем получателям одно MMS-сообщение" - "Неизвестный номер" - "Новое сообщение" - "Новое сообщение" - "Выбрать SIM-карту" - "Выбрана SIM-карта %1$s" - "Изменить тему" - "Выбрать SIM-карту или изменить тему" - "Нажмите и удерживайте, чтобы записать аудио" - "Начать чат" - "SMS/MMS" - "Чаты из SMS/MMS" - "SMS/MMS" - "Новое сообщение" - "Список чатов" - "Загрузка чатов" - "Загрузка сообщений…" - "Другие чаты" - "Другие сообщения" - "Чат удален" - "Чат удален. Нажмите здесь, чтобы перейти к другому." - "Заблокирован" - "Разблокирован" - "Заканчивается место в памяти устройства. Некоторые данные могут быть потеряны." - "Выбор прикрепленных файлов" - "Подтверждение выбора" - "Выбрано: %d" - "Удалите один или несколько прикрепленных файлов и повторите попытку." - "Если вы не удалите один или несколько прикрепленных файлов, сообщение может не дойти до адресата." - "Вы можете отправлять по одному видео за раз. Удалите лишние ролики и повторите попытку." - "Не удалось загрузить прикрепленные файлы." - "Отправить" - "Не удается начать разговор" - "%1$s (%2$s)" - "SIM-карта выбрана: %s" - diff --git a/res/values-si-rLK/arrays.xml b/res/values-si-rLK/arrays.xml deleted file mode 100644 index 89f6c6c..0000000 --- a/res/values-si-rLK/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "කාරණාවක් නොමැත" - "කාරණාවක් නොමැත" - - - "ඔව්" - "නැත" - "හරි" - "හා හා" - "ස්තූතියි" - "මම එකඟයි" - "නයිස්" - "මම එන ගමන්" - "හරි, මම ඔබ වෙත පසුව පැමිණෙන්නම්" - ":)" - ":(" - - diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml deleted file mode 100644 index 11a5943..0000000 --- a/res/values-si-rLK/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "පණිවිඩ යැවීම" - "පණිවිඩ යැවීම" - "සංවාදය තෝරන්න" - "සැකසීම්" - "පණිවිඩය යවන්න" - "ඇමිණුමක් එක් කරන්න" - "උදවු" - "සාදරයෙන් පිළිගනිමු" - "මඟ හරින්න" - "ඊළඟ >" - "මීළඟ" - "පිටවන්න" - "සැකසීම් >" - "සැකසීම්" - "පණිවිඩ යැවීමට SMS, Phone සහ සම්බන්ධතා වෙත අවසර අවශ්‍යයි." - "ඔබට සැකසීම් > යෙදුම් > පණිවිඩ යැවීම > අවසරවල අවසර වෙනස් කළ හැකිය." - "ඔබට සැකසීම්, යෙදුම්, පණිවිඩ යැවීම, අවසරවල අවසර වෙනස් කළ හැකිය." - "නිතරම" - "සියලුම සම්බන්ධතා" - "%s වෙත යවන්න" - "පින්තූර හෝ වීඩියෝ ලබාගන්න" - "මෙම උපාංගයෙන් පින්තූර තෝරන්න" - "ශ්‍රව්‍ය තැටිගත කරන්න" - "ඡායාරූපය තෝරන්න" - "මාධ්‍යය තෝරාගෙන තිබේ." - "මාධ්‍යය තෝරාගෙන නොතිබේ." - "%d ක් තෝරාගන්නා ලදි" - "රූපය %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "රූපය" - "ශ්‍රව්‍ය තැටිගත කරන්න" - "බෙදාගන්න" - "මේ දැන්" - "දැන්" - - මිනිත්තු %d - මිනිත්තු %d - - - පැය %d - පැය %d - - - දින %d - දින %d - - - සති %d - සති %d - - - මාස %d - මාස %d - - - අවුරුදු %d - අවුරුදු %d - - "0 පන්තියේ පණිවිඩය" - "සුරකින්න" - "උපාංගයේ ඉඩ අඩුය. පණිවිඩකරණය ඉඩ හිස් කර ගැනීමට පැරණි පණිවිඩ ස්වයංක්‍රියව මකනු ඇත." - "ආචයනය ඉඩ ප්‍රමාණය අඩු වී ඇත" - "පණිවිඩ යැවීම ඔබේ උපාංගය මත වැඩි ඉඩක් ලබාගත හැකිවන තෙක් පණිවිඩ නොයැවිය හැකිය නැතහොත් ලබා නොගත හැකිය." - "අඩු SMS ආචයනය. ඇතැම් විට ඔබට පණිවිඩ මැකීමට සිදුවේ." - "ඔබගේ දුරකථන අංකය තහවුරු කරන්න" - "පණිවිඩ යැවීම ඔබේ සමූහ පණිවිඩ නිසි ලෙස බෙදා හරිනු ඇති බවට මෙම එක්-වරක් පියවර තහවුරු කරනු අැත." - "දුරකථන අංකය" - "මාධ්‍ය සමඟ ඇති සියලුම පණිවිඩ මකන්න" - "%s වඩා පැරණි පණිවිඩ මකන්න" - "%s වඩා පැරණි පණිවිඩ ස්වයංක්‍රීයව මකන්න" - "නොසලකා හරින්න" - "මාධ්‍ය සමඟ ඇති සියලුම පණිවිඩ මකන්නද?" - "%s වඩා පැරණි පණිවිඩ මකන්නද?" - "%s වඩා පැරණි පණිවිඩ ස්වයංක්‍රීයව මැකීම සක්‍රිය කරන්නද?" - "%s පැවසුවා" - "ඔබ පැවසුවා" - "%s වෙතින් පණිවිඩය:" - "ඔබ පණිවිඩයක් යැවුවා" - "යවමින්..." - "යැව්වේ නැත. නැවත උත්සාහ කිරීමට ස්පර්ශ කරන්න." - "යැව්වේ නැත. නැවත උත්සාහ කරමින්…" - "නැවත යවන්න නැතහොත් මකන්න" - "කරුණාකර හදිසි සේවාවන් සඳහා හඬ ඇමතුමක් ගන්න. මේ මොහොතේ ඔබගේ පෙළ පණිවිඩය යැවීමට නොහැක." - "අසාර්ථක වුණි" - "බාගැනීමට අලුත් MMS පණිවුඩයකි" - "අලුත් MMS පණිවුඩය" - "බාගැනීමට නොහැකි විය" - "නැවත උත්සාහයට ස්පර්ශ කරන්න" - "බාගැනීමට ස්පර්ශ කරන්න" - "බාගතකරන්න නැතහොත් මකන්න" - "බාගනිමින්…" - "පණිවිඩය කල් ඉකුත්වී ඇත හෝ දැනට නොමැත." - "ප්‍රමාණය: %1$s, කල් ඉකුත් වීම: %2$s" - "යැවිය නොහැක. ලබන්නා වලංගු නැත." - "ජාලය තුළ සේවාව සක්‍රිය කර නොමැත" - "ජාලයේ ගැටළුවක් හේතුවෙන් යැවීම කළ නොහැක" - "පණිවිඩය කල් ඉකුත්වී ඇත හෝ දැනට නොමැත." - "(කාරණාවක් නොමැත)" - "නොහඳුනන යවන්නෙක්" - "භාර දුණි" - "%1$s පණිවිඩය %2$s වෙතින් බාගත නොහැකි විය." - "අඩු මතකය නිසා දත්ත සමුදාය මත මෙහෙයුම සම්පූර්ණ කළ නොහැකි විය" - "පණිවිඩය යැවුවේ නැත" - "සමහර පණිවිඩ පණිවිඩ යැවීමේදී නොයවන ලදී" - - සංවාද %d ක් තුළ පණිවිඩ %d - සංවාද %d ක් තුළ පණිවිඩ %d - - "පණිවිඩය බානොගන්නා ලදී" - "සමහර පණිවිඩ පණිවිඩ යැවීමේදී බා නොගන්නා ලදී" - - සංවාද %d ක් තුළ පණිවිඩ %d - සංවාද %d ක් තුළ පණිවිඩ %d - - "%1$s වෙත පණිවිඩය ගියේ නැත" - "කරුණාකර හදිසි සේවාවන් සඳහා හඬ ඇමතුමක් ගන්න. මේ මොහොතේ %1$s වෙත ඔබගේ පෙළ පණිවිඩය යැවීමට නොහැක." - - නව පණිවිඩ %d - නව පණිවිඩ %d - - "ආරම්භ කරන්න" - "කැමරාව නොපවතී" - "කැමරාව නොපවතී" - "වීඩියෝව ගැනීම නොතිබේ" - "මාධ්‍ය සුරැකීම කළ නොහැක" - "පින්තූරය ලබාගත නොහැක" - "ආපසු" - "සංරක්ෂිත" - "මකන්න" - "සංරක්ෂිත කරන්න" - "සංරක්ෂණය නොකළ" - "දැනුම්දීම් අක්‍රිය කරන්න" - "දැනුම්දීම් සක්‍රිය කරන්න" - "සම්බන්ධතාවය එක් කරන්න" - "බාගන්න" - "යවන්න" - "මකන්න" - "මෙම පණිවිඩය මකන්න?" - "මෙම ක්‍රියාව අවසන් කිරීම කළ නොහැක." - "මකන්න" - - මෙම සංවාද මකන්නද? - මෙම සංවාද මකන්නද? - - "මකන්න" - "අවලංගු කරන්න" - "වෙත" - "බහු පින්තූර තෝරන්න" - "තේරීම සහතික කරන්න" - "+%d" - "ශ්‍රව්‍ය පටිගත කළ නොහැක. නැවත උත්සාහ කරන්න." - "ශ්‍රව්‍ය ධාවනය කළ නොහැක. නැවත උත්සාහ කරන්න." - "ශ්‍රව්‍ය සුරැකීම කළ නොහැක. නැවත උත්සාහ කරන්න." - "ස්පර්ශ කරන්න සහ අල්ලා සිටින්න" - ", " - " " - ": " - " " - "පින්තූරය" - "ශ්‍රව්‍ය පසුර" - "වීඩියෝව" - "සම්බන්ධතා කාඩ් පත" - "බාගන්න" - "SMS ඔස්සේ ප්‍රතිචාර දක්වන්න" - "MMS හරහා පිළිතුරු දෙන්න" - "පිළිතුරු දෙන්න" - - සහභාගිවන්නන් %d - සහභාගිවන්නන් %d - - "මම" - "සම්බන්ධතා අවහිර කරන ලද සහ සංරක්ෂණය කරන ලද" - "සම්බන්ධතාව අවහිර කිරීම ඉවත් කර සංරක්ෂණය නොකරන ලදී" - "%d ක් සංරක්ෂණය කරන ලදි" - "%d ක් සංරක්ෂණය නොකරන ලදි" - "දැනුම්දීම් ක්‍රියාවිරහිත කරන ලදී" - "දැනුම්දීම් ක්‍රියාත්මක කරන ලදී" - "සියල්ල සකසා ඇත. නැවත යැවීමට ස්පර්ශ කරන්න." - "පණිවිඩ යැවීම සාර්ථකව පෙරනිමි SMS යෙදුම ලෙස සකසන ලදී." - - ඇමුණුම් ඉවත ලන්න - ඇමුණුම් ඉවත ලන්න - - "ශ්‍රව්‍ය ඇමුණුම" - "ශ්‍රව්‍ය ඇමුණුම ධාවනය කරන්න" - "විරාමය" - "%s වෙතින් පණිවිඩය: %s." - "%s වෙතින් අසාර්ථක පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙතින් පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙත නොයැවූ පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙත පණිවිඩයක් යවමින්: %s. වේලාව: %s." - "%s වෙත අසාර්ථක පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙත පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙතින් අසාර්ථක පණිවිඩයක්: %s. වේලාව: %s. %s." - "%s වෙතින් පණිවිඩයක්: %s. වේලාව: %s. %s." - "%s වෙත නොයැවූ පණිවිඩය: %s. වේලාව: %s." - "%s වෙත පණිවිඩයක් යවමින්: %s. වේලාව: %s." - "%s වෙත අසාර්ථක පණිවිඩයක්: %s. වේලාව: %s." - "%s වෙත පණිවිඩයක්: %s. වේලාව: %s." - "පණිවිඩය අසාර්ථක විය. නැවත උත්සාහ කිරීමට ස්පර්ශ කරන්න." - "%s සමඟ සංවාදය" - "කාරණය මකන්න" - "වීඩියෝව ග්‍රහණය කරගන්න" - "නිසල පින්තූරයක් ලබාගන්න" - "පින්තූරයක් ගන්න" - "වීඩියෝව පටිගත කිරීම ආරම්භ කරන්න" - "පූර්ණ තිර කැමරාව වෙත මාරු වෙන්න" - "ඉදිරිපස සහ පසුපස කාමර අතර මාරු නොවන්න" - "පටිගත කිරීම නවත්වා වීඩියෝව අමුණන්න" - "වීඩියෝ පටිගත කිරීම නැවත් වන්න" - "ඡායාරූප පණිවිඩ යැවීම" - - ඡායාරූප %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - ඡායාරූප %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - - - වීඩියෝ %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - වීඩියෝ %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - - - ඇමුණුම් %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - ඇමුණුම් %d ක් \"%s\" ඇල්බමය වෙත සුරකින ලදී - - - ඇමුණුම් %d ක් \"බාගැනීම්\" වෙත සුරකින ලදී - ඇමුණුම් %d ක් \"බාගැනීම්\" වෙත සුරකින ලදී - - - ඇමුණුම් %d ක් සුරකින ලදි - ඇමුණුම් %d ක් සුරකින ලදි - - - ඇමුණුම් %d ක් සුරැකීමට නොහැකි විය - ඇමුණුම් %d ක් සුරැකීමට නොහැකි විය - - "MMS ඇමුණුම සුරකින ලදි" - "සැකසීම්" - "සංරක්ෂිත" - "වසන්න" - "MMS" - "උසස්" - "නිදොස්කරණය" - "දැනුම්දීම්" - "ශබ්දය" - "නිහඬ" - "කම්පනය වන්න" - "අවහිර කරන ලදි" - "SMS යැවීමේ වාර්තාව" - "ඔබ යවන ලද සෑම පණිවිඩයකටම යැවීමේ වාර්තාවක් ඉල්ලන්න" - "ස්වයං-ලබාගැනීම" - "MMS පණිවිඩ ස්වයංක්‍රීයව ලබාගන්න" - "රෝමින් ස්වයං-ලබාගැනීම" - "රෝමිං විට MMS ස්වයංක්‍රියව යළි ලබා ගන්න" - "කණ්ඩායම් පණිවිඩ යැවීම්" - "ලබන්නන් කිහිපදෙනෙක් සිටින විට එක් පණිවිඩයක් යැවීමට MMS භාවිතා කරන්න" - "සුපුරුදු SMS යෙදුම" - "සුපුරුදු SMS යෙදුම" - - - "ඔබගේ දුරකථන අංකය" - "නොදනී" - "යන පණිවිඩ හඬ" - "SMS ගොඩ ගසන්න" - "ලැබුණු SMS අමු දත්ත බාහිර ආචයන ගොනුවකට ගොඩ ගසන්න" - "MMS ගොඩ ගසන්න" - "ලැබුණු MMS අමු දත්ත බාහිර ආචයන ගොනුවකට ගොඩ ගසන්න" - "නොරැහැන් ඇඟවීම්" - "පණිවිඩ විකල්ප" - "පෙළ පිටපත් කරන්න" - "විස්තර බලන්න" - "මකන්න" - "ඉදිරියට යවන්න" - "පණිවිඩ විස්තර" - "වර්ගය: " - "පෙළ පණිවුඩය" - "බහු මාධ්‍ය පණිවිඩය" - "වෙතින්: " - "වෙත: " - "යවන ලද: " - "ලැබිණි: " - "කාරණය: " - "ප්‍රමාණය: " - "ප්‍රමුඛතාව: " - "SIM: " - "ඉහළ" - "සාමාන්‍ය" - "පහළ" - "SIM %s" - "සැඟවුණු යවන්නාගේ ලිපිනය" - "ඇමුණුම් පුරණය කිරීමේදී පණිවිඩය යැවිය නොහැක." - "ඇමුණුම පුර්ණය කළ නොහැක. නැවත උත්සාහ කරන්න." - "ජාලය සූදානම් නැත. නැවත උත්සාහ කරන්න." - "පෙළ මකන්න" - "පෙළ සහ අංක ඇතුළු කිරීම අතර මාරුවන්න" - "සහභාගිවන්නන් තව එකතු කරන්න" - "සහභාගිවන්නන් සහතික කරන්න" - "අළුත් සංවාදයක් ආරම්භ කරන්න" - "මෙම අයිතමය තෝරන්න" - "වීඩියෝව ධාවනය කරන්න" - "පුද්ගලයින් සහ විකල්ප" - "නිදොස්කරණය" - "පුද්ගලයින් සහ විකල්ප" - "පොදු" - "මෙම සංවාදයේ සිටින පුද්ගලයින්" - "ඇමතුමක් ලබාගන්න" - "පණිවිඩය යවන්න" - "<small>%s සිට</small><br/> පණිවිඩ යවන්න" - - ඡායාරූප යවන්න - ඡායාරූප යවන්න - - - ශ්‍රව්‍යය යවන්න - ශ්‍රව්‍යය යවන්න - - - වීඩියෝ යවන්න - වීඩියෝ යවන්න - - - සම්බන්ධතා කාඩ්පත් යවන්න - සම්බන්ධතා කාඩ්පත් යවන්න - - - ඇමුණුම් යවන්න - ඇමුණුම් යවන්න - - - ඇමුණුම් %d ක් යැවීමට සුදානම් - ඇමුණුම් %d ක් යැවීමට සුදානම් - - "ප්‍රතිපෝෂණය යවන්න" - "Google Play වෙළඳසැල තුළ බලන්න" - "අනුවාදයේ තොරතුරු" - "අනුවාදය %1$s" - "විවෘත මූලාශ්‍ර බලපත්‍ර" - "දැනුම්දීම්" - "ඇමුණුම් සීමාව ළඟා විය" - "ඇමුණුම ප්‍රවේශනය කිරීම අසාර්ථක විය." - "සම්බන්ධතාවෙත එකතු කරනද?" - "සම්බන්ධතාවය එකතු කරන්න" - "මාතෘකාව" - "මාතෘකාව " - "%s%s" - "සම්බන්ධතා කාඩ් පත් පුර්ණ කරමින්" - "සම්බන්ධතා කාඩ්පත පුර්ණය කළ නොහැක" - "සම්බන්ධ්තා කාඩ්පත පෙන්වන්න" - - සම්බන්ධතා %d - සම්බන්ධතා %d - - "සම්බන්ධතා කාඩ්පත්" - "උපන්දිනය" - "සටහන්" - "ඉදිරියට යන පණිවිඩය" - "පිළිතුරු දෙන්න" - "+1" - "+%d" - "SMS අබල කරන ලදි" - "යැවීමට, පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න" - "පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න" - "වෙනස් කරන්න" - "පණිවිඩ ලබා ගැනීම සඳහා, පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න" - "SMS පණිවිඩ යැවීමට සඳහා මනාප SIM එකක් තෝරාගෙන නොමැත" - "උපාංගයේ හිමිකරු මෙම යෙදුමට ඉඩ නොදෙන ලදි." - "හරි" - "සංවාදයේ සහභාගීවන්නන් ගොඩක් සිටි" - - වලංගු නොවන සම්බන්ධතා - වලංගු නොවන සම්බන්ධතා - - "කැමරා පින්තූරය පුර්ණය කළ නොහැක" - "ඔබ: " - "%s: " - "කටු සටහන" - "අලුත් සංවාදක් ඔබ ආරම්භ කළ පසු, එය මෙතැන ලැයිස්තු කර ඇති බව ඔබ දකීවි" - "සංරක්ෂණය කළ සංවාද මෙහි දිස් වේ" - "සංවාද පුර්ණය වෙමින්..." - "පින්තූරය" - "ශ්‍රව්‍ය පසුර" - "වීඩියෝව" - "සම්බන්ධතා කාඩ් පත" - "MMS" - "අස් කරන්න" - "නැවත උත්සාහ කරන්න" - "අලුත් පණිවිඩයක් ආරම්භ කිරීමට සම්බන්ධතාවේ නම හෝ දුරකථන අංකය ඇතුළු කරන්න" - "අවහිර කරන්න" - "%s අවහිර කරන ලද" - "%s අවහිර නොකරන්න" - "%s අවහිර කරලද?" - "මෙම අංකයෙන් දිගටම පණිවිඩ ලැබීමට ඔබ ඉදිරියට යන ලදි නමුත් තවදුරත් දැනුම් දෙන්න එපා. මෙම සංවාදය සංරක්ෂණය කරවී." - "සම්බන්ධතා අවහිර කරන ලදි" - "අවහිර නොකරන්න" - "සම්බන්ධතා අවහිර කරන ලදි" - "ලේඛන පුස්ථකාලයෙන් පින්තූර තෝරන්න" - "පණිවිඩය යවමින්" - "පණිවිඩය යවන ලදී" - "සෙල්‍යුලර් දත්ත අක්‍රිය කර තිබේ. ඔබගේ සැකසීම් පරික්ෂා කරන්න." - "අහස්යානා ආකාරය තුළ පණිවිඩ යැවිය නොහැක" - "පණිවිඩය යැවිය නොහැකි විය" - "පණිවිඩය බාගන්නා ලදි" - "සෙල්‍යුලර් දත්ත අක්‍රිය කර තිබේ. ඔබගේ සැකසීම් පරික්ෂා කරන්න." - "ගුවන්යානා ප්‍රකාරයේදී පණිවිඩ බාගැනීමට නොහැකිය" - "පණිවිඩය බාගැනීමට නොහැකි විය" - "බිංදුව" - "එක" - "දෙක" - "තුන" - "හතර" - "පහ" - "හය" - "හත" - "අට" - "නවය" - "%1$s සමඟ පණිවිඩය යැවිය නොහැක, දෝෂය %2$d" - "නොදන්නා වාහකයක් සමඟ පණිවිඩය යැවිය නොහැක, දෝෂය %1$d" - "ඉදිරි: %s" - "පණිවිඩ නොයවන ලදි: ජාලය තුළ සක්‍රිය නොකළ සේවාවකි" - "පණිවිඩ නොයවන ලදි: ගමනාන්ත ලිපිනය වලංගු නැත" - "පණිවිඩ නොයවන ලදි: පණිවිඩය වලංගු නැත" - "පණිවිඩ නොයවන ලදි: අන්තර්ගතය සහාය නොදක්වයි" - "පණිවිඩ නොයවන ලදි: පණිවිඩ සහාය නොදක්වයි" - "පණිවිඩය නොයවන ලදි: ඉතාම විශාලයි" - "අලුත් පණිවිඩය" - "බලන්න" - "පින්තුරය" - "අදාළ යෙදුම සොයාගත නොහැක" - "ලබන්නා ඉවත් කරන්න" - "අලුත් පණිවිඩය" - "අවලංගු කරන්න" - "ප්‍රවේශ ස්ථානය සංස්කරණය" - "සකසා නැත" - "නම‍" - "APN" - "MMSC" - "MMS ප්‍රොක්සිය" - "MMS තොට" - "MCC" - "MNC" - "APN වර්ගය" - "APN මකන්න" - "නව APN" - "සුරකින්න" - "ඉවතලන්න" - "නම ක්ෂේත්‍රය හිස් විය නොහැක." - "APN හිස් විය නොහැක." - "MCC ක්ෂේත්‍රය සංඛ්‍යා 3 ක් විය යුතුය." - "MNC ක්ෂේත්‍රය ඉලක්කම් 2 ක් හෝ 3 ක් විය යුතුය." - "සුපුරුදු APN සැකසුම් යළි පිහිටුවමින්." - "සුපුරුදු වෙත යළි සකසන්න" - "සුපුරුදු APN සැකසීම නැවත සැකසීම සම්පූර්ණ කරන ලදි." - "නම් යොදා නැත" - "ප්‍රවේශ ලක්ෂ්‍ය නම්" - "APN" - "නව APN" - "මෙම පරිශීලකයා සඳහා ප්‍රවේශ ලක්ෂ්‍යයේ නමේ සැකසීම් නොතිබේ" - "පසුරු පුවරුව වෙත පිටපත් කරන්නද?" - "පිටපත්කරණය" - "%s වෙත" - "පොදු" - "උසස්" - "පොදු සැකසීම්" - "උසස් සැකසුම්" - "\"%s\" SIM" - "සියළුම ලබන්නන් වෙත තනි SMS පණිවිඩ යවන්න. ඕනෑම පිළිතුරක් පමණක් ඔබට ලැබේ" - "සියළුම ලබන්නන් වෙත තනි MMS යවන්න" - "නොදන්නා අංකය" - "අලුත් පණිවිඩය" - "අලුත් පණිවිඩය." - "SIM තෝරාගැනීම" - "%1$s තෝරාගත්තේය, SIM තෝරාගැනීම" - "මාතෘකාව සකසන්න" - "SIM තෝරන්න හෝ මාතෘකාව සකසන්න" - "ශ්‍රව්‍ය පටිගත කිරීමට තට්ටු කර අල්ලා සිටින්න" - "අළුත් සංවාදයක් ආරම්භ කරන්න" - "පණිවිඩ යැවීම" - "පණිවිඩ යැවීමේ ලැයිස්තුව" - "පණිවිඩ යැවීම" - "අලුත් පණිවිඩය" - "සංවාද ලැයිස්තුව" - "සංවාද පුරණය වෙමින්" - "පණිවුඩ පූරණය කරමින්" - "තවත් සංවාද පෙන්වන්න" - "View more messages" - "සංවාදය මකාදැමුණි" - "සංවාදය මකන ලදී. වෙනස් පණිවිඩ යැවීමේ සංවාදයක් පෙන්වීමට ස්පර්ශ කරන්න" - "අවහිර කරන ලදි" - "අවහුර නොකළ" - "ගබඩා ඉඩ අවමයි. ඇතැම් දත්ත අහිමි වනු ඇත." - "ඇමුණුම් තෝරන්න" - "තේරීම සහතික කරන්න" - "%d ක් තෝරන ලදි" - "කරුණාකර ඇමුණුම් එකක් හෝ වැඩි සංඛ්‍යාවක් ඉවත් කර නැවත උත්සාහ කරන්න." - "ඔබට ඔබේ පණිවිඩය යැවීමට උත්සාහ කළ හැකි වුවත්, ඔබ ඇමුණුම් එකක් හෝ වැඩි සංඛ්‍යාවක් ඉවත් කරන්නේ නම් හැර එය බෙදාගැනීම නොහැකි විය හැකිය." - "ඔබට පණිවිඩයකට එක් වීඩියෝවක් පමණක් යැවිය හැකිය. කරුණාකර අමතර වීඩියෝ ඉවත් කර නැවත උත්සාහ කරන්න." - "පණිවිඩ යැවීම ඇමුණුම පූරණය කිරීමට අසමත් විය." - "කෙසේ වුවත් යවන්න" - "සංවාදය ඇරඹීමට නොහැකි විය" - "%1$s (%2$s)" - "%s තෝරාගත්තේය" - diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml deleted file mode 100644 index 0e48259..0000000 --- a/res/values-sk/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "bez predmetu" - "žiadny predmet" - - - "Áno" - "Nie" - "OK" - "Haha" - "Ďakujeme" - "Súhlasím" - "Pekné" - "Som na ceste" - "OK, odpíšem neskôr" - ":)" - ":(" - - diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml deleted file mode 100644 index 32ec96b..0000000 --- a/res/values-sk/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "SMS a MMS" - "SMS a MMS" - "Vybrať konverzáciu" - "Nastavenia" - "Odoslať správu" - "Pridať prílohu" - "Pomocník" - "Vitajte" - "Preskočiť" - "Ďalej >" - "Ďalej" - "Ukončiť" - "Nastavenia >" - "Nastavenia" - "Aplikácia SMS a MMS vyžaduje povolenie na prístup k správam SMS, telefónu a kontaktom." - "Povolenia môžete zmeniť v časti Nastavenia > Aplikácie > SMS a MMS > Povolenia." - "Povolenia môžete zmeniť v časti Nastavenia, Aplikácie, SMS a MMS, Povolenia." - "Často kontaktované osoby" - "Všetky kontakty" - "Odoslať na číslo %s" - "Snímať obrázky alebo videá" - "Vybrať obrázky z tohto zariadenia" - "Zaznamenať zvuk" - "Vybrať fotku" - "Je vybrané médium." - "Výber média bol zrušený." - "Vybrané: %d" - "obrázok – %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "obrázok" - "Zaznamenanie zvuku" - "Zdieľať" - "Práve teraz" - "Teraz" - - %d min - %d min - %d min - %d min - - - %d hodiny - %d hodiny - %d hodín - %d hodina - - - %d dni - %d dňa - %d dní - %d deň - - - %d týždne - %d týždňa - %d týždňov - týždeň - - - %d mesiace - %d mesiaca - %d mesiacov - mesiac - - - %d roky - %d roka - %d rokov - rok - - "Správa triedy 0" - "Uložiť" - "V zariadení dochádza voľné miesto. Služba SMS a MMS bude automaticky odstraňovať staršie správy, aby uvoľnila miesto." - "Nedostatok miesta na úložisku" - "Aplikácia SMS a MMS nebude môcť posielať ani prijímať správy, pokiaľ v zariadení nebude k dispozícii viac miesta." - "V úložisku pre správy SMS nie je dostatok voľného miesta. Je možné, že budete musieť správy odstrániť." - "Potvrďte svoje telefónne číslo" - "Tento jednorazový krok zaistí, že aplikácia SMS a MMS bude správne doručovať vaše skupinové správy." - "Telefónne číslo" - "Odstrániť všetky správy s médiami" - "Odstrániť správy staršie ako %s" - "Automaticky odstrániť správy staršie ako %s" - "Ignorovať" - "Odstrániť všetky správy s médiami?" - "Odstrániť správy staršie ako %s?" - "Chcete odstrániť správy staršie ako %s a zapnúť automatické odstraňovanie?" - "%s povedal(a)" - "Povedali ste" - "Správa od používateľa %s:" - "Odoslali ste správu" - "Odosiela sa…" - "Správa nebola odoslaná. Klepnutím to skúste znova." - "Správa nebola odoslaná. Skúšame to znova…" - "Znova odoslať alebo odstrániť" - "Zavolajte na pohotovostné služby. Vašu textovú správu sa v tejto chvíli nepodarilo doručiť." - "Nepodarilo sa" - "Nová správa MMS na stiahnutie" - "Nová správa MMS" - "Nepodarilo sa stiahnuť" - "Klepnutím to skúste znova" - "Stiahnite klepnutím" - "Stiahnuť alebo odstrániť" - "Sťahuje sa..." - "Platnosť správy vypršala alebo správa nie je k dispozícii" - "veľkosť: %1$s, vypršanie platnosti: %2$s" - "Správu nie je možné odoslať. Nie je uvedený platný príjemca." - "Služba nie je v sieti aktivovaná" - "Správu sa nepodarilo odoslať, pretože sa vyskytol problém so sieťou" - "Platnosť správy vypršala alebo správa nie je k dispozícii" - "(Bez predmetu)" - "Neznámy odosielateľ" - "Doručené" - "Nepodarilo sa stiahnuť správu %1$s od odosielateľa %2$s" - "Operáciu v databáze sa nepodarilo dokončiť z dôvodu nedostatku pamäte" - "Správu sa nepodarilo odoslať" - "Niektoré správy neboli odoslané v aplikácii SMS a MMS" - - %d Počet správ v %d konverzáciách - %d Počet správ v %d konverzácie - %d Počet správ v %d konverzáciách - %d Počet správ v jednej konverzácii - - "Správu sa nepodarilo stiahnuť" - "Niektoré správy neboli stiahnuté v aplikácii SMS a MMS" - - %d Počet správ v %d konverzáciách - %d Počet správ v %d konverzácie - %d Počet správ v %d konverzáciách - %d Počet správ v jednej konverzácii - - "Správa na číslo %1$s nebola odoslaná" - "Zavolajte na pohotovostné služby. Vašu textovú správu na číslo %1$s sa v tejto chvíli nepodarilo doručiť." - - %d nové správy - %d novej správy - %d nových správ - Nová správa - - "Spustiť" - "Fotoaparát nie je k dispozícii" - "Fotoaparát nie je k dispozícii" - "Snímanie videa nie je k dispozícii" - "Média nie je možné uložiť" - "Snímku nie je možné odfotiť" - "Späť" - "Archivované" - "Odstrániť" - "Archivovať" - "Vybrať z archívu" - "Vypnúť upozornenia" - "Zapnúť upozornenia" - "Pridať kontakt" - "Stiahnuť" - "Odoslať" - "Odstrániť" - "Chcete odstrániť túto správu?" - "Táto akcia sa nedá vrátiť späť." - "Odstrániť" - - Odstrániť tieto konverzácie? - Odstrániť tieto konverzácie? - Odstrániť tieto konverzácie? - Odstrániť túto konverzáciu? - - "Odstrániť" - "Zrušiť" - "Komu" - "Vybrať viaceré obrázky" - "Potvrdiť výber" - "+ %d" - "Zvuk sa nedá zaznamenať. Skúste to znova." - "Zvuk sa nedá prehrať. Skúste to znova." - "Zvuk sa nepodarilo uložiť. Skúste to znova." - "Klepnutie a podržanie" - ", " - " " - ": " - " " - "Fotografia" - "Zvukový klip" - "Video" - "Vizitka" - "Stiahnuť" - "Odpovedať pomocou správy SMS" - "Odpovedať správou MMS" - "Odpovedať" - - %d účastníci - %d účastníka - %d účastníkov - %d účastník - - "Ja" - "Kontakt bol zablokovaný a archivovaný" - "Kontakt bol odblokovaný a jeho archivácia bola zrušená" - "Archivované: %d" - "Zrušená archivácia: %d" - "Upozornenia sú vypnuté" - "Upozornenia sú zapnuté" - "Všetko je pripravené. Klepnite znova na tlačidlo Odoslať." - "Aplikácia SMS a MMS bola úspešne nastavená ako predvolená aplikácia pre SMS" - - Zahodiť prílohy - Zahodiť prílohy - Zahodiť prílohy - Zahodiť prílohu - - "Zvuková príloha" - "Prehrať zvukovú prílohu" - "Pozastaviť" - "Správa od používateľa %s: %s" - "Správa od používateľa %s bola neúspešná: %s. Čas: %s." - "Správa od používateľa %s: %s. Čas: %s." - "Správa nebola odoslaná používateľovi %s: %s. Čas: %s." - "Odosiela sa správa používateľovi %s: %s. Čas: %s." - "Správa používateľovi %s bola neúspešná: %s. Čas: %s." - "Správa používateľovi %s: %s. Čas: %s." - "Neúspešná správa od používateľa %s: %s. Čas: %s. %s." - "Správa od používateľa %s: %s. Čas: %s. %s." - "Správa nebola odoslaná skupine %s: %s. Čas: %s." - "Odosiela sa správa skupine %s: %s. Čas: %s." - "Správa skupine %s bola neúspešná: %s. Čas: %s." - "Správa skupine %s: %s. Čas: %s." - "Neúspešná správa. Skúste to znova klepnutím." - "Rozhovor s používateľmi %s" - "Odstrániť predmet" - "Natočiť video" - "Urobiť statickú snímku" - "Vyfotiť" - "Spustiť natáčanie videa" - "Prepnúť na fotoaparát na celej obrazovke" - "Prepnúť medzi predným a zadným fotoaparátom" - "Zastaviť zaznamenávanie a pripojiť video" - "Zastaviť zaznamenávanie videa" - "Fotky v aplikácii SMS a MMS" - - %d fotky boli uložené do albumu %s - %d fotky bolo uloženej do albumu %s - %d fotiek bolo uložených do albumu %s - %d fotka bola uložená do albumu %s - - - %d videá boli uložené do albumu %s - %d videa bolo uloženého do albumu %s - %d videí bolo uložených do albumu %s - %d video bolo uložené do albumu %s - - - %d prílohy boli uložené do albumu %s - %d prílohy bolo uloženej do albumu %s - %d príloh bolo uložených do albumu %s - %d príloha bola uložená do albumu %s - - - %d prílohy boli uložené do priečinka so stiahnutými súbormi - %d prílohy bolo uloženej do priečinka so stiahnutými súbormi - %d príloh bolo uložených do priečinka so stiahnutými súbormi - %d príloha bola uložená do priečinka so stiahnutými súbormi - - - Boli uložené %d prílohy - Bolo uloženej %d prílohy - Bolo uložených %d príloh - Bola uložená %d príloha - - - Nepodarilo sa uložiť %d prílohy - Nepodarilo sa uložiť %d prílohy - Nepodarilo sa uložiť %d príloh - Nepodarilo sa uložiť %d prílohu - - "Uložené prílohy správ MMS" - "Nastavenia" - "Archivované" - "Zavrieť" - "MMS" - "Rozšírené" - "Ladiť" - "Upozornenia" - "Zvuk" - "Tichý režim" - "Vibrovanie" - "Blokované" - "Prehľady doručovania SMS" - "Vyžadovať potvrdenie o doručení každej odoslanej SMS" - "Automatické načítanie" - "Automaticky načítať správy MMS" - "Automatické načítanie pri roamingu" - "Automaticky načítať správy MMS počas roamingu" - "Skupinové správy" - "Použiť MMS na odoslanie jednej správy, ak má niekoľko príjemcov" - "Predvolená aplikácia SMS" - "Predvolená aplikácia SMS" - - - "Vaše telefónne číslo" - "Neznáme" - "Zvuky odosielanej správy" - "Zálohovať správy SMS" - "Zálohovať nespracované údaje prijatých správ SMS do súboru v externom úložisku" - "Zálohovať správy MMS" - "Zálohovať nespracované údaje prijatých správ MMS do súboru v externom úložisku" - "Bezdrôtové núdzové upozornenia" - "Možnosti správy" - "Kopírovať text" - "Zobraziť podrobnosti" - "Odstrániť" - "Poslať ďalej" - "Podrobnosti správy" - "Typ: " - "Textová správa" - "Multimediálna správa" - "Od: " - "Komu: " - "Odoslané: " - "Prijaté: " - "Predmet: " - "Veľkosť: " - "Priorita: " - "SIM karta: " - "Vysoká" - "Normálna" - "Nízka" - "Slot SIM %s" - "Skrytá adresa odosielateľa" - "Počas načítavania príloh nie je možné odoslať správu." - "Nie je možné načítať prílohu. Skúste to znova." - "Sieť nie je pripravená. Skúste to znova." - "Odstrániť text" - "Prepnúť medzi zadávaním textu a číslic" - "Pridať ďalších účastníkov" - "Potvrdiť účastníkov" - "Začať novú konverzáciu" - "Vybrať túto položku" - "Prehrať video" - "Ľudia a možnosti" - "Ladiť" - "Ľudia a možnosti" - "Všeobecné" - "Ľudia v tejto konverzácii" - "Zavolať" - "Odoslať správu" - "Odošlite správu<br/><small>zo SIM karty %s</small>" - - Odoslať fotky - Odoslať fotky - Odoslať fotky - Odoslať fotku - - - Odoslať zvukové súbory - Odoslať zvukové súbory - Odoslať zvukové súbory - Odoslať zvukový súbor - - - Odoslať videá - Odoslať videá - Odoslať videá - Odoslať video - - - Odoslať vizitky - Odoslať vizitky - Odoslať vizitky - Odoslať vizitku - - - Odoslať prílohy - Odoslať prílohy - Odoslať prílohy - Odoslať prílohu - - - %d prílohy sú pripravené na odoslanie - %d prílohy je pripravenej na odoslanie - %d príloh je pripravených na odoslanie - Jedna príloha je pripravená na odoslanie - - "Odoslať spätnú väzbu" - "Zobraziť v Obchode Google Play" - "Informácie o verzii" - "Verzia %1$s" - "Licencie open source" - "Upozornenia" - "Dosiahli ste limit pre počet príloh" - "Prílohu sa nepodarilo načítať." - "Pridať medzi kontakty?" - "Pridať kontakt" - "Predmet" - "Predmet: " - "%s%s" - "Načítava sa vizitka" - "Nepodarilo sa načítať vizitku" - "Zobraziť vizitku" - - %d kontakty - %d kontaktu - %d kontaktov - %d kontakt - - "Vizitky" - "Narodeniny" - "Poznámky" - "Poslanie správy ďalej" - "Odpovedanie" - "+1" - "+%d" - "Správy SMS sú zakázané" - "Ak chcete odosielať správy, nastavte SMS a MMS ako predvolenú aplikáciu pre SMS" - "Nastavte SMS a MMS ako predvolenú aplikáciu pre SMS" - "Zmeniť" - "Ak chcete prijímať správy, nastavte SMS a MMS ako predvolenú aplikáciu pre SMS" - "Nie je vybraná preferovaná SIM karta na odosielanie správ SMS." - "Túto aplikáciu vlastník zariadenia nepovolil." - "OK" - "V konverzácii je príliš mnoho účastníkov" - - Neplatné kontakty - Neplatné kontakty - Neplatné kontakty - Neplatný kontakt - - "Obrázok z fotoaparátu sa nepodarilo načítať" - "Vy: " - "%s: " - "Koncept" - "Tu uvidíte svoje konverzácie" - "Tu sa zobrazujú archivované konverzácie" - "Načítavajú sa konverzácie..." - "Obrázok" - "Zvukový klip" - "Video" - "Vizitka" - "MMS" - "Späť" - "Skúsiť znova" - "Zadajte meno kontaktu alebo telefónne číslo a začnite písať novú správu" - "Blokovať" - "Blokovať kontakt %s" - "Odblokovať kontakt %s" - "Blokovať %s?" - "Z tohto čísla budete aj naďalej dostávať správy. Nebudete už však dostávať upozornenia. Táto konverzácia bude archivovaná." - "Blokované kontakty" - "ODBLOKOVAŤ" - "Blokované kontakty" - "Vyberte obrázok z knižnice dokumentov" - "Správa sa odosiela" - "Správa bola odoslaná" - "Prenos mobilných údajov je vypnutý. Skontrolujte svoje nastavenia." - "V režime V lietadle nie je možné odosielať správy" - "Odoslanie správy zlyhalo" - "Správa bola stiahnutá" - "Prenos mobilných dát je vypnutý. Skontrolujte svoje nastavenia." - "V režime v lietadle nie je možné sťahovať správy" - "Správu nie je možné stiahnuť" - "Nula" - "Jedna" - "Dva" - "Tri" - "Štyri" - "Päť" - "Šesť" - "Sedem" - "Osem" - "Deväť" - "Správu prostredníctvom operátora %1$s nie je možné odoslať. Chyba: %2$d" - "Správu prostredníctvom neznámeho operátora nie je možné odoslať. Chyba: %1$d" - "Poslať ďalej: %s" - "Správa nebola odoslaná: služba nie je aktivovaná v sieti" - "Správa nebola odoslaná: neplatná cieľová adresa" - "Správa nebola odoslaná: neplatná správa" - "Správa nebola odoslaná: nepodporovaný obsah" - "Správa nebola odoslaná: nepodporovaná správa" - "Správa nebola odoslaná: veľkosť prekročila limit" - "Nová správa" - "Zobraziť" - "Obrázok" - "Nenašla sa vhodná aplikácia" - "Odstrániť príjemcu" - "Nová správa" - "Zrušiť" - "Úprava prístupového bodu" - "Nenastavené" - "Názov" - "Názov prístupového bodu" - "MMSC" - "Proxy server systému MMS" - "Port systému MMS" - "MCC" - "MNC" - "Typ prístupového bodu" - "Odstrániť názov prístupového bodu (APN)" - "Nový prístupový bod" - "Uložiť" - "Zrušiť" - "Pole Meno nemôže byť prázdne." - "Názov prístupového bodu (APN) nemôže byť prázdny." - "Pole MCC musí obsahovať 3 číslice." - "Pole MNC musí obsahovať 2 alebo 3 číslice." - "Obnovujú sa predvolené nastavenia názvu prístupového bodu (APN)." - "Obnoviť predvolené" - "Predvolené nastavenia prístupových bodov boli obnovené" - "Bez názvu" - "Názvy prístupových bodov" - "Prístupové body" - "Nový prístupový bod" - "Nastavenia názvu prístupového bodu (APN) nie sú dostupné pre tohto používateľa" - "Kopírovať do schránky?" - "Kopírovať" - "na SIM kartu %s" - "Všeobecné" - "Rozšírené" - "Všeobecné nastavenia" - "Rozšírené nastavenia" - "SIM karta %s" - "Odoslať osobitné správy SMS všetkým príjemcom. Všetky odpovede dostanete iba vy." - "Odoslať jednu správu MMS všetkým príjemcom" - "Neznáme číslo" - "Nová správa" - "Nová správa" - "Výber SIM karty" - "Výber SIM karty (vybraná SIM karta %1$s)" - "Upraviť predmet" - "Vybrať SIM kartu alebo upraviť predmet" - "Zvuk zaznamenáte klepnutím a podržaním" - "Začať novú konverzáciu" - "SMS a MMS" - "Zoznam v aplikácii SMS a MMS" - "SMS a MMS" - "Nová správa" - "Zoznam konverzácií" - "Načítavajú sa konverzácie..." - "Načítavajú sa správy" - "Zobraziť ďalšie konverzácie" - "Zobraziť viac správ" - "Konverzácia bola odstránená" - "Konverzácia bola odstránená. Klepnutím si zobrazte inú konverzáciu aplikácie SMS a MMS." - "Zablokované" - "Odblokované" - "V úložisku je málo miesta. Niektoré údaje môžu byť stratené." - "Vybrať prílohy" - "Potvrďte výber" - "Vybrané: %d" - "Odstráňte jednu alebo viac príloh a skúste to znova." - "Svoju správu sa môžete pokúsiť odoslať, ale pravdepodobne bude doručená až vtedy, keď odstránite jednu alebo viac príloh." - "Môžete odoslať iba jedno video na správu. Odstráňte ďalšie videá a skúste to znova." - "Aplikácii SMS a MMS sa nepodarilo načítať prílohu." - "Aj tak odoslať" - "Rozhovor nie je možné spustiť" - "%1$s (%2$s)" - "Vybrané: %s" - diff --git a/res/values-sl/arrays.xml b/res/values-sl/arrays.xml deleted file mode 100644 index 0cd69b7..0000000 --- a/res/values-sl/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "brez zadeve" - "nizadeve" - - - "Da" - "Ne" - "V redu" - "Hehe" - "Hvala" - "Strinjam se" - "Lepo" - "Prihajam" - "V redu, oglasim se pozneje" - ":)" - ":(" - - diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml deleted file mode 100644 index 2352632..0000000 --- a/res/values-sl/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "Sporočila" - "Sporočila" - "Izbira pogovora" - "Nastavitve" - "Pošlji sporočilo" - "Dodajte prilogo" - "Pomoč" - "Pozdravljeni" - "Preskoči" - "Naprej >" - "Naprej" - "Izhod" - "Nastavitve >" - "Nastavitve" - "Sporočila zahtevajo dovoljenje za dostop do sporočil SMS, telefona in stikov." - "Dovoljenja lahko spreminjate v »Nastavitve > Aplikacije > Sporočila > Dovoljenja«." - "Dovoljenja lahko spreminjate v »Nastavitve, Aplikacije, Sporočila, Dovoljenja«." - "Pogosti" - "Vsi stiki" - "Pošlji na številko %s" - "Snemanje slik ali videoposnetkov" - "Izbira slik iz te naprave" - "Snemanje zvoka" - "Izbira fotografije" - "Predstavnost je izbrana." - "Izbor predstavnosti je preklican." - "Št. izbranih: %d" - "slika %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "slika" - "Snemanje zvoka" - "Skupna raba" - "Pravkar" - "Zdaj" - - %d min - %d min - %d min - %d min - - - %d ura - %d uri - %d ure - %d ur - - - %d dan - %d dneva - %d dni - %d dni - - - %d teden - %d tedna - %d tedne - %d tednov - - - %d mesec - %d meseca - %d mesece - %d mesecev - - - %d leto - %d leti - %d leta - %d let - - "Sporočilo razreda 0" - "Shrani" - "V napravi primanjkuje prostora za shranjevanje. Sporočila bodo zaradi sprostitve prostora samodejno izbrisala starejša sporočila." - "Zmanjkalo bo prostora za shranjevanje" - "Sporočila morda ne bodo pošiljala ali sprejemala sporočil, dokler v napravi ne bo na voljo več prostora." - "Prostora za SMS-je je malo. Morda boste morali izbrisati sporočila." - "Potrditev telefonske številke" - "S tem enkratnim korakom boste zagotovili, da bodo Sporočila pravilno dostavljala skupinska sporočila." - "Telefonska številka" - "Izbriši vsa sporočila s predstavnostjo" - "Izbriši sporočila, starejša od %s" - "Samodejno brisanje sporočil, starejših od %s" - "Prezri" - "Želite izbrisati vsa sporočila s predstavnostjo?" - "Želite izbrisati sporočila, starejša od %s?" - "Želite izbrisati sporočila, starejša od %s, in vklopiti samodejno brisanje?" - "Oseba %s sporoča" - "Sporočili ste" - "Sporočilo osebe %s" - "Poslali ste sporočilo" - "Pošiljanje …" - "Ni bilo poslano. Dotaknite se za vnovični poskus." - "Ni bilo poslano. Vnovični poskus …" - "Znova pošlji ali izbriši" - "Pokličite nujno pomoč. SMS-ja ni bilo mogoče poslati." - "Ni uspelo" - "Novo sporočilo MMS za prenos" - "Novo sporočilo MMS" - "Prenos ni uspel" - "Dotaknite se za vnovični poskus" - "Dotaknite se za prenos" - "Prenesi ali izbriši" - "Prenašanje …" - "Sporočilo je poteklo ali ni na voljo" - "velikost: %1$s, poteče: %2$s" - "Pošiljanje ni mogoče. Prejemnik ni veljaven." - "Storitev ni aktivirana v omrežju" - "Pošiljanje zaradi težave v omrežju ni uspelo" - "Sporočilo je poteklo ali ni na voljo" - "(Ni zadeve)" - "Neznan pošiljatelj" - "Dostavljeno" - "Sporočila %1$s osebe %2$s ni bilo mogoče prenesti." - "Postopka v zbirki podatkov ni bilo mogoče dokončati, ker primanjkuje pomnilnika" - "Sporočilo ni bilo poslano" - "Nekatera sporočila niso bila poslana v Sporočilih" - - %d sporočilo v %d pogovoru - %d sporočili v %d pogovorih - %d sporočila v %d pogovorih - %d sporočil v %d pogovorih - - "Sporočilo ni bilo preneseno" - "Nekatera sporočila niso bila prenesena v Sporočilih" - - %d sporočilo v %d pogovoru - %d sporočili v %d pogovorih - %d sporočila v %d pogovorih - %d sporočil v %d pogovorih - - "Sporočilo na številko %1$s ni bilo poslano" - "Pokličite nujno pomoč. SMS-ja na %1$s ni bilo mogoče poslati." - - %d novo sporočilo - %d novi sporočili - %d nova sporočila - %d novih sporočil - - "Začni" - "Fotoaparat ni na voljo" - "Fotoaparat ni na voljo" - "Snemanje videoposnetkov ni na voljo" - "Predstavnosti ni mogoče shraniti" - "Ni mogoče posneti fotografije" - "Nazaj" - "Arhivirano" - "Izbriši" - "Arhiviraj" - "Odstrani iz arhiva" - "Izklop obvestil" - "Vklop obvestil" - "Dodaj stik" - "Prenos" - "Pošlji" - "Izbriši" - "Želite izbrisati sporočilo?" - "Tega dejanja ni mogoče razveljaviti." - "Izbriši" - - Želite izbrisati te pogovore? - Želite izbrisati te pogovore? - Želite izbrisati te pogovore? - Želite izbrisati te pogovore? - - "Izbriši" - "Prekliči" - "Za" - "Izbira več slik" - "Potrditev izbora" - "+%d" - "Zvoka ni mogoče posneti. Poskusite znova." - "Zvoka ni mogoče predvajati. Poskusite znova." - "Zvoka ni bilo mogoče shraniti. Poskusite znova." - "Dotaknite se in pridržite" - ", " - " " - ": " - " " - "Slika" - "Zvočni posnetek" - "Videoposnetek" - "Osebna vizitka" - "Prenos" - "Odgovor prek sporočila SMS" - "Odg. z MMS-jem" - "Odgovori" - - %d udeleženec - %d udeleženca - %d udeleženci - %d udeležencev - - "Jaz" - "Blokirani in arhivirani stiki" - "Stik odblokiran in odstranjen iz arhiva" - "Št. arhiviranih: %d" - "Št. povrnjenih iz arhiva: %d" - "Obvestila izklopljena" - "Obvestila vklopljena" - "Vse je nastavljeno. Znova se dotaknite »Pošlji«." - "Sporočila so uspešno nastavljena kot privzeta aplikacija za sporočila SMS." - - Zavrzi priloge - Zavrzi priloge - Zavrzi priloge - Zavrzi priloge - - "Zvočna priloga" - "Predvajanje zvočne priloge" - "Začasna zaustavitev" - "Sporočilo pošiljatelja %s: %s." - "Neuspelo sporočilo osebe %s: %s. Čas: %s." - "Sporočilo osebe %s: %s. Čas: %s." - "Neposlano sporočilo osebi %s: %s. Čas: %s." - "Pošiljanje sporočila osebi %s: %s. Čas: %s." - "Neuspelo sporočilo osebi %s: %s. Čas: %s." - "Sporočilo osebi %s: %s. Čas: %s." - "Neuspelo sporočilo osebe %s: %s. Čas: %s. %s." - "Sporočilo osebe %s: %s. Čas: %s. %s." - "Neposlano sporočilo skupini %s: %s. Čas: %s." - "Pošiljanje sporočila skupini %s: %s. Čas: %s." - "Neuspelo sporočilo skupini %s: %s. Čas: %s." - "Sporočilo skupini %s: %s. Čas: %s." - "Neuspelo sporočilo. Dotaknite se za vnovičen poskus." - "Pogovor z udeleženci %s" - "Izbris zadeve" - "Posnemi video" - "Posnemi fotografijo" - "Fotografiraj" - "Začni snemanje videa" - "Preklopi v celozaslonski način fotoaparata" - "Preklop med sprednjim in zadnjim fotoaparatom" - "Ustavi snemanje in pripni videoposnetek" - "Ustavi snemanje videoposnetka" - "Fotografije v Sporočilih" - - %d fotografija shranjena v album »%s« - %d fotografiji shranjeni v album »%s« - %d fotografije shranjene v album »%s« - %d fotografij shranjenih v album »%s« - - - %d videoposnetek shranjen v album »%s« - %d videoposnetka shranjena v album »%s« - %d videoposnetki shranjeni v album »%s« - %d videoposnetkov shranjenih v album »%s« - - - %d priloga shranjena v album »%s« - %d prilogi shranjeni v album »%s« - %d priloge shranjene v album »%s« - %d prilog shranjenih v album »%s« - - - %d priloga shranjena v »Prenosi« - %d prilogi shranjeni v »Prenosi« - %d priloge shranjene v »Prenosi« - %d prilog shranjenih v »Prenosi« - - - %d priloga je bila shranjena - %d prilogi sta bili shranjeni - %d priloge so bile shranjene - %d prilog je bilo shranjenih - - - %d priloge ni bilo mogoče shraniti - %d prilog ni bilo mogoče shraniti - %d prilog ni bilo mogoče shraniti - %d prilog ni bilo mogoče shraniti - - "Shranjena priloga sporočila MMS" - "Nastavitve" - "Arhivirano" - "Zapri" - "MMS" - "Dodatno" - "Odpravljanje napak" - "Obvestila" - "Zvok" - "Tiho" - "Vibriranje" - "Blokirano" - "Poročila o dostavi sporočil SMS" - "Zahtevanje poročila o dostavi za vsak poslan SMS" - "Samodejni prenos" - "Samodejni prenos MMS-jev" - "Samodejni prenos med gostovanjem" - "Samodejni prenos sporočil MMS med gostovanjem" - "Skupinsko pošiljanje sporočil" - "Če želite eno sporočilo poslati več prejemnikom, uporabite MMS" - "Privzeta aplikacija za sporočila SMS" - "Privzeta aplikacija za sporočila SMS" - - - "Vaša telefonska številka" - "Neznano" - "Zvoki pri odhodnih sporočilih" - "Izvoz SMS-jev" - "Izvoz neobdelanih podatkov iz prejetih SMS-jev v datoteko v zunanji napravi za shranjevanje" - "Izvoz MMS-jev" - "Izvoz neobdelanih podatkov iz prejetih MMS-jev v datoteko v zunanji napravi za shranjevanje" - "Brezžična opozorila" - "Možnosti sporočila" - "Kopiraj besedilo" - "Ogled podrobnosti" - "Brisanje" - "Posreduj" - "Podrobnosti sporočila" - "Vrsta: " - "SMS" - "MMS" - "Od: " - "Za: " - "Poslano: " - "Prejeto: " - "Zadeva: " - "Velikost: " - "Prednost: " - "SIM: " - "Visoka" - "Običajna" - "Nizka" - "SIM %s" - "Skrit naslov pošiljatelja" - "Med nalaganjem priponk ni mogoče poslati sporočila." - "Priloge ni mogoče naložiti. Poskusite znova." - "Omrežje ni pripravljeno. Poskusite znova." - "Izbriši besedilo" - "Preklop med vnašanjem besedila in številk" - "Dodaj več udeležencev" - "Potrditev udeležencev" - "Začenjanje novega pogovora" - "Izbira tega elementa" - "Predvajanje videoposnetka" - "Osebe in možnosti" - "Odpravljanje napak" - "Osebe in možnosti" - "Splošno" - "Osebe v tem pogovoru" - "Klicanje" - "Pošlji sporočilo" - "Pošiljanje sporočila<br/><small>s kartico %s</small>" - - Pošljite fotografije - Pošljite fotografije - Pošljite fotografije - Pošljite fotografije - - - Pošljite zvočne posnetke - Pošljite zvočne posnetke - Pošljite zvočne posnetke - Pošljite zvočne posnetke - - - Pošljite videoposnetke - Pošljite videoposnetke - Pošljite videoposnetke - Pošljite videoposnetke - - - Pošljite osebne vizitke - Pošljite osebne vizitke - Pošljite osebne vizitke - Pošljite osebne vizitke - - - Pošljite priloge - Pošljite priloge - Pošljite priloge - Pošljite priloge - - - %d priloga je pripravljena za pošiljanje - %d prilogi sta pripravljeni za pošiljanje - %d priloge so pripravljene za pošiljanje - %d prilog je pripravljenih za pošiljanje - - "Pošljite povratne informacije" - "Ogled v Trgovini Google Play" - "Podatki o različici" - "Različica %1$s" - "Odprtokodne licence" - "Obvestila" - "Dosežena je omejitev za priloge" - "Nalaganje priloge ni uspelo." - "Ali želite dodati med stike?" - "Dodaj stik" - "Zadeva" - "Zadeva: " - "%s%s" - "Nalaganje osebne vizitke" - "Vizitke ni bilo mogoče naložiti" - "Ogled osebne vizitke" - - %d stik - %d stika - %d stiki - %d stikov - - "Vizitke" - "Rojstni dan" - "Zapiski" - "Posredovanje sporočila" - "Odgovor" - "še 1" - "več kot %d" - "SMS-ji so onemogočeni" - "Če želite pošiljati, nastavite Sporočila kot privzeto aplikacijo za sporočila SMS" - "Nastavite Sporočila kot privzeto aplikacijo za sporočila SMS" - "Spremeni" - "Če želite prejemati sporočila, nastavite Sporočila kot privzeto aplikacijo za sporočila SMS" - "Ni izbrane prednostne kartice SIM za pošiljanje sporočil SMS" - "Lastnik naprave ne dovoli uporabe te aplikacije." - "V redu" - "Preveč udeležencev v pogovoru" - - Neveljavni stiki - Neveljavni stiki - Neveljavni stiki - Neveljavni stiki - - "Slike fotoaparata ni bilo mogoče naložiti" - "Vi: " - "%s: " - "Osnutek" - "Ko začnete nov pogovor, bo naveden tukaj" - "Tu so prikazani arhivirani pogovori" - "Nalaganje pogovorov ..." - "Slika" - "Zvočni posnetek" - "Videoklic" - "Osebna vizitka" - "MMS" - "Razveljavi" - "Poskusi znova" - "Če želite začeti novo sporočilo, vnesite ime stika ali telefonsko številko" - "Blokiraj" - "Blokiraj stik %s" - "Odblokiraj stik %s" - "Želite blokirati %s?" - "Še vedno boste prejemali sporočila s te številke, vendar ne boste več obveščeni. Ta pogovor bo arhiviran." - "Blokirani stiki" - "ODBLOKIRAJ" - "Blokirani stiki" - "Izbira slike iz knjižnice dokumentov" - "Pošiljanje sporočila" - "Sporočilo poslano" - "Prenos podatkov v mobilnem omrežju je izklopljen. Preverite nastavitve." - "V načinu za letalo ni mogoče pošiljati sporočil" - "Sporočila ni bilo mogoče poslati." - "Sporočilo preneseno" - "Prenos podatkov v mobilnem omrežju je izklopljen. Preverite nastavitve." - "V načinu za letalo ni mogoče prenašati sporočil" - "Sporočila ni bilo mogoče prenesti" - "Nič" - "Ena" - "Dva" - "Tri" - "Štiri" - "Pet" - "Šest" - "Sedem" - "Osem" - "Devet" - "Sporočila prek operaterja %1$s ni mogoče poslati, napaka %2$d" - "Sporočila prek neznanega operaterja ni mogoče poslati, napaka %1$d" - "Fwd: %s" - "Sporočilo ni bilo poslano: storitev ni aktivirana v omrežju" - "Sporočilo ni poslano: neveljaven naslov prejemnika" - "Sporočilo ni poslano: neveljavno sporočilo" - "Sporočilo ni poslano: nepodprta vsebina" - "Sporočilo ni poslano: nepodprto sporočilo" - "Sporočilo ni bilo poslano: prevelika velikost" - "Novo sporočilo" - "Pogled" - "Slika" - "Ni bilo mogoče najti primerne aplikacije" - "Odstrani prejemnika" - "Novo sporočilo" - "Prekliči" - "Urejanje dostopne točke" - "Ni nastavljeno" - "Ime" - "APN" - "Storitveni center za sporočila MMS" - "Strežnik proxy za sporočila MMS" - "Vrata za sporočila MMS" - "Mobilna koda države" - "Koda mobilnega omrežja" - "Vrsta APN-ja" - "Izbriši APN" - "Nov APN" - "Shrani" - "Zavrzi" - "Polje z imenom ne sme biti prazno." - "APN ne sme biti prazen." - "Polje z mobilno kodo države mora vsebovati 3 števke." - "Polje s kodo mobilnega omrežja mora vsebovati 2 ali 3 števke." - "Obnavljanje privzetih nastavitev APN-ja." - "Ponastavi na privzeto" - "Ponastavitev privzetih nastavitev APN-ja je končana." - "Neimenovano" - "Imena dostopnih točk" - "APN-ji" - "Nov APN" - "Nastavitve imena dostopne točke niso na voljo za tega uporabnika" - "Želite kopirati v odložišče?" - "Kopiraj" - "v kartico %s" - "Splošno" - "Dodatno" - "Splošne nastavitve" - "Dodatne nastavitve" - "Kartica SIM »%s«" - "Pošiljanje posameznih sporočil SMS vsem prejemnikom. Morebitne odgovore boste prejeli samo vi." - "Pošiljanje enega sporočila MMS vsem prejemnikom" - "Neznana številka" - "Novo sporočilo" - "Novo sporočilo." - "Izbirnik kartice SIM" - "Izbrana kartica SIM %1$s, izbirnik kartic SIM" - "Uredi zadevo" - "Izberi kartico SIM ali uredi zadevo" - "Dotakni se in pridrži za snemanje zvoka" - "Začenjanje novega pogovora" - "Sporočila" - "Seznam sporočil" - "Sporočila" - "Novo sporočilo" - "Seznam pogovorov" - "Nalaganje pogovorov" - "Nalaganje sporočil" - "Več pogovorov" - "Prikaz več sporočil" - "Pogovor je izbrisan" - "Pogovor je izbrisan. Dotaknite se, če želite prikazati drug pogovor v Sporočilih." - "Blokiran(-a)" - "Odblokiran(-a)" - "Primanjkuje prostora za shranjevanje. Nekateri podatki bodo morda izgubljeni." - "Izbira prilog" - "Potrditev izbora" - "Št. izbranih: %d" - "Odstranite eno ali več prilog in poskusite znova." - "Lahko poskusite poslati sporočilo, vendar morda ne bo dostavljeno, razen če odstranite eno ali več prilog." - "Pošljete lahko samo en videoposnetek na sporočilo. Odstranite druge videoposnetke in poskusite znova." - "Sporočila niso uspela naložiti priloge." - "Vseeno pošlji" - "Pogovora ni bilo mogoče začeti" - "%1$s (%2$s)" - "Izbrana kartica SIM %s" - diff --git a/res/values-sq-rAL/arrays.xml b/res/values-sq-rAL/arrays.xml deleted file mode 100644 index 2744180..0000000 --- a/res/values-sq-rAL/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "pa subjekt" - "pa subjekt" - - - "Po" - "Jo" - "Në rregull!" - "Hehe" - "Faleminderit" - "Pranoj" - "Këndshëm" - "Jam rrugës" - "Në rregull, më lejo të të marr më vonë" - ":)" - ":(" - - diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml deleted file mode 100644 index b98ea92..0000000 --- a/res/values-sq-rAL/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mesazhet" - "Mesazhet" - "Përzgjidh bisedën" - "Cilësimet" - "Dërgo mesazh" - "Shto një bashkëngjitje" - "Ndihma" - "Mirë se erdhe" - "Kapërce" - "Përpara >" - "Përpara" - "Dil" - "Cilësimet >" - "Cilësimet" - "Mesazhet kanë nevojë për leje për në SMS, Telefon dhe Kontakte." - "Lejet mund t\'i ndryshosh te Cilësimet, Aplikacionet, Mesazhet, Lejet." - "Lejet mund t\'i ndryshosh te Cilësimet, Aplikacionet, Mesazhet, Lejet." - "Të shpeshta" - "Të gjitha kontaktet" - "Dërgoje te %s" - "Regjistro fotografi ose video" - "Zgjidh imazhe nga kjo pajisje" - "Regjistro audio" - "Zgjidh foto" - "Artikulli \"media\" u zgjodh." - "Artikulli \"media\" nuk u zgjodh." - "U përzgjodhën %d" - "imazhi %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "imazhi" - "Regjistro audio" - "Shpërnda" - "Pikërisht tani" - "Tani" - - %d minuta - %d minutë - - - %d orë - %d orë - - - %d ditë - %d ditë - - - %d javë - një javë - - - %d muaj - një muaj - - - %d vite - një vit - - "Mesazh i kategorisë 0" - "Ruaj" - "Në pajisje ka mbetur pak hapësirë e lirë. Mesazhet do të fshijnë automatikisht mesazhet e vjetra për të liruar hapësirë." - "Hapësira ruajtëse po mbaron" - "\"Mesazhet\" mund të mos dërgojë ose pranojë mesazhe derisa të ketë hapësirë tjetër të lirë në pajisjen tënde." - "Hapësirë e paktë ruajtëse për SMS-tw. Mund të të duhet të fshish mesazhe." - "Konfirmo numrin tënd të telefonit" - "Ky hap që kryhet vetëm një herë do të garantojë që Mesazhet t\'i dorëzojë siç duhet mesazhet e tua në grup." - "Numri i telefonit" - "Fshiji të gjitha mesazhet me klipe \"media\"" - "Fshi mesazhe më të vjetra se %s" - "Fshi automatikisht mesazhe më të vjetra se %s" - "Shpërfill" - "Të fshihen të gjitha mesazhet me klipe \"media\"?" - "Të fshihen mesazhet më të vjetra se %s?" - "Të fshihen mesazhet më të vjetra se %s dhe të aktivizohet fshirja automatike?" - "%s tha" - "Ti the" - "Mesazh nga %s" - "Dërgove një mesazh" - "Po dërgon..." - "Nuk u dërgua. Prek për të provuar sërish." - "Nuk u dërgua. Po provon sërish…" - "Dërgo sërish ose fshi" - "Bëj një telefonatë me zë drejt shërbimeve të urgjencës. Mesazhi yt me tekst nuk arriti të dërgohej këtë herë." - "Dështoi" - "Mesazh i ri MMS-je për të shkarkuar" - "Mesazh i ri MMS-je" - "Nuk arriti ta shkarkonte" - "Prek për të provuar sërish" - "Prek për të shkarkuar" - "Shkarko ose fshi" - "Po shkarkon…" - "Mesazhi skadoi ose nuk mund të përdoret" - "madhësia: %1$s, skadimi: %2$s" - "Nuk mund ta dërgojë. Marrësi është i pavlefshëm." - "Shërbimi nuk është i aktivizuar në rrjet" - "Nuk arriti ta dërgonte për shkak të një problemit në rrjet" - "Mesazhi skadoi ose nuk mundësohet" - "(Pa subjekt)" - "Dërgues i panjohur" - "Dorëzuar" - "Nuk arriti të shkarkonte mesazhin %1$s nga %2$s." - "Nuk arriti të përfundonte operacionin e bazës së të dhënave për shkak të kujtesës së pakët" - "Mesazhi nuk është dërguar" - "Disa mesazhe nuk janë dërguar te Mesazhet" - - %d mesazhe në %d bashkëbisedime - %d mesazhe në një bashkëbisedim - - "Mesazhi nuk u shkarkua" - "Disa mesazhe nuk janë shkarkuar te Mesazhet" - - %d mesazhe në %d bashkëbisedime - %d mesazhe në një bashkëbisedim - - "Mesazhi për %1$s nuk u dërgua" - "Bëj një telefonatë me zë drejt shërbimeve të urgjencës. Mesazhi yt me tekst për %1$s nuk arriti të dërgohej këtë herë." - - %d mesazhe të reja - Mesazh i ri - - "Fillo" - "Kamera nuk mund të përdoret" - "Kamera nuk mund të përdoret" - "Regjistrimi i videos nuk mundësohet" - "Nuk mund të ruajë materiale \"media\"" - "Nuk mund të nxjerrë fotografi" - "Prapa" - "Arkivuar" - "Fshi" - "Arkivo" - "Hiq nga arkiva" - "Çaktivizo njoftimet" - "Aktivizo njoftimet" - "Shto kontakt" - "Shkarko" - "Dërgo" - "Fshi" - "Të fshihet ky mesazh?" - "Ky veprim nuk mund të zhbëhet." - "Fshi" - - Fshiji këto biseda? - Fshije këtë bisedë? - - "Fshi" - "Anulo" - "Për" - "Përzgjidh shumë imazhe" - "Konfirmo përzgjedhjen" - "+%d" - "Nuk mund të regjistrojë audio. Provo sërish." - "Nuk mund të luajë audio. Provo sërish." - "Nuk arriti ta ruante audion. Provo sërish." - "Prek dhe mbaj shtypur" - ", " - " " - ": " - " " - "Fotografi" - "Klip audioje" - "Video" - "Kartë kontakti" - "Shkarko" - "Përgjigju përmes SMS-së" - "Përgjigju përmes MMS-së" - "Përgjigju" - - %d pjesëmarrës - %d pjesëmarrës - - "Unë" - "Kontakt i bllokuar dhe i arkivuar" - "Kontakti u zhbllokua dhe arkivimi i tij u anulua" - "%d të arkivuara" - "%d të paarkivuara" - "Njoftimet janë të çaktivizuara" - "Njoftimet janë të aktivizuara" - "Gjithçka gati. Prek sërish \"Dërgo\"." - "\"Mesazhet\" u caktua me sukses si aplikacioni me parazgjedhje i SMS-ve." - - Hiq bashkëngjitjet - Hiq bashkëngjitjen - - "Bashkëngjitja audio" - "Luaj bashkëngjitjen në audio" - "Pauzë" - "Mesazh nga %s: %s." - "Mesazh i dështuar nga %s: %s. Ora: %s." - "Mesazh nga %s: %s. Ora: %s." - "Mesazh i padërguar për %s: %s. Ora: %s." - "Po dërgon mesazh te %s: %s. Ora: %s." - "Mesazh i dështuar për %s: %s. Ora: %s." - "Mesazh për %s: %s. Ora: %s." - "Mesazh i dështuar nga %s: %s. Ora: %s. %s." - "Mesazh nga %s: %s. Ora: %s. %s." - "Mesazh i padërguar për %s: %s. Ora: %s." - "Po dërgon mesazh te %s: %s. Ora: %s." - "Mesazh i dështuar për %s: %s. Ora: %s." - "Mesazh për %s: %s. Ora: %s." - "Mesazhi dështoi. Prek për ta provuar sërish." - "Bisedë me %s" - "Fshi subjektin" - "Regjistro video" - "Regjistro një pamje të palëvizshme" - "Nxirr fotografi" - "Fillo regjistrimin në video" - "Kalo në kamerën me ekran të plotë" - "Ndërro mes kamerës para dhe mbrapa" - "Ndalo regjistrimin dhe bashkëngjit video" - "Ndalo regjistrimin e videos" - "Foto me mesazhe" - - %d fotografi u ruajtën tek albumi \"%s\" - %d fotografi u ruajt tek albumi \"%s\" - - - %d video u ruajtën tek albumi \"%s\" - %d video u ruajt tek albumi \"%s\" - - - %d bashkëngjitje u ruajtën tek albumi\"%s\" - %d bashkëngjitje u ruajt tek albumi \"%s\" - - - %d bashkëngjitje u ruajtën tek \"Shkarkimet\" - %d bashkëngjitje u ruajt tek \"Shkarkimet\" - - - %d bashkëngjitje u ruajtën - %d bashkëngjitje u ruajt - - - %d bashkëngjitje nuk mund të ruheshin - %d bashkëngjitje nuk mund të ruhej - - "Bashkëgjitje MMS e ruajtur" - "Cilësimet" - "Arkivuar" - "Mbyll" - "MMS" - "Të përparuara" - "Korrigjo" - "Njoftime" - "Zëri" - "Hesht" - "Dridh" - "I bllokuar" - "Raportet e dorëzimit të SMS" - "Kërko një raport dorëzimi për çdo SMS që dërgon" - "Gjej automatikisht" - "Gjej automatikisht mesazhe MMS" - "Gjetje automatike në roaming" - "Merr automatikisht mesazhin MMS kur je në roaming" - "Mesazhe në grup" - "Përdor \"MMS\" për të dërguar një mesazh të vetëm kur ka shumë marrës" - "Aplikacioni me parazgjedhje i SMS-ve" - "Aplikacioni me parazgjedhje i SMS-ve" - - - "Numri yt i telefonit" - "E panjohur" - "Tingujt e mesazheve dalëse" - "Hidh SMS-të" - "Hidhi të dhënat SMS të papërpunuara në skedarin e hapësirës së jashtme ruajtëse" - "Hidh MMS-të" - "Hidhi të dhënat MMS të papërpunuara në skedarin e hapësirës së jashtme ruajtëse" - "Sinjalizimi pa tel" - "Opsionet e mesazhit" - "Kopjo tekstin" - "Shiko detajet" - "Fshi" - "Përpara" - "Detajet e mesazhit" - "Shkruaj: " - "Mesazh me tekst" - "Mesazh multimedial" - "Nga: " - "Për: " - "Dërguar: " - "Marrë: " - "Subjekti: " - "Madhësia: " - "Përparësia: " - "Karta SIM: " - "E lartë" - "Normale" - "E ulët" - "Numri i kartës SIM: %s" - "Adresë e fshehur e dërguesit" - "Nuk mund të dërgojë mesazh kur ngarkon bashkëngjitje." - "Nuk mund ta ngarkojë bashkëngjitjen. Provo sërish." - "Rrjeti nuk është gati. Provo përsëri." - "Fshi tekstin" - "Kalo mes futjes së tekstit dhe numrave" - "Shto më shumë pjesëmarrës" - "Konfirmo pjesëmarrësit" - "Fillo bisedë të re" - "Përzgjidh këtë artikull" - "Luaj video" - "Kontaktet dhe opsionet" - "Korrigjo" - "Kontaktet dhe opsionet" - "Të përgjithshme" - "Kontakte në këtë bisedë" - "Bëj telefonatë" - "Dërgo mesazh" - "Dërgo mesazh<br/><small>nga %s</small>" - - Dërgo fotografitë - Dërgo fotografinë - - - Dërgo audiot - Dërgo audion - - - Dërgo videot - Dërgo videon - - - Dërgo kartat e kontaktit - Dërgo kartën e kontaktit - - - Dërgo bashkëngjitjet - Dërgo bashkëngjitjen - - - %d bashkëngjitje gati për dërgim - Një bashkëngjitje gati për dërgim - - "Dërgo përshtypjet" - "Shiko në Dyqanin \"Luaj me Google\"" - "Informacion mbi versionin" - "Versioni %1$s" - "Licenca me burim informacioni të hapur" - "Njoftime" - "U arrit kufiri i bashkëngjitjeve" - "Dështoi në ngarkimin e bashkëngjitjes." - "Shto te kontaktet?" - "Shto kontakt" - "Subjekti" - "Subjekti: " - "%s%s" - "Po ngarkon kartën e kontaktit" - "Nuk arriti të ngarkonte kartën e kontakteve" - "Shiko kartën e kontaktit" - - %d kontakte - %d kontakt - - "Kartat e kontakteve" - "Ditëlindje" - "Shënime" - "Transfero mesazhin" - "Përgjigju" - "+1" - "+%d" - "Funksioni \"SMS\" u çaktivizua" - "Për të dërguar, caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve" - "Caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve" - "Ndrysho" - "Për të pranuar mesazhe, caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve" - "Nuk u përzgjodh kartë e preferuar SIM për dërgimin e mesazheve SMS" - "Ky aplikacion nuk lejohet nga zotëruesi i pajisjes." - "Në rregull!" - "Shumë pjesëmarrës në një bisedë" - - Kontakte të pavlefshme - Kontakt i pavlefshëm - - "Nuk arriti të ngarkonte pamjen e kamerës" - "Ti: " - "%s: " - "Draft" - "Pasi të fillosh një bisedë të re, do ta shikosh të listuar këtu" - "Bisedat e arkivuara shfaqen këtu" - "Po ngarkon bisedat…" - "Fotografi" - "Klip audioje" - "Video" - "Kartë kontakti" - "MMS" - "Zhbëj" - "Provo përsëri" - "Fut një emër kontakti ose numër telefoni për të nisur një mesazh të ri" - "Blloko" - "Blloko %s" - "Zhblloko %s" - "Të bllokohet %s?" - "Do të vazhdosh të marrësh mesazhe nga ky numër, por nuk do të njoftohesh më. Kjo bisedë do të arkivohet." - "Kontaktet e bllokuara" - "ZHBLLOKO" - "Kontaktet e bllokuara" - "Zgjidh imazh nga biblioteka e dokumenteve" - "Po dërgon mesazhin" - "Mesazhi u dërgua" - "Të dhënat celulare janë të çaktivizuara. Kontrollo cilësimet." - "Nuk mund të dërgojë mesazhe në modalitetin \"në aeroplan\"" - "Mesazhi nuk u arrit të dërgohej" - "Mesazhi u shkarkua" - "Të dhënat celulare janë të çaktivizuara. Kontrollo cilësimet." - "Nuk mund të shkarkojë mesazhe në modalitetin \"në aeroplan\"" - "Mesazhi nuk u arrit të shkarkohej" - "Zero" - "Një" - "Dy" - "Tre" - "Katër" - "Pesë" - "Gjashtë" - "Shtatë" - "Tetë" - "Nëntë" - "Mesazhi nuk mund të dërgohet me %1$s - gabimi %2$d" - "Mesazhi nuk mund të dërgohet me një operator celular të panjohur - gabimi %1$d" - "Transfero: %s" - "Mesazhi nuk u dërgua - shërbimi nuk është i aktivizuar në rrjet" - "Mesazhi nuk u dërgua - adresë e pavlefshme vendmbërritje" - "Mesazhi nuk u dërgua - mesazh i pavlefshëm" - "Mesazhi nuk u dërgua - përmbajtje e pambështetur" - "Mesazhi nuk u dërgua - mesazh i pambështetur" - "Mesazhi nuk u dërgua: shumë i madh" - "Mesazh i ri" - "Shiko" - "Imazh" - "Nuk arriti të gjente një aplikacion të përshtatshëm" - "Hiq marrësin" - "Mesazh i ri" - "Anulo" - "Redakto pikën e qasjes" - "Nuk është vendosur" - "Emri" - "APN-ja" - "MMSC-ja" - "Përfaqësuesi i MMS-ve" - "Porta e MMS-ve" - "MCC-ja" - "MNC" - "Lloji i APN-së" - "Fshi APN-në" - "APN e re" - "Ruaj" - "Injoro" - "Fusha \"Emri\" nuk të lihet bosh." - "APN-ja nuk mund të lihet bosh." - "Fusha e MCC-së duhet të ketë 3 shifra." - "Fusha e MNC-së duhet të ketë 2 ose 3 shifra." - "Po restauron cilësimet e APN-së me parazgjedhje." - "Rivendos në parazgjedhje" - "Rivendosja e cilësimeve të APN-së me parazgjedhje përfundoi." - "Pa titull" - "Emrat e Pikës së Qasjes" - "APN-të" - "APN e re" - "Cilësimet e Emrit të Pikës së Qasjes nuk mund të përdoren për këtë përdorues" - "Të kopjohet në kujtesën e fragmenteve" - "Kopjo" - "për %s" - "Të përgjithshme" - "Të përparuara" - "Cilësimet e përgjithshme" - "Cilësimet e përparuara" - "Emri i kartës SIM: \"%s\"" - "Dërgoju mesazhe SMS të gjithë marrësve. Vetëm ti do të marrësh përgjigje" - "Dërgoju një MMS të vetme, të gjithë marrësve" - "Numër i panjohur" - "Mesazh i ri." - "Mesazh i ri." - "Përzgjedhësi i kartës SIM" - "%1$s i përzgjedhur, përzgjedhësi i kartës SIM" - "Redakto subjektin" - "Përzgjidh kartën SIM ose redakto subjektin" - "Prek dhe mbaj shtypur për të regjistruar audio" - "Fillo bisedë të re" - "Mesazhet" - "Lista e Mesazheve" - "Mesazhet" - "Mesazh i ri" - "Lista e bisedave" - "Po ngarkon bisedat" - "Po ngarkon mesazhet" - "Shiko më shumë biseda" - "Shiko më shumë mesazhe" - "Biseda është fshirë" - "Biseda u fshi. Prek për të shfaqur një bisedë tjetër te \"Mesazhet\"" - "I bllokuar" - "I zhbllokuar" - "Hapësira ruajtëse është e vogël. Disa të dhëna mund të humbin." - "Përzgjidh bashkëngjitje" - "Konfirmo përzgjedhjen" - "U përzgjodhën %d" - "Hiq një ose më shumë bashkëngjitje dhe provo sërish." - "Mund të provosh ta dërgosh mesazhin, por mund të mos dërgohet pa hequr një ose disa bashkëngjitje." - "Mund të dërgosh vetëm një video për çdo mesazh. Hiqi videot shtesë dhe provo përsëri." - "Mesazhet dështuan në ngarkimin e bashkëngjitjes." - "Dërgoje gjithsesi" - "Nuk arriti të fillonte bisedën" - "%1$s (%2$s)" - "%s u zgjodh" - diff --git a/res/values-sr/arrays.xml b/res/values-sr/arrays.xml deleted file mode 100644 index f099488..0000000 --- a/res/values-sr/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "без наслова" - "нема наслова" - - - "Да" - "Не" - "Потврди" - "Хе хе" - "Хвала" - "Слажем се" - "Фино" - "Стижем ускоро" - "Добро, јавићу ти се касније" - ":)" - ":(" - - diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml deleted file mode 100644 index 7155cd9..0000000 --- a/res/values-sr/strings.xml +++ /dev/null @@ -1,545 +0,0 @@ - - - - - "Размена порука" - "Размена порука" - "Изаберите преписку" - "Подешавања" - "Пошаљи поруку" - "Додај прилог" - "Помоћ" - "Добро дошли" - "Прескочи" - "Следеће >" - "Даље" - "Изађи" - "Подешавања >" - "Подешавања" - "Размена порука тражи дозволу за SMS, телефон и контакте." - "Можете да мењате дозволе у одељку Подешавања > Апликације > Размена порука > Дозволе." - "Можете да мењате дозволе у одељку Подешавања, Апликације, Размена порука, Дозволе." - "Често контактирани" - "Сви контакти" - "Пошаљи на %s" - "Сними слику или видео снимак" - "Избор слика са овог уређаја" - "Сними звук" - "Изаберите слику" - "Изабрали сте медијски садржај." - "Опозвали сте избор медијског садржаја." - "Изабраних: %d" - "слика %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "слика" - "Снимање звука" - "Дели" - "Управо сад" - "Сада" - - %d мин - %d мин - %d мин - - - %d сат - %d сата - %d сати - - - %d дан - %d дана - %d дана - - - %d недеље - %d недеље - %d недеља - - - %d месеца - %d месеца - %d месеци - - - %d године - %d године - %d година - - "Порука класе 0" - "Сачувај" - "Понестаје простора на уређају. Апликација за размену порука ће аутоматски обрисати старије поруке да би ослободила простор." - "Понестаје складишног простора" - "Размена порука можда неће моћи да шаље нити прима поруке док још простора не буде доступно на уређају." - "Понестаје меморије за SMS. Можда ћете морати да избришете поруке." - "Потврдите број телефона" - "Овим једнократним кораком обезбеђујете да Размена порука правилно испоручује групне поруке." - "Број телефона" - "Избриши све поруке са медијским садржајем" - "Избриши поруке старије од %s" - "Аутоматски бриши поруке старије од %s" - "Игнориши" - "Желите ли да избришете све поруке са медијским садржајем?" - "Желите ли да избришете поруке старије од %s?" - "Желите ли да избришете поруке старије од %s и да укључите аутоматско брисање?" - "%s каже" - "Рекли сте" - "Порука од корисника %s" - "Послали сте поруку" - "Шаље се..." - "Слање није успело. Додирните да бисте покушали поново." - "Слање није успело. Покушавамо поново…" - "Пошаљите поново или избришите" - "Упутите гласовни позив службама за хитне случајеве. Слање SMS-а тренутно није успело." - "Није успело" - "Нова MMS порука за преузимање" - "Нова MMS порука" - "Преузимање није успело" - "Додирните да бисте покушали поново" - "Додирните да бисте преузели" - "Преузмите или избришите" - "Преузима се..." - "Порука је истекла или није доступна" - "величина: %1$s, истиче: %2$s" - "Слање није могуће. Прималац није важећи." - "Услуга није активирана у мрежи" - "Слање није могуће због проблема са мрежом" - "Порука је истекла или није доступна" - "(Без наслова)" - "Непознати пошиљалац" - "Испоручено" - "Није могуће преузети поруку %1$s пошиљаоца %2$s." - "Није могуће довршити операцију у вези са базом података због недостатка меморије" - "Порука није послата" - "Неке поруке нису послате у Размени порука" - - %d поруке(а) у %d конверзацији - %d поруке(а) у %d конверзације - %d поруке(а) у %d конверзација - - "Порука није преузета" - "Неке поруке нису преузете у Размени порука" - - %d поруке(а) у %d конверзацији - %d поруке(а) у %d конверзације - %d поруке(а) у %d конверзација - - "Порука на %1$s није послата" - "Упутите гласовни позив службама за хитне случајеве. Слање SMS-а на %1$s тренутно није успело." - - %d нова порука - %d нове поруке - %d нових порука - - "Покрени" - "Камера није доступна" - "Камера није доступна" - "Снимање видео снимака није доступно" - "Чување медија није могуће" - "Није успело снимање слике" - "Назад" - "Архивирано" - "Избриши" - "Архивирај" - "Опозови архивирање" - "Искључи обавештења" - "Укључи обавештења" - "Додај контакт" - "Преузми" - "Пошаљи" - "Избриши" - "Желите да избришете ову поруку?" - "Ова радња не може да се опозове." - "Избриши" - - Желите ли да избришете ове конверзације? - Желите ли да избришете ове конверзације? - Желите ли да избришете ове конверзације? - - "Избриши" - "Откажи" - "Прима" - "Изаберите више слика" - "Потврди избор" - "+%d" - "Снимање звука није успело. Покушајте поново." - "Пуштање звука није успело. Покушајте поново." - "Чување звука није успело. Покушајте поново." - "Додирните и задржите" - ", " - " " - ": " - " " - "Слика" - "Аудио исечак" - "Видео" - "Картица контакта" - "Преузми" - "Одговори преко SMS-а" - "Одговори MMS-ом" - "Одговорите" - - %d учесник - %d учесника - %d учесника - - "Ја" - "Контакт је блокиран и архивиран" - "Контакт је деблокиран и архивирање је опозвано" - "Архивирано: %d" - "Опозвано је архивирање за: %d" - "Обавештења су искључена" - "Обавештења су укључена" - "Све је спремно. Додирните Пошаљи поново." - "Размена порука је подешена као подразумевана апликација за SMS." - - Одбаци прилоге - Одбаци прилоге - Одбаци прилоге - - "Аудио прилог" - "Пусти аудио прилог" - "Пауза" - "%s вам шаље нову поруку: %s" - "Није примљена порука од корисника %s: %s. Време: %s." - "Порука од корисника %s: %s. Време: %s." - "Порука није послата кориснику %s: %s. Време: %s." - "Шаље се порука кориснику %s: %s. Време: %s." - "Није успело слање поруке кориснику %s: %s. Време: %s." - "Порука кориснику %s: %s. Време: %s." - "Није примљена порука од корисника %s: %s. Време: %s. %s." - "Порука од корисника %s: %s. Време: %s. %s." - "Порука која није послата групи %s: %s. Време: %s." - "Шаље се порука групи %s: %s. Време: %s." - "Није успело слање поруке групи %s: %s. Време: %s." - "Порука групи %s: %s. Време: %s." - "Слање поруке није успело. Додирните да бисте поново покушали." - "Конверзација са учесницима %s" - "Брисање наслова" - "Снимите видео" - "Снимите слику" - "Снимите слику" - "Почните да снимате видео" - "Пређите на камеру целог екрана" - "Пребацујте са предње на задњу камеру и обрнуто" - "Заустави снимање и приложи видео" - "Заустави снимање звука" - "Слике из Размене порука" - - %d слика је сачувана у албуму „%s - %d слике су сачуване у албуму „%s - %d слика је сачувано у албуму „%s - - - %d видео је сачуван у албуму „%s - %d видео снимка су сачувана у албуму „%s - %d видео снимака је сачувано у албуму „%s - - - %d прилог је сачуван у албуму „%s - %d прилога су сачувана у албуму „%s - %d прилога је сачувано у албуму „%s - - - %d прилог је сачуван у директоријуму „Преузимања“ - %d прилога су сачувана у директоријуму „Преузимања“ - %d прилога је сачувано у директоријуму „Преузимања“ - - - %d прилог је сачуван - %d прилога су сачувана - %d прилога је сачувано - - - Нисмо успели да сачувамо %d прилог - Нисмо успели да сачувамо %d прилога - Нисмо успели да сачувамо %d прилога - - "Сачувани прилог MMS-а" - "Подешавања" - "Архивирано" - "Затвори" - "MMS" - "Напредно" - "Отклањање грешака" - "Обавештења" - "Звук" - "Нечујно" - "Вибрација" - "Блокирано" - "Извештаји о испоруци SMS-ова" - "Захтевајте извештај о испоруци за сваки послати SMS" - "Аутоматско преузимање" - "Аутоматско преузимање MMS порука" - "Аутоматски преузимај у ромингу" - "Аутоматски преузимај MMS у ромингу" - "Групна размена порука" - "Користите MMS за слање једне поруке када постоји више прималаца" - "Подразумевана апликација за SMS" - "Подразумевана апликација за SMS" - - - "Број телефона" - "Непознато" - "Звукови за одлазне поруке" - "Направи сирову копију SMS-а" - "Направи сирову копију необрађених података примљеног SMS-а у датотеци спољног складишног простора" - "Направи сирову копију MMS-а" - "Направи сирову копију необрађених података примљеног MMS-а у датотеци спољног складишног простора" - "Обавештења преко мобилне мреже" - "Опције поруке" - "Копирај текст" - "Погледајте детаље" - "Избриши" - "Проследи" - "Детаљи поруке" - "Тип: " - "SMS" - "Мултимедијална порука" - "Од: " - "Коме: " - "Послато: " - "Примљено: " - "Наслов: " - "Величина: " - "Приоритет: " - "SIM картица: " - "Висок" - "Нормалан" - "Низак" - "Отвор за SIM картицу %s" - "Скривена адреса пошиљаоца" - "Не можете да пошаљете поруку док се прилози учитавају." - "Учитавање прилога није успело. Покушајте поново." - "Мрежа није спремна. Покушајте поново." - "Избриши текст" - "Пребацивање између уноса текста и бројева" - "Додај још учесника" - "Потврди учеснике" - "Започни нову конверзацију" - "Изаберите ову ставку" - "Пусти видео" - "Људи и опције" - "Отклони грешке" - "Људи и опције" - "Опште" - "Људи у овој конверзацији" - "Позови" - "Пошаљите поруку" - "Пошаљите поруку<br/><small>са картице %s</small>" - - Пошаљите слике - Пошаљите слике - Пошаљите слике - - - Пошаљите аудио снимке - Пошаљите аудио снимке - Пошаљите аудио снимке - - - Пошаљите видео снимке - Пошаљите видео снимке - Пошаљите видео снимке - - - Пошаљите картице контаката - Пошаљите картице контаката - Пошаљите картице контаката - - - Пошаљите прилоге - Пошаљите прилоге - Пошаљите прилоге - - - %d прилог је спреман за слање - %d прилога су спремна за слање - %d прилога је спремно за слање - - "Пошаљи повратне информације" - "Прикажи у Google Play продавници" - "Информације о верзији" - "Верзија %1$s" - "Лиценце отвореног кода" - "Обавештења" - "Ограничење за прилоге је достигнуто" - "Учитавање прилога није успело." - "Желите ли да додате у контакте?" - "Додај контакт" - "Наслов" - "Наслов: " - "%s%s" - "Учитава се картица контакта" - "Учитавање картице контакта није успело" - "Погледајте картицу контакта" - - %d контакт - %d контакта - %d контаката - - "Картице контаката" - "Рођендан" - "Белешке" - "Проследите поруку" - "Одговорите" - "+1" - "+%d" - "SMS је онемогућен" - "Да бисте слали поруке, подесите Размену порука као подразумевану апликацију за SMS" - "Подесите Размену порука као подразумевану апликацију за SMS" - "Промени" - "Да бисте примали поруке, подесите Размену порука као подразумевану апликацију за SMS" - "Није изабран жељени SIM за слање SMS-ова" - "Власник уређаја не дозвољава ову апликацију." - "Потврди" - "Превише учесника у конверзацији" - - Неважећи контакти - Неважећи контакти - Неважећи контакти - - "Учитавање слике са камере није успело" - "Ви: " - "%s: " - "Недовршена порука" - "Када започнете нову конверзацију, видећете је наведену овде" - "Овде се појављују архивиране преписке" - "Преписке се учитавају..." - "Слика" - "Аудио клип" - "Видео" - "Картица контакта" - "MMS" - "Опозови" - "Покушај поново" - "Унесите име или број телефона контакта да бисте започели нову поруку" - "Блокирај" - "Блокирај %s" - "Деблокирај %s" - "Желите ли да блокирате %s?" - "И даље ћете примати поруке са овог броја, али нећете више добијати обавештења. Ова преписка ће бити архивирана." - "Блокирани контакти" - "ДЕБЛОКИРАЈ" - "Блокирани контакти" - "Изаберите слику из библиотеке докумената" - "Порука се шаље" - "Порука је послата" - "Подаци за мобилне уређаје су искључени. Проверите подешавања." - "Слање порука није могуће у Режиму рада у авиону" - "Порука није послата" - "Порука је преузета" - "Подаци за мобилне уређаје су искључени. Проверите подешавања." - "Није могуће преузимати поруке у режиму рада у авиону" - "Преузимање поруке није успело" - "Нула" - "Један" - "Два" - "Три" - "Четири" - "Пет" - "Шест" - "Седам" - "Осам" - "Девет" - "Није могуће послати поруку помоћу мобилног оператера %1$s, грешка %2$d" - "Није могуће послати поруку помоћу непознатог мобилног оператера, грешка %1$d" - "Fwd: %s" - "Порука није послата: услуга није активирана на мрежи" - "Порука није послата: неважећа одредишна адреса" - "Порука није послата: неважећа порука" - "Порука није послата: неподржани садржај" - "Порука није послата: неподржана порука" - "Порука није послата: превелика је" - "Нова порука" - "Прикажи" - "Слика" - "Проналажење одговарајуће апликације није успело" - "Уклањање примаоца" - "Нова порука" - "Откажи" - "Измените приступну тачку" - "Није подешено" - "Назив" - "Назив приступне тачке" - "MMSC" - "MMS прокси" - "MMS порт" - "MCC" - "MNC" - "Тип назива приступне тачке" - "Избриши назив приступне тачке" - "Нов назив приступне тачке" - "Сачувај" - "Одбаци" - "Поље Назив не сме да буде празно." - "Поље Назив приступне тачке не сме да буде празно." - "Поље MCC мора да садржи 3 цифре." - "Поље MNC мора да садржи 2 или 3 цифре." - "Враћање подразумеваних подешавања назива приступне тачке." - "Ресетуј подразумеване вредности" - "Ресетовање подразумеваних подешавања назива приступне тачке је довршено." - "Ненасловљено" - "Називи приступних тачака" - "Називи приступних тачака" - "Нов назив приступне тачке" - "Подешавања назива приступне тачке нису доступна за овог корисника" - "Желите ли да копирате у привремену меморију?" - "Копирај" - "на картицу %s" - "Опште" - "Напредно" - "Општа подешавања" - "Напредна подешавања" - "„%s“ SIM" - "Шаљите појединачне SMS поруке свим примаоцима. Само ви ћете добијати одговоре" - "Пошаљите један MMS свим примаоцима" - "Непознат број" - "Нова порука" - "Нова порука." - "Бирач SIM картице" - "Изабран је %1$s, бирач SIM картица" - "Измени наслов" - "Изабери SIM или измени наслов" - "Додирните и задржите за снимање звука" - "Започни нову конверзацију" - "Размена порука" - "Листа из Размене порука" - "Размена порука" - "Нова порука" - "Листа преписки" - "Учитавају се конверзације" - "Учитавају се поруке" - "Прикажи још конверзација" - "Прикажи још порука" - "Конверзација је избрисана" - "Конверзација је избрисана. Додирните да бисте приказали неку другу конверзацију Размене порука" - "Блокирано" - "Деблокирано" - "Нема довољно меморијског простора. Неки подаци ће можда бити изгубљени." - "Изабери прилоге" - "Потврди избор" - "Изабраних: %d" - "Уклоните један прилог или више њих и покушајте поново." - "Можете да покушате да пошаљете поруку, али можда неће бити испоручена ако не уклоните један прилог или више њих." - "Можете да пошаљете само један видео по поруци. Уклоните друге видео снимке и покушајте поново." - "Размена порука није успела да учита прилог." - "Ипак пошаљи" - "Започињање преписке није успело" - "%1$s (%2$s)" - "Изабрали сте %s" - diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml deleted file mode 100644 index 8f03e4b..0000000 --- a/res/values-sv/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "inget ämne" - "inget ämne" - - - "Ja" - "Nej" - "OK" - "Hehe" - "Tack" - "Jag accepterar" - "Nice" - "På väg" - "OK, jag återkommer senare" - ":)" - ":(" - - diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml deleted file mode 100644 index 36ad85c..0000000 --- a/res/values-sv/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Sms/mms" - "Sms/mms" - "Välj konversation" - "Inställningar" - "Skicka meddelande" - "Lägg till en bilaga" - "Hjälp" - "Välkommen" - "Hoppa över" - "Nästa >" - "Nästa" - "Stäng" - "Inställningar >" - "Inställningar" - "Meddelanden behöver ha åtkomst till Sms, Telefon och Kontakter." - "Du kan ändra behörigheterna i Inställningar > Appar > Meddelanden > Behörigheter." - "Du kan ändra behörigheterna i Inställningar, Appar, Meddelanden, Behörigheter." - "Frekventa" - "Alla kontakter" - "Skicka till %s" - "Ta bilder eller spela in video" - "Välj bilder från den här enheten" - "Spela in ljud" - "Välj foto" - "Video-/bildobjektet har markerats." - "Video-/bildobjektet har avmarkerats." - "%d har valts" - "bild %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "bild" - "Spela in ljud" - "Dela" - "Nyss" - "Nu" - - %d min - %d min - - - %d timmar - %d timme - - - %d dagar - %d dag - - - %d veckor - en vecka - - - %d månader - en månad - - - %d år - ett år - - "Meddelande, klass 0" - "Spara" - "Det finns ont om utrymme på enheten. Om du skickar och tar emot sms kommer äldre sms att raderas för att göra plats." - "Lagringsutrymmet börjar ta slut" - "Du kan behöva frigöra utrymme på enheten för att kunna skicka eller ta emot sms med Meddelanden." - "Lagringsutrymmet för sms är fullt. Du kan behöva ta bort sms." - "Bekräfta ditt telefonnummer" - "Genom detta engångssteg säkerställer du att dina gruppmeddelanden levereras som de ska med Meddelanden." - "Telefonnummer" - "Ta bort alla meddelanden med mediefiler" - "Ta bort meddelanden som är äldre än %s" - "Ta automatiskt bort meddelanden som är äldre än %s" - "Ignorera" - "Vill du ta bort alla meddelanden med media?" - "Vill du ta bort meddelanden som är äldre än %s?" - "Vill du ta bort meddelanden som är äldre än %s och aktivera automatisk borttagning?" - "%s sade" - "Du sade" - "Meddelande från %s" - "Du har skickat ett meddelande" - "Skickar …" - "Det gick inte att skicka. Tryck om du vill försöka igen." - "Det gick inte att skicka. Försöker igen ..." - "Skicka igen eller ta bort" - "Ring larmcentralen. Det gick inte att skicka ett sms just nu." - "Misslyckades" - "Nya MMS att ladda ned" - "Nytt MMS" - "Nedladdningen genomfördes inte" - "Tryck om du vill försöka igen" - "Ladda ned genom att trycka" - "Ladda ned eller radera" - "Hämtar ..." - "Meddelandet har upphört att gälla eller är inte tillgängligt" - "storlek: %1$s, finns kvar till: %2$s" - "Det gick inte att skicka. Mottagaren är inte giltig." - "Tjänsten är inte aktiverad i nätverket" - "Det gick inte att skicka på grund av nätverksproblem" - "Meddelandet har upphört att gälla eller är inte tillgängligt." - "(Inget ämne)" - "Okänd avsändare" - "Levererat" - "Det gick inte att ladda ned meddelandet %1$s från %2$s." - "Det gick inte att slutföra åtgärden i databasen på grund av för låg minneskapacitet" - "Meddelandet har inte skickats" - "Vissa meddelanden skickades inte i Meddelanden" - - %d meddelanden i %d konversationer - %d meddelanden i en konversation - - "Meddelandet har inte laddats ned" - "Vissa meddelanden laddades inte ned i Meddelanden" - - %d meddelanden i %d konversationer - %d meddelanden i en konversation - - "Meddelandet till %1$s skickades inte" - "Ring larmcentralen. Det gick inte att skicka ett sms till %1$s just nu." - - %d nya meddelanden - Nytt meddelande - - "Börja" - "Kameran är inte tillgänglig" - "Kameran är inte tillgänglig" - "Det går inte att spela in video" - "Det går inte att spara media" - "Det går inte att fota" - "Föregående" - "Arkiverat" - "Ta bort" - "Arkivera" - "Ångra arkivering" - "Inaktivera aviseringar" - "Aktivera aviseringar" - "Lägg till kontakt" - "Ladda ned" - "Skicka" - "Ta bort" - "Vill du ta bort meddelandet?" - "Åtgärden kan inte ångras." - "Ta bort" - - Vill du ta bort dessa konversationer? - Vill du ta bort konversationen? - - "Ta bort" - "Avbryt" - "Till" - "Markera flera bilder" - "Bekräfta ditt val" - "+%d" - "Det gick inte att spela in ljud. Försök igen." - "Det gick inte att spela upp ljudet. Försök igen." - "Det gick inte att spara ljud. Försök igen." - "Tryck länge" - ", " - " " - ": " - " " - "Bild" - "Ljudklipp" - "Video" - "Visitkort" - "Ladda ned" - "Svara via sms" - "Svara via mms" - "Svara" - - %d deltagare - %d deltagare - - "Jag" - "Kontakten har blockerats och arkiverats" - "Kontakten har återaktiverats och hämtats från arkivet" - "%d har arkiverats" - "%d arkiveringar har ångrats" - "Aviseringar har inaktiverats" - "Aviseringar har aktiverats" - "Allt klart. Tryck på Skicka igen." - "Meddelanden har angetts som standardapp för sms." - - Släng bilagor - Släng bilaga - - "Ljudbilaga" - "Spela upp ljudbilaga" - "Pausa" - "Meddelande från %s: %s." - "Meddelande som inte kunde skickas från %s: %s. Tidpunkt: %s." - "Meddelande från %s: %s. Tidpunkt: %s." - "Meddelande som inte har skickats till %s: %s. Tidpunkt: %s." - "Skickar meddelande till %s: %s. Tidpunkt: %s." - "Meddelande som inte kunde skickas till %s: %s. Tidpunkt: %s." - "Meddelande till %s: %s. Tidpunkt: %s." - "Meddelande som inte kunde skickas från %s: %s. Tidpunkt: %s. %s." - "Meddelande från %s: %s. Tidpunkt: %s. %s." - "Meddelande som inte har skickats till %s: %s. Tidpunkt: %s." - "Skickar meddelande till %s: %s. Tidpunkt: %s." - "Meddelande som inte kunde skickas till %s: %s. Tidpunkt: %s." - "Meddelande till %s: %s. Tidpunkt: %s." - "Det här meddelandet gick inte att skicka. Tryck om du vill försöka igen." - "Samtal med %s" - "Radera ämne" - "Spela in video" - "Ta en stillbild" - "Ta bild" - "Börja spela in video" - "Byt till fullskärmskamera" - "Växla mellan främre och bakre kamera" - "Stoppa inspelningen och bifoga en video" - "Sluta spela in video" - "Skicka foton med Meddelanden" - - %d foton har sparats i albumet %s - %d foto har sparats i albumet %s - - - %d videor har sparats i albumet %s - %d video har sparats i albumet %s - - - %d bilagor har sparats i albumet %s - %d bilaga har sparats i albumet %s - - - %d bilagor har sparats i Nedladdningar - %d bilaga har sparats i Nedladdningar - - - %d bilagor sparas - %d bilaga har sparats - - - Det gick inte att spara %d bilagor - Det gick inte att spara %d bilaga - - "Mms-bilaga har sparats" - "Inställningar" - "Arkiverat" - "Stäng" - "Mms" - "Avancerat" - "Felsökning" - "Aviseringar" - "Ljud" - "Tyst" - "Vibration" - "Blockerad" - "Sms-leveransrapporter" - "Begär en leveransrapport för varje sms du skickar" - "Automatisk hämtning" - "Hämta mms-meddelanden automatiskt" - "Autohämtning vid roaming" - "Hämta mms automatiskt vid roaming" - "Gruppmeddelanden" - "Använd mms för att skicka ett meddelande när det finns flera mottagare" - "Standardapp för sms" - "Standardapp för sms" - - - "Ditt telefonnummer" - "Okänt" - "Ljud vid utgående meddelande" - "Dumpa sms" - "Dumpa rådata för mottagna sms i en extern lagringsfil" - "Dumpa mms" - "Dumpa rådata för mottagna mms i en extern lagringsfil" - "Trådlösa varningar" - "Meddelandealternativ" - "Kopiera text" - "Visa mer information" - "Ta bort" - "Vidarebefordra" - "Meddelandeinformation" - "Typ: " - "Sms" - "Mms" - "Från: " - "Till: " - "Skickat: " - "Mottaget: " - "Ämne: " - "Storlek: " - "Prioritet: " - "SIM: " - "Hög" - "Normal" - "Låg" - "SIM %s" - "Avsändaradress dold" - "Kan inte skicka meddelandet samtidigt som bilagor läses in." - "Det gick inte att läsa in bilagan. Försök igen." - "Nätverket är inte klart. Försök igen." - "Ta bort text" - "Växla mellan att ange text och siffror" - "Lägg till fler deltagare" - "Bekräfta deltagare" - "Starta ny konversation" - "Välj det här objektet" - "Spela upp video" - "Personer och alternativ" - "Felsökning" - "Personer och alternativ" - "Allmänt" - "Personer i den här konversationen" - "Ring ett samtal" - "Skicka meddelande" - "Skicka meddelandet<br/><small>från %s</small>" - - Skicka foton - Skicka foto - - - Skicka ljud - Skicka ljud - - - Skicka videor - Skicka video - - - Skicka kontaktkort - Skicka kontaktkort - - - Skicka bilagor - Skicka bilaga - - - %d bilagor är klara att skicka - En bilaga är klar att skicka - - "Skicka feedback" - "Visa i Google Play Butik" - "Version" - "Version %1$s" - "Licenser, öppen källkod" - "Aviseringar" - "Gränsen för bilagor har uppnåtts" - "Det gick inte att läsa in bilagan." - "Vill du lägga till detta i Kontakter?" - "Lägg till kontakt" - "Ämne" - "Ämne: " - "%s%s" - "Läser in visitkort" - "Det gick inte att läsa in kontaktkortet" - "Visa visitkort" - - %d kontakter - %d kontakt - - "Kontaktkort" - "Födelsedatum" - "Anteckningar" - "Vidarebefordra meddelande" - "Svara" - "+ 1" - "+%d" - "Sms har inaktiverats" - "Ange Meddelanden som standardapp för sms om du vill skicka" - "Ange Meddelanden som standardapp för sms" - "Ändra" - "Ställ in Meddelanden som standardapp för sms om du vill ta emot meddelanden" - "Inget SIM-kort har angetts som primärt för att skicka sms" - "Appen tillåts inte av enhetsägaren." - "OK" - "För många deltagare i en konversation" - - Ogiltiga kontakter - Ogiltig kontakt - - "Det gick inte att läsa in kamerabild" - "Du: " - "%s: " - "Utkast" - "När du startar en ny konversation listas den här" - "Arkiverade konversationer visas här" - "Läser in konversationer …" - "Bild" - "Ljudklipp" - "Video" - "Visitkort" - "Mms" - "Ångra" - "Försök igen" - "Ange namnet på en kontakt eller ett telefonnummer för att starta ett nytt meddelande" - "Blockera" - "Blockera %s" - "Återaktivera %s" - "Vill du blockera %s?" - "Du kan fortfarande få meddelanden från det här numret, men du får inga fler aviseringar. Den här konversationen arkiveras." - "Blockerade kontakter" - "ÅTERAKTIVERA" - "Blockerade kontakter" - "Välj en bild från dokumentbiblioteket" - "Skickar meddelande" - "Meddelandet har skickats" - "Mobildata har inaktiverats. Kontrollera inställningarna." - "Det går inte att skicka meddelanden i flygplansläge" - "Meddelandet kunde inte skickas" - "Meddelandet har laddats ned" - "Mobildata har inaktiverats. Kontrollera inställningarna." - "Det går inte att ladda ned meddelanden i flygplansläge" - "Det gick inte att ladda ned meddelandet" - "Noll" - "Ett" - "Två" - "Tre" - "Fyra" - "Fem" - "Sex" - "Sju" - "Åtta" - "Nio" - "Det gick inte att skicka meddelandet med %1$s, fel %2$d" - "Det gick inte att skicka meddelandet med okänd operatör, fel %1$d" - "Vidarebefordra: %s" - "Meddelandet har inte skickats: tjänsten är inte aktiverad i nätverket" - "Meddelandet skickades inte: ogiltig e-postadress" - "Meddelandet skickades inte: ogiltigt meddelande" - "Meddelandet skickades inte: inget stöd för innehållet" - "Meddelandet skickades inte: inget stöd för meddelandet" - "Meddelandet skickades inte: för stort" - "Nytt meddelande" - "Visa" - "Bild" - "Ingen app för detta hittades" - "Ta bort mottagare" - "Nytt meddelande" - "Avbryt" - "Redigera åtkomstpunkt" - "Har inte angetts" - "Namn" - "APN" - "MMSC" - "MMS-proxy" - "MMS-port" - "MCC" - "MNC" - "APN-typ" - "Ta bort APN" - "Nytt APN" - "Spara" - "Ignorera" - "Fältet Namn får inte vara tomt." - "APN får inte vara tomt." - "MCC-fältet måste bestå av 3 siffror." - "MNC-fältet måste bestå av 2 eller 3 siffror." - "Återställer standardinställningar för APN." - "Återställ standardinställningarna" - "Standardinställningar för APN har återställts." - "Namnlös" - "Åtkomstpunktens namn (APN)" - "APN:er" - "Nytt APN" - "Inställningarna för åtkomstpunktens namn är inte tillgängliga för den här användaren" - "Vill du kopiera till urklipp?" - "Kopiera" - "till %s" - "Allmänt" - "Avancerat" - "Allmänna inställningar" - "Avancerade inställningar" - "SIM-kortet %s" - "Skicka enskilda sms till alla mottagare. Det är endast du som får de eventuella svaren." - "Skicka samma mms till alla mottagare." - "Okänt nummer" - "Nytt meddelande" - "Nytt meddelande." - "SIM-kortsväljaren" - "%1$s har valts, SIM-kortsväljare" - "Redigera ämne" - "Välj SIM-kort eller redigera ämne" - "Tryck länge för att spela in ljud" - "Starta ny konversation" - "Meddelanden" - "Meddelandelista" - "Meddelanden" - "Nytt meddelande" - "Konversationslista" - "Konversationer hämtas" - "Läser in meddelanden" - "Visa fler konversationer" - "Visa fler meddelanden" - "Konversationen har raderats" - "Konversationen raderades. Tryck här om du vill visa en annan konversation i Meddelanden." - "Blockerad" - "Återaktiverad" - "Lagringsutrymmet är nästan fullt. All data kanske inte sparas." - "Välj bilagor" - "Bekräfta ditt val" - "%d har valts" - "Ta bort en eller flera av bilagorna och försök igen." - "Du kan testa att skicka meddelandet, men det kanske inte kommer fram om du inte tar bort en eller flera av bilagorna först." - "Det går bara att skicka en video per meddelande. Ta bort ytterligare videor och försök igen." - "Det gick inte att läsa in bilagan i Meddelanden." - "Skicka ändå" - "Det gick inte att starta en konversation" - "%1$s (%2$s)" - "%s har valts" - diff --git a/res/values-sw/arrays.xml b/res/values-sw/arrays.xml deleted file mode 100644 index 1641f55..0000000 --- a/res/values-sw/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "kichwa hakijaongezwa" - "kichwa hakijaongezwa" - - - "Ndiyo" - "Hapana" - "Sawa" - "Hehe" - "Asante" - "Nakubali" - "Shwari" - "Niko njiani" - "Sawa, nitawasiliana na wewe baadaye" - ":)" - ":(" - - diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml deleted file mode 100644 index b415367..0000000 --- a/res/values-sw/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Utumaji ujumbe" - "Utumaji ujumbe" - "Chagua mazungumzo" - "Mipangilio" - "Tuma Ujumbe" - "Ongeza kiambatisho" - "Usaidizi" - "Karibu" - "Ruka" - "Inayofuata >" - "Inayofuata" - "Ondoka" - "Mipangilio >" - "Mipangilio" - "Programu ya Ujumbe inahitaji ruhusa za SMS, Simu na Anwani." - "Unaweza kubadilisha ruhusa katika Mipangilio> Programu> Ujumbe> Ruhusa." - "Unaweza kubadilisha ruhusa katika Mipangilio, Programu, Ujumbe, Ruhusa." - "Mara kwa mara" - "Anwani zote" - "Tuma kwa %s" - "Piga picha au urekodi video" - "Chagua picha kutoka kifaa hiki" - "Rekodi sauti" - "Chagua picha" - "Maudhui yameteuliwa." - "Maudhui yameondolewa uteuzi." - "%d zimechaguliwa" - "picha %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "picha" - "Rekodi sauti" - "Shiriki" - "Sasa hivi tu" - "Sasa" - - Dakika %d - Dakika %d - - - Saa %d - Saa %d - - - Siku %d - Siku %d - - - wiki %d - wiki - - - miezi %d - mwezi - - - miaka %d - mwaka - - "Ujumbe wa tabaka 0" - "Hifadhi" - "Nafasi haitoshi kwenye kifaa. Programu ya Ujumbe itafuta kiotomatiki ujumbe wa zamani ili kuacha nafasi kwenye kifaa." - "Nafasi ya kuhafadhi inakaribia kujaa" - "Huenda programu ya Ujumbe isitume au kupokea ujumbe hadi nafasi zaidi ipatikane katika kifaa chako." - "Hifadhi ya SMS ni ndogo. Itakubidi ufute ujumbe." - "Thibitisha nambari yako ya simu" - "Hatua hii ya kufanywa mara moja itahakikisha kuwa programu ya Ujumbe itawasilisha ujumbe wako wa kikundi bila tatizo." - "Nambari ya simu" - "Futa ujumbe wote ulio na picha, sauti au video" - "Futa ujumbe uliokaa zaidi ya %s" - "Futa kiotomatiki ujumbe uliokaa zaidi ya %s" - "Puuza" - "Je, ungependa kufuta ujumbe wote ulio na maudhui?" - "Je, ungependa kufuta ujumbe uliokaa zaidi ya %s?" - "Je, ungependa kufuta ujumbe uliokaa zaidi ya %s na uwashe kufuta kiotomatiki?" - "%s alisema" - "Ulisema" - "Ujumbe kutoka %s" - "Ulituma ujumbe" - "Inatuma…" - "Haikutumwa. Gusa ili ujaribu tena." - "Haikutumwa. Jaribu tena..." - "Tuma tena au ufute" - "Tafadhali piga simu ya sauti kwa huduma za dharura. Ujumbe wako haukuweza kupokelewa kwa wakati huu." - "Imeshindikana" - "Ujumbe mpya wa MMS wa kupakua" - "Ujumbe mpya wa MMS" - "Haikuweza kupakua" - "Gusa ili ujaribu tena" - "Gusa ili upakue" - "Pakua au ufute" - "Inapakua…" - "Ujumbe umepitwa na wakati au haupo" - "ukubwa: %1$s, mwisho: %2$s" - "Haiwezi kutuma. Mpokeaji si sahihi." - "Huduma haijaanza kutumika kwenye mtandao." - "Haikutuma kwa sababu ya hitilafu ya mtandao" - "Ujumbe umepitwa na wakati au haupo" - "(Kichwa hakijaongezwa)" - "Mtumaji asiyejulikana" - "Umewasilishwa" - "Haikuweza kupakua ujumbe %1$s kutoka %2$s." - "Haikuweza kukamilisha utendaji wa hifadhidata kwa sababu ya kumbukumbu ndogo" - "Ujumbe haujatumwa" - "Baadhi ya ujumbe haukutumwa katika programu ya Ujumbe" - - Ujumbe %d katika mazungumzo %d - Ujumbe %d katika mazungumzo moja - - "Ujumbe haujapakuliwa" - "Baadhi ya ujumbe haukupakuliwa katika programu ya Ujumbe" - - Ujumbe %dkatika mazungumzo %d - Ujumbe %d katika mazungumzo moja - - "Ujumbe kwa %1$s haukutumwa" - "Tafadhali piga simu ya sauti kwa huduma za dharura. Ujumbe wako kwa %1$s haukuweza kupokelewa kwa wakati huu." - - Ujumbe %d mpya - Ujumbe mpya - - "Anza" - "Kamera haipatikani" - "Kamera haipatikani" - "Kurekodi video hakupatikani" - "Haiwezi kuhifadhi maudhui ya picha au video" - "Haiwezi kupiga picha" - "Nyuma" - "Imewekwa kwenye kumbukumbu" - "Futa" - "Kumbukumbu" - "Ondoa kwenye kumbukumbu" - "Zima arifa" - "Washa arifa" - "Ongeza anwani" - "Pakua" - "Tuma" - "Futa" - "Je, ungependa kufuta ujumbe huu?" - "Kitendo hiki hakiwezi kutenduliwa." - "Futa" - - Ungependa kufuta mazungumzo haya? - Ungependa kufuta mazungumzo haya? - - "Futa" - "Ghairi" - "Kwa" - "Chagua picha nyingi" - "Thibitisha uteuzi" - "%d+" - "Haiwezi kurekodi sauti. Jaribu tena." - "Haiwezi kucheza sauti. Jaribu tena." - "Haikuweza kuhifadhi sauti. Jaribu tena." - "Gusa na ushikilie" - ", " - " " - ": " - " " - "Picha" - "Klipu ya sauti" - "Video" - "Kadi ya anwani" - "Pakua" - "Jibu kupitia SMS" - "Jibu kupitia MMS" - "Jibu" - - Washiriki %d - Mshiriki %d - - "Mimi" - "Anwani imezuiwa na imewekwa kwenye kumbukumbu" - "Anwani imeacha kuzuiliwa na imetolewa kwenye kumbukumbu" - "%d yamewekwa kwenye kumbukumbu" - "%d yameondolewa kwenye kumbukumbu" - "Arifa zimezimwa" - "Arifa zimewashwa" - "Imewekwa. Gusa Tuma tena." - "Umefaulu kuweka Programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS." - - Tupa viambatisho - Tupa kiambatisho - - "Kiambatisho cha sauti" - "Cheza kiambatisho cha sauti" - "Sitisha" - "Ujumbe kutoka kwa %s: %s." - "Ujumbe ulioshindikana kutoka kwa %s: %s. Saa: %s." - "Ujumbe kutoka %s: %s. Saa: %s." - "Ujumbe ambao haukutumwa kwa %s: %s. Saa: %s." - "Inatuma ujumbe kwa %s: %s. Saa: %s." - "Ujumbe ulioshindikana kwa %s: %s. Saa: %s." - "Ujumbe kwa %s: %s. Saa: %s." - "Ujumbe ulioshindikana kutoka kwa %s: %s. Saa: %s. %s." - "Ujumbe kutoka %s: %s. Saa: %s. %s." - "Ujumbe ambao haukutumwa kwa %s: %s. Saa: %s." - "Inatuma ujumbe kwa %s: %s. Saa: %s." - "Ujumbe ulioshindikana kwenda kwa %s: %s. Saa: %s." - "Ujumbe kwa %s: %s. Saa: %s." - "Ujumbe ulishindikana. Gusa ili ujaribu tena." - "Mazungumzo na %s" - "Futa kichwa" - "Rekodi video" - "Piga picha ya mnato" - "Piga picha" - "Anza kurekodi video" - "Badili utumie kamera ya skrini nzima" - "Badili kati ya kamera ya nyuma na mbele" - "Acha kurekodi na kuambatisha video" - "Acha kurekodi video" - "Picha za programu ya Ujumbe" - - Picha %d zimehifadhiwa kwenye albamu ya \"%s\" - Picha %d imehifadhiwa kwenye albamu ya \"%s\" - - - Video %d zimehifadhiwa kwenye albamu ya \"%s\" - Video %d imehifadhiwa kwenye albamu ya \"%s\" - - - Viambatisho %d vimehifadhiwa kwenye albamu ya \"%s\" - Kiambatisho %d kimehifadhiwa kwenye albamu ya \"%s\" - - - Viambatisho %d vimehifadhiwa kwenye \"Vipakuliwa\" - Kiambatisho %d kimehifadhiwa kwenye \"Vipakuliwa\" - - - Imehifadhi viambatisho %d - Imehifadhi kiambatisho %d - - - Haikuweza kuhifadhi viambatisho %d - Haikuweza kuhifadhi kiambatisho %d - - "Kiambatisho cha MMS kimehifadhiwa" - "Mipangilio" - "Imewekwa kwenye kumbukumbu" - "Funga" - "MMS" - "Mahiri" - "Tatua" - "Arifa" - "Mlio" - "Kimya" - "Tetema" - "Imezuiwa" - "Ripoti za kupokelewa kwa SMS" - "Omba ripoti ya kuthibitisha kuwa kila SMS unayotuma imewasilishwa" - "Pakua kiotomatiki" - "Rejesha ujumbe wa MMS kiotomatiki" - "Rejesha kiotomatiki wakati wa kutumia mitandao mingine" - "Rejesha MMS kiotomatiki unapotumia mitandao mingine" - "Utumaji ujumbe kwa kikundi" - "Tumia MMS kutuma ujumbe mmoja wakati kuna wapokeaji wengi" - "Programu chaguo-msingi ya SMS" - "Programu chaguo-msingi ya SMS" - - - "Nambari yako ya simu" - "Isiyojulikana" - "Sauti za ujumbe unaotumwa" - "Tupa SMS" - "Tupa data ghafi za SMS zilizopokelewa kwenye faili ya hifadhi ya nje" - "Tupa ujumbe wa MMS" - "Tupa data ghafi za ujumbe wa MMS uliopokelewa kwenye faili ya hifadhi ya nje" - "Arifa ya dharura" - "Chaguo za ujumbe" - "Nakili maandishi" - "Tazama maelezo" - "Futa" - "Sambaza" - "Maelezo ya ujumbe" - "Aina: " - "Ujumbe wa maandishi" - "Ujumbe anuai" - "Kutoka: " - "Kwa: " - "Imetumwa: " - "Imepokelewa: " - "Kichwa: " - "Ukubwa: " - "Kipaumbele: " - "SIM: " - "Juu" - "Kawaida" - "Chini" - "SIM %s" - "Anwani ya mtumaji iliyofichwa" - "Haiwezi kutuma ujumbe wakati inapakiwa viambatisho." - "Haiwezi kupakia kiambatisho. Jaribu tena." - "Mtandao hauko tayari. Jaribu tena." - "Futa ujumbe" - "Badili kati ya kuweka maandishi na nambari" - "Ongeza washiriki zaidi" - "Thibitisha washiriki" - "Anzisha mazungumzo mapya" - "Chagua bidhaa hii" - "Cheza video" - "Watu na chaguo" - "Tatua" - "Watu na chaguo" - "Jumla" - "Watu katika mazungumzo haya" - "Piga simu" - "Tuma ujumbe" - "Tuma ujumbe<br/><small>kutoka %s</small>" - - Tuma picha - Tuma picha - - - Tuma sauti - Tuma sauti - - - Tuma video - Tuma video - - - Tuma kadi za anwani - Tuma kadi ya anwani - - - Tuma viambatisho - Tuma kiambatisho - - - Viambatisho %d viko tayari kutumwa - Kiambatisho kimoja kiko tayari kutumwa - - "Tuma maoni" - "Angalia katika Duka la Google Play" - "Maelezo kuhusu toleo hili" - "Toleo la %1$s" - "Leseni za programu huria" - "Arifa" - "Kikomo cha kiambatisho kilifikiwa" - "Imeshindwa kupakia kiambatisho." - "Ungependa kuongeza kwenye Anwani?" - "Ongeza Anwani" - "Kichwa" - "Kichwa: " - "%s%s" - "Inapakia kadi ya anwani" - "Haikuweza kupakia kadi ya anwani" - "Angalia kadi ya anwani" - - Anwani %d - Anwani %d - - "Kadi za mawasiliano" - "Siku ya kuzaliwa" - "Madokezo" - "Sambaza ujumbe" - "Jibu" - "+1" - "+%d" - "SMS imezimwa" - "Ili utume ujumbe, weka programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS" - "Weka programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS" - "Badilisha" - "Ili kupokea ujumbe, weka programu ya Ujumbe iwe programu ya chaguo-msingi ya SMS" - "Hakuna SIM inayopendelewa kwa kutuma ujumbe wa SMS iliyochaguliwa" - "Programu hii hairuhusiwi na mmiliki kifaa." - "SAWA" - "Washiriki wengi mno katika mazungumzo" - - Anwani zisizo sahihi - Anwani isiyo sahihi - - "Haikuweza kupakia picha ya kamera" - "Wewe: " - "%s: " - "Rasimu" - "Utakapoanzisha mazungumzo mapya, utayaona yameorodheshwa hapa" - "Mazungumzo yaliyowekwa kwenye kumbukumbu huonekana hapa" - "Inapakia mazungumzo..." - "Picha" - "Klipu ya sauti" - "Video" - "Kadi ya anwani" - "MMS" - "Tendua" - "Jaribu tena" - "Andika jina la anwani au nambari ya simu ili uanze ujumbe mpya" - "Zuia" - "Zuia %s" - "Ondoa kizuizi kwa %s" - "Ungependa kuzuia %s?" - "Utaendelea kupokea ujumbe kutoka nambari hii ila hutapewa arifa tena. Mazungumzo haya yatawekwa kwenye kumbukumbu." - "Anwani zilizozuiwa" - "ONDOA KIZUIZI" - "Anwani zilizozuiwa" - "Chagua picha kutoka maktaba ya hati" - "Inatuma ujumbe" - "Ujumbe umetumwa" - "Data ya simu za mkononi imezimwa. Angalia mipangilio yako." - "Haiwezi kutuma ujumbe katika Hali ya ndegeni" - "Ujumbe haukutumwa" - "Ujumbe umepakuliwa" - "Data ya simu za mkononi imezimwa. Angalia mipangilio yako." - "Haiwezi kupakua ujumbe katika Hali ya ndegeni" - "Ujumbe haukuweza kupakuliwa" - "Sifuri" - "Moja" - "Mbili" - "Tatu" - "Nne" - "Tano" - "Sita" - "Saba" - "Nane" - "Tisa" - "Huwezi kutuma ujumbe ukitumia %1$s, hitilafu %2$d" - "Huwezi kutuma ujumbe ukitumia mtoa huduma asiyejulikana, hitilafu %1$d" - "Fwd: %s" - "Ujumbe haukutumwa: huduma haijaanza kutumika kwenye mtandao." - "Ujumbe haujatumwa: anwani ya unakotuma si sahihi" - "Ujumbe haujatumwa: ujumbe usio sahihi" - "Ujumbe haujatumwa: maudhui yasiyoweza kutumika" - "Ujumbe haujatumwa: ujumbe usioweza kutumika" - "Ujumbe haujatumwa: ni mkubwa mno" - "Ujumbe mpya" - "Tazama" - "Picha" - "Haikuweza kupata programu inayofaa" - "Ondoa mpokeaji" - "Ujumbe mpya" - "Ghairi" - "Badilisha mahali pa kufikia" - "Haijawekwa" - "Jina" - "APN" - "MMSC" - "Proksi ya MMS" - "Mlango wa MMS" - "MCC" - "MNC" - "Aina ya APN" - "Futa APN" - "APN Mpya" - "Hifadhi" - "Tupa" - "Sehemu hii haiwezi kuachwa ikiwa haijajazwa." - "APN haiwezi kuwa tupu." - "Sehemu ya MCC lazima iwe na nambari 3." - "Sehemu ya MNC lazima iwe na nambari 2 au 3." - "Inarejesha mipangilio mbadala ya APN" - "Rudisha kwenye chaguo-msingi" - "Kuweka upya kwa mipangilio mbadala ya APN kumekamilika" - "Haina Kichwa" - "Majina ya Lango la Mtandao" - "APN" - "APN Mpya" - "Mipangilio ya Jina la Lango la Mtandao (APN) haipatikani kwa mtumiaji huyu" - "Je, ungependa kunakili kwenye ubao klipu?" - "Nakili" - "kwa %s" - "Jumla" - "Mahiri" - "Mipangilio ya jumla" - "Mipangilio ya kina" - "SIM \"%s\"" - "Tuma ujumbe binafsi wa SMS kwa wapokeaji wote. Wewe tu ndiye utakayepata majibu yoyote" - "Tuma MMS moja kwa wapokeaji wote" - "Nambari isiyojulikana" - "Ujumbe mpya" - "Ujumbe mpya." - "Kichagua SIM" - "%1$s imechaguliwa, Kichagua SIM" - "Badilisha kichwa" - "Chagua SIM au ubadilishe kichwa" - "Gusa na ushikilie ili urekodi sauti" - "Anzisha mazungumzo mapya" - "Ujumbe" - "Orodha ya Ujumbe" - "Ujumbe" - "Ujumbe mpya" - "Orodha ya mazungumzo" - "Inapakia mazungumzo" - "Inapakia ujumbe" - "Tazama mazungumzo zaidi" - "Angalia ujumbe zaidi" - "Mazungumzo yamefutwa" - "Mazungumzo yamefutwa. Gusa ili uonyeshe mazungumzo tofauti ya programu ya Ujumbe" - "Amezuiwa" - "Ameondolewa kizuizi" - "Nafasi ya hifadhi ni ndogo. Unaweza kupoteza baadhi ya data." - "Chagua viambatisho" - "Thibitisha uteuzi" - "%d vimechaguliwa" - "Tafadhali ondoa moja au zaidi ya viambatisho na ujaribu tena." - "Unaweza kujaribu kutuma ujumbe wako, lakini huenda usitumwe hadi uondoe kiambatisho kimoja au zaidi." - "Unaweza kutuma video moja pekee kwa kila ujumbe. Tafadhali ondoa video za ziada na ujaribu tena." - "Programu ya Ujumbe haikupakia kiambatisho." - "Tuma tu" - "Haikuweza kuanza mazungumzo" - "%1$s (%2$s)" - "%s imechaguliwa" - diff --git a/res/values-ta-rIN/arrays.xml b/res/values-ta-rIN/arrays.xml deleted file mode 100644 index 9953b60..0000000 --- a/res/values-ta-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "தலைப்பு இல்லை" - "தலைப்பு இல்லை" - - - "ஆம்" - "இல்லை" - "சரி" - "ஹிஹி" - "நன்றி" - "ஆம்" - "அருமை" - "வந்துகொண்டிருக்கிறேன்" - "பின்னர் அழைக்கிறேன்" - ":)" - ":(" - - diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml deleted file mode 100644 index e107ffe..0000000 --- a/res/values-ta-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "மெசேஜ்" - "மெசேஜ்" - "உரையாடலைத் தேர்ந்தெடுக்கவும்" - "அமைப்பு" - "செய்தி அனுப்பு" - "இணைப்பைச் சேர்" - "உதவி" - "நல்வரவு" - "தவிர்" - "அடுத்து >" - "அடுத்து" - "வெளியேறு" - "அமைப்புகள் >" - "அமைப்புகள்" - "செய்தியிடலுக்கு SMS, ஃபோன் மற்றும் தொடர்புகள் ஆகியவற்றுக்கான அனுமதி தேவை." - "அமைப்புகள் > பயன்பாடுகள் > செய்தியிடல் > அனுமதிகள் என்பதில் அனுமதிகளை மாற்றலாம்." - "அமைப்புகள், பயன்பாடுகள், செய்தியிடல், அனுமதிகள் என்பதில் அனுமதிகளை மாற்றலாம்." - "அடிக்கடி தொடர்புகொண்டவர்கள்" - "எல்லா தொடர்புகளும்" - "%s க்கு அனுப்பு" - "படங்கள் அல்லது வீடியோவை எடுக்கவும்" - "இந்தச் சாதனத்தில் இருந்து படங்களைத் தேர்வு செய்யவும்" - "ஆடியோவைப் பதிவுசெய்" - "படத்தைத் தேர்வுசெய்" - "மீடியா தேர்ந்தெடுக்கப்பட்டது." - "மீடியா தேர்வுநீக்கப்பட்டது." - "%d தேர்ந்தெடுக்கப்பட்டது" - "படம் %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "படம்" - "ஆடியோவைப் பதிவுசெய்" - "பகிர்" - "சற்றுமுன்" - "இப்போது" - - %d நிமிடங்கள் - %d நிமிடம் - - - %d மணிநேரம் - %d மணிநேரம் - - - %d நாட்கள் - %d நாள் - - - %d வாரங்கள் - ஒரு வாரம் - - - %d மாதங்கள் - ஒரு மாதம் - - - %d ஆண்டுகள் - ஒரு ஆண்டு - - "கிளாஸ் 0 செய்தி" - "சேமி" - "சாதனத்தில் இடம் குறைவாக உள்ளது. இடத்தைக் காலியாக்க, செய்தியிடலானது பழைய உரைச் செய்திகளை தானாகவே நீக்கும்." - "சேமிப்பிடம் குறைகிறது" - "உங்கள் சாதனத்தில் கூடுதல் இடம் கிடைக்கும் வரை செய்தியிடல் செய்திகளை அனுப்பாமல் அல்லது பெறாமல் இருக்கக் கூடும்." - "SMS சேமிப்பகம் குறைவாக உள்ளது. நீங்கள் செய்திகளை நீக்க வேண்டியிருக்கலாம்." - "உங்கள் ஃபோன் எண்ணை உறுதிசெய்யவும்" - "இந்த ஒரு முறை செயல்பாடானது, செய்தியிடல் உங்கள் குழுச் செய்திகளைச் சரியாக வழங்குவதை உறுதிப்படுத்தும்." - "ஃபோன் எண்" - "மீடியாவுடனான செய்திகளை நீக்கு" - "%s க்கு முந்தைய செய்திகளை நீக்கு" - "%s காலத்திற்கு முந்தைய செய்திகளைத் தானாகவே நீக்கு" - "புறக்கணி" - "மீடியாவுடனான எல்லா செய்திகளையும் நீக்க வேண்டுமா?" - "%s காலத்திற்கு முந்தைய செய்திகளை நீக்க வேண்டுமா?" - "%s காலத்திற்கு முந்தைய செய்திகளை நீக்கி, தானாக நீக்கும் அம்சத்தை இயக்கவா?" - "%s கூறியது" - "நீங்கள் கூறியது" - "%s இடமிருந்து செய்தி" - "செய்தி அனுப்பியுள்ளீர்கள்" - "அனுப்புகிறது…" - "அனுப்பவில்லை. மீண்டும் முயல, தொடவும்." - "அனுப்பவில்லை. மீண்டும் முயல்கிறது…" - "மீண்டும் அனுப்பு அல்லது நீக்கு" - "அவசரகால சேவைகளை அழைக்கவும். உங்கள் உரைச் செய்தியை தற்போது வழங்க முடியவில்லை." - "தோல்வி" - "இறக்க, புதிய MMS செய்தி உள்ளது" - "புதிய MMS செய்தி" - "இறக்க முடியவில்லை" - "மீண்டும் முயல, தொடவும்" - "இறக்க, தொடவும்" - "இறக்கு அல்லது நீக்கு" - "இறக்குகிறது…" - "செய்தி காலாவதியானது அல்லது இல்லை" - "அளவு: %1$s, காலாவதியாகும் தேதி: %2$s" - "அனுப்ப முடியவில்லை. பெறுநர் விவரம் தவறாக உள்ளது." - "நெட்வொர்க்கில் சேவை இயக்கப்படவில்லை" - "நெட்வொர்க் சிக்கல் காரணமாக அனுப்ப முடியவில்லை" - "செய்தி காலாவதியானது அல்லது இல்லை" - "(தலைப்பு இல்லை)" - "அறியப்படாத அனுப்புநர்" - "டெலிவரி செய்யப்பட்டது" - "%2$s இலிருந்து %1$s செய்தியைப் பதிவிறக்க முடியவில்லை." - "குறைந்த நினைவகம் காரணமாகத் தரவுத்தளச் செயல்பாட்டை முடிக்க முடியவில்லை" - "செய்தி அனுப்பப்படவில்லை" - "செய்தியிடலில் சில செய்திகள் அனுப்பப்படவில்லை" - - %d உரையாடல்களில் %d செய்திகள் உள்ளன - ஒரு உரையாடலில் %d செய்திகள் உள்ளன - - "செய்தி இறக்கப்படவில்லை" - "செய்தியிடலில் சில செய்திகள் இறக்கப்படவில்லை" - - %d உரையாடல்களில் %d செய்திகள் உள்ளன - ஒரு உரையாடலில் %d செய்திகள் உள்ளன - - "%1$sக்குச் செய்தி அனுப்பப்படவில்லை" - "அவசரகால சேவைகளை அழைக்கவும். %1$s_1க்கு நீங்கள் அனுப்பிய செய்தியை தற்போது வழங்க முடியவில்லை." - - %d புதிய செய்திகள் - புதிய செய்தி - - "தொடங்கு" - "கேமரா இல்லை" - "கேமரா இல்லை" - "வீடியோ படமெடுக்க முடியாது" - "மீடியாவைச் சேமிக்க முடியவில்லை" - "படமெடுக்க முடியாது" - "முந்தையது" - "காப்பகப்படுத்தப்பட்டன" - "நீக்கு" - "காப்பிடு" - "காப்பிட வேண்டாம்" - "அறிவிப்புகளை முடக்கு" - "அறிவிப்புகளை இயக்கு" - "தொடர்பைச் சேர்" - "இறக்கு" - "அனுப்பு" - "நீக்கு" - "இந்தச் செய்தியை நீக்கவா?" - "இந்தச் செயலை பின்னர் செயல்தவிர்க்க முடியாது." - "நீக்கு" - - உரையாடல்களை நீக்கவா? - உரையாடலை நீக்கவா? - - "நீக்கு" - "ரத்துசெய்" - "பெறுநர்" - "பல படங்களைத் தேர்ந்தெடு" - "தேர்வை உறுதிப்படுத்து" - "+%d" - "ஆடியோவைப் பதிவு செய்ய இயலவில்லை. மீண்டும் முயலவும்." - "ஆடியோவை இயக்க முடியவில்லை. மீண்டும் முயலவும்." - "ஆடியோவைச் சேமிக்க முடியவில்லை. மீண்டும் முயலவும்." - "தொட்டுப் பிடித்திருக்கவும்" - ", " - " " - ": " - " " - "படம்" - "ஆடியோ கிளிப்" - "வீடியோ" - "தொடர்பு கார்டு" - "இறக்கு" - "SMS மூலம் பதிலளி" - "MMS மூலம் பதிலளி" - "பதிலளி" - - %d பங்கேற்பாளர்கள் - %d பங்கேற்பாளர் - - "எனக்கு" - "தொடர்பு தடுக்கப்பட்டு காப்பகப்படுத்தப்பட்டது" - "தொடர்பு தடைநீக்கப்பட்டுள்ளது & பட்டியலில் தோன்றும்" - "%d காப்பகப்படுத்தப்பட்டன" - "%d பட்டியலில் தோன்றும்" - "அறிவிப்புகள் முடக்கப்பட்டுள்ளன" - "அறிவிப்புகள் இயக்கப்பட்டுள்ளன" - "எல்லாம் முடிந்தது. மீண்டும் அனுப்பு என்பதைத் தொடவும்." - "செய்தியிடல் இயல்பு SMS பயன்பாடாக அமைக்கப்பட்டது." - - இணைப்புகளை நிராகரி - இணைப்பை நிராகரி - - "ஆடியோ இணைப்பு" - "ஆடியோ இணைப்பை இயக்கவும்" - "இடைநிறுத்து" - "செய்தி அனுப்பியவர்: %s - %s." - "%sக்கு அனுப்பிய செய்தி தோல்வி: %s. நேரம்: %s." - "%s அனுப்பிய செய்தி: %s. நேரம்: %s." - "%sக்கு அனுப்பப்படாத செய்தி: %s. நேரம்: %s." - "%sக்குச் செய்தி அனுப்புகிறது: %s. நேரம்: %s." - "%sக்கு அனுப்பிய செய்தி தோல்வி: %s. நேரம்: %s." - "%sக்கான செய்தி: %s. நேரம்: %s." - "%sக்கு அனுப்பிய செய்தி தோல்வி: %s. நேரம்: %s. %s." - "%s இடமிருந்து செய்தி: %s. நேரம்: %s. %s." - "%sக்கு அனுப்பப்படாத செய்தி: %s. நேரம்: %s." - "%sக்குச் செய்தி அனுப்புகிறது: %s. நேரம்: %s." - "%sக்கு அனுப்பிய செய்தி தோல்வி: %s. நேரம்: %s." - "%sக்கான செய்தி: %s. நேரம்: %s." - "செய்தி அனுப்புவதில் தோல்வி. மீண்டும் முயற்சிக்க, தொடவும்." - "%s உடனான உரையாடல்" - "தலைப்பை நீக்கு" - "வீடியோ எடு" - "ஸ்டில் படமெடு" - "படமெடு" - "வீடியோவைப் படமெடு" - "முழுத் திரை கேமராவிற்கு மாறு" - "முன் மற்றும் பின்பக்க கேமராவிற்கு இடையே மாறு" - "பதிவுசெய்வதை நிறுத்தி வீடியோவை இணைக்கவும்" - "வீடியோ பதிவு செய்வதை நிறுத்து" - "செய்தியிடல் படங்கள்" - - %d படங்கள், \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டன - %d படம், \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டது - - - %d வீடியோக்கள், \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டன - %d வீடியோ, \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டது - - - %d இணைப்புகள், \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டன - %d இணைப்பு, \"%s\" ஆல்பத்தில் சேமிக்கப்பட்டது - - - %d இணைப்புகள், \"இறக்கங்கள்\" என்பதில் சேமிக்கப்பட்டன - %d இணைப்பு, \"இறக்கங்கள்\" என்பதில் சேமிக்கப்பட்டது - - - %d இணைப்புகள் சேமிக்கப்பட்டன - %d இணைப்பு சேமிக்கப்பட்டது - - - %d இணைப்புகளைச் சேமிக்க முடியவில்லை - %d இணைப்பைச் சேமிக்க முடியவில்லை - - "சேமித்த MMS இணைப்பு" - "அமைப்பு" - "காப்பகப்படுத்தப்பட்டன" - "மூடு" - "MMS" - "மேம்பட்டவை" - "பிழைத்திருத்து" - "அறிவிப்புகள்" - "ஒலி" - "நிசப்தம்" - "அதிர்வு" - "தடுக்கப்பட்டது" - "SMS டெலிவரி அறிக்கைகள்" - "நீங்கள் அனுப்பும் ஒவ்வொரு SMS க்கும் வழங்கல் அறிக்கையைக் கோரவும்" - "தானாக மீட்டெடு" - "தானாக MMS செய்திகளை மீட்டெடு" - "ரோமிங்கில் தானாக மீட்டெடு" - "ரோமிங்கின் போது தானாகவே MMSஐ மீட்டெடு" - "குழு மெசேஜ்" - "ஒரு செய்தியைப் பல பெறுநர்களுக்கு அனுப்புவதற்கு MMS ஐப் பயன்படுத்து" - "இயல்புநிலை SMS பயன்பாடு" - "இயல்புநிலை SMS பயன்பாடு" - - - "மொபைல் எண்" - "தெரியாதது" - "வெளிச்செல்லும் செய்தி ஒலிகள்" - "SMS ஐ நகலெடு" - "பெற்ற SMS அசல் தரவை வெளிப்புறச் சேமிப்பகக் கோப்பில் நகலெடு" - "MMS ஐ நகலெடு" - "பெற்ற MMS அசல் தரவை வெளிப்புறச் சேமிப்பகச் கோப்பில் நகலெடு" - "வயர்லெஸ் விழிப்பூட்டல்கள்" - "செய்தி விருப்பங்கள்" - "உரையை நகலெடு" - "விவரங்களைக் காட்டு" - "நீக்கு" - "முன்னனுப்பு" - "செய்தி விவரங்கள்" - "வகை: " - "உரைச் செய்தி" - "மல்டிமீடியா செய்தி" - "அனுப்புநர்: " - "பெறுநர்: " - "அனுப்பியது: " - "பெறப்பட்டது: " - "தலைப்பு: " - "அளவு: " - "முன்னுரிமை: " - "SIM: " - "உயர்தரம்" - "இயல்பு" - "குறைந்த தரம்" - "SIM %s" - "மறைவிலிருக்கும் அனுப்புநர் முகவரி" - "இணைப்புகளை ஏற்றும் போது செய்தியை அனுப்ப முடியாது." - "இணைப்பை ஏற்ற முடியவில்லை. மீண்டும் முயலவும்." - "நெட்வொர்க் தயாராக இல்லை. மீண்டும் முயற்சிக்கவும்." - "உரையை நீக்கு" - "உரை மற்றும் எண்களை உள்ளிடுவதன் இடையே மாறு" - "மேலும் பங்கேற்பாளர்களைச் சேர்" - "பங்கேற்பாளர்களை உறுதிப்படுத்தவும்" - "புதிய உரையாடலைத் தொடங்கு" - "இதனைத் தேர்வுசெய்" - "வீடியோவை இயக்கு" - "நபர்களும் விருப்பங்களும்" - "பிழைத்திருத்து" - "நபர்களும் விருப்பங்களும்" - "பொது" - "உரையாடலில் உள்ளவர்கள்" - "அழை" - "செய்தி அனுப்பு" - "செய்தி அனுப்ப:<br/><small>%s</small>" - - படங்களை அனுப்பு - படத்தை அனுப்பு - - - ஆடியோக்களை அனுப்பு - ஆடியோவை அனுப்பு - - - வீடியோக்களை அனுப்பு - வீடியோவை அனுப்பு - - - தொடர்பு கார்டுகளை அனுப்பு - தொடர்பு கார்டை அனுப்பு - - - இணைப்புகளை அனுப்பு - இணைப்பை அனுப்பு - - - %d இணைப்புகள் அனுப்புவதற்குத் தயாராக உள்ளன - ஒரு இணைப்பு அனுப்புவதற்குத் தயாராக உள்ளது - - "கருத்தை அனுப்பு" - "Google Play Store இல் காட்டு" - "பதிப்புத் தகவல்" - "பதிப்பு %1$s" - "ஓப்பன் சோர்ஸ் உரிமங்கள்" - "அறிவிப்புகள்" - "இணைப்பிற்கான வரம்பை அடைந்துவிட்டீர்கள்" - "இணைப்பை ஏற்றுவதில் தோல்வி." - "தொடர்புகளில் சேர்க்கவா?" - "தொடர்பைச் சேர்" - "தலைப்பு" - "தலைப்பு: " - "%s%s" - "தொடர்பு கார்டை ஏற்றுகிறது" - "தொடர்பு கார்டை ஏற்ற முடியவில்லை" - "தொடர்பு கார்டைக் காட்டு" - - %d தொடர்புகள் - %d தொடர்பு - - "தொடர்பு கார்டுகள்" - "பிறந்தநாள்" - "குறிப்புகள்" - "செய்தியை முன்னனுப்பு" - "பதிலளிக்கவும்" - "+1" - "+%d" - "SMS முடக்கப்பட்டது" - "அனுப்ப, இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்" - "இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்" - "மாற்று" - "செய்திகளைப் பெற, இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்" - "SMS செய்திகளை அனுப்புவதற்கு விருப்பமான சிம் எதுவும் தேர்ந்தெடுக்கப்படவில்லை" - "சாதன உரிமையாளர் இந்தப் பயன்பாட்டை அனுமதிக்கவில்லை." - "சரி" - "உரையாடலில் பலர் உள்ளனர்" - - தவறான தொடர்புகள் - தவறான தொடர்பு - - "கேமரா படத்தை ஏற்ற முடியவில்லை" - "நீங்கள்: " - "%s: " - "வரைவு" - "புதிய உரையாடலைத் தொடங்கியதும், அது இங்கு பட்டியலிடப்படும்" - "காப்பகப்படுத்திய உரையாடல்கள் இங்கே தோன்றும்" - "உரையாடல்களை ஏற்றுகிறது…" - "படம்" - "ஆடியோ கிளிப்" - "வீடியோ" - "தொடர்பு கார்டு" - "MMS" - "செயல்தவிர்" - "மீண்டும் முயற்சிக்கவும்" - "புதிய செய்தி உருவாக்க, தொடர்பின் பெயர் அல்லது மொபைல் எண்ணை உள்ளிடவும்" - "தடு" - "%sஐத் தடு" - "%s இன் தடுப்பை நீக்கு" - "%sஐத் தடைசெய்யவா?" - "தொடர்ந்து இந்த எண்ணில் இருந்து செய்திகளைப் பெறுவீர்கள், அது குறித்து இனி உங்களுக்கு அறிவிக்கப்படாது. உரையாடல் காப்பகப்படுத்தப்படும்." - "தடுக்கப்பட்ட தொடர்புகள்" - "தடுப்பை நீக்கு" - "தடுக்கப்பட்ட தொடர்புகள்" - "ஆவண நூலகத்தில் இருந்து படத்தைத் தேர்வு செய்யவும்" - "செய்தி அனுப்பப்படுகிறது" - "செய்தி அனுப்பப்பட்டது" - "செல்லுலார் தரவு முடக்கப்பட்டுள்ளது. அமைப்புகளைச் சரிபார்க்கவும்." - "விமானப் பயன்முறையில் செய்திகளை அனுப்ப முடியாது" - "செய்தியை அனுப்ப முடியவில்லை" - "செய்தி பதிவிறக்கப்பட்டது" - "செல்லுலார் தரவு முடக்கப்பட்டுள்ளது. அமைப்புகளைச் சரிபார்க்கவும்." - "விமானப் பயன்முறையில் செய்திகளை இறக்க முடியாது" - "செய்தி இறக்கப்படவில்லை" - "பூஜ்ஜியம்" - "ஒன்று" - "இரண்டு" - "மூன்று" - "நான்கு" - "ஐந்து" - "ஆறு" - "ஏழு" - "எட்டு" - "ஒன்பது" - "%1$s மூலம் செய்தியை அனுப்ப முடியவில்லை, பிழை %2$d" - "அறியப்படாத மொபைல் நிறுவனம் மூலம் செய்தியை அனுப்ப முடியவில்லை, பிழை %1$d" - "அனுப்பு: %s" - "செய்தி அனுப்பப்படவில்லை: நெட்வொர்க்கில் சேவை செயல்படுத்தப்படவில்லை" - "செய்தி அனுப்பப்படவில்லை: தவறான இலக்கு முகவரி" - "செய்தி அனுப்பப்படவில்லை: தவறான செய்தி" - "செய்தி அனுப்பப்படவில்லை: ஆதரிக்கப்படாத உள்ளடக்கம்" - "செய்தி அனுப்பப்படவில்லை: ஆதரிக்கப்படாத செய்தி" - "செய்தி அனுப்பப்படவில்லை: மிகப் பெரியது" - "புதிய செய்தி" - "காட்டு" - "படம்" - "தகுந்த பயன்பாட்டைக் கண்டுபிடிக்க இயலவில்லை" - "பெறுநரை அகற்று" - "புதிய செய்தி" - "ரத்துசெய்" - "ஆக்சஸ் பாயிண்ட்டைத் திருத்து" - "அமைக்கப்படவில்லை" - "பெயர்" - "APN" - "MMSC" - "MMS ப்ராக்ஸி" - "MMS போர்ட்" - "MCC" - "MNC" - "APN வகை" - "APNஐ நீக்கு" - "புதிய APN" - "சேமி" - "நிராகரி" - "பெயர் புலம் வெறுமையாக இருக்கக்கூடாது." - "APN வெறுமையாக இருக்கக்கூடாது." - "MCC புலம் கண்டிப்பாக 3 இலக்கங்களில் இருக்க வேண்டும்." - "MNC புலம் கண்டிப்பாக 2 அல்லது 3 இலக்கங்களில் இருக்க வேண்டும்." - "இயல்புநிலை APN அமைப்புகளை மீட்டமைக்கிறது." - "இயல்புநிலைக்கு மீட்டமை" - "இயல்புநிலை APN அமைப்புகளை மீட்டமைப்பது முடிந்தது." - "பெயரிடப்படாதது" - "ஆக்சஸ் பாயிண்ட் நேம்கள்" - "APNகள்" - "புதிய APN" - "இவரால் ஆக்சஸ் பாயிண்ட் நேம் அமைப்புகளை மாற்ற முடியாது" - "கிளிப்போர்டுக்கு நகலெடுக்கவா?" - "நகலெடு" - "%s" - "பொது" - "மேம்பட்டவை" - "பொது அமைப்பு" - "மேம்பட்ட அமைப்பு" - "\"%s\" சிம்" - "எல்லா பெறுநர்களுக்கும் SMS செய்திகளைத் தனித்தனியாக அனுப்பவும். நீங்கள் மட்டுமே பதில்களைப் பெறுவீர்கள்" - "எல்லா பெறுநர்களுக்கும் MMS செய்திகளைத் தனித்தனியாக அனுப்பவும்" - "தெரியாத எண்" - "புதிய செய்தி" - "புதிய செய்தி." - "சிம் தேர்வி" - "%1$s தேர்ந்தெடுக்கப்பட்டது, சிம் தேர்வி" - "தலைப்பைத் திருத்து" - "சிம்மைத் தேர்ந்தெடுக்கவும் அல்லது தலைப்பைத் திருத்தவும்" - "ஆடியோவைப் பதிவுசெய்ய, தொட்டுப் பிடித்திருக்கவும்" - "புதிய உரையாடலைத் தொடங்கு" - "செய்தியிடல்" - "செய்தியிடல் பட்டியல்" - "செய்தியிடல்" - "புதிய செய்தி" - "உரையாடல் பட்டியல்" - "உரையாடல்களை ஏற்றுகிறது" - "செய்திகளை ஏற்றுகிறது" - "மேலும் உரையாடல்களைக் காட்டு" - "கூடுதல் செய்திகளைக் காட்டு" - "உரையாடல் நீக்கப்பட்டது" - "உரையாடல் நீக்கப்பட்டது. வேறொரு செய்தியிடல் உரையாடலைக் காட்ட, தொடவும்" - "தடுக்கப்பட்டார்" - "தடுப்பு நீக்கப்பட்டார்" - "சேமிப்பகம் குறைவாக உள்ளது. சில தரவை இழக்க நேரிடலாம்." - "இணைப்புகளைத் தேர்ந்தெடு" - "தேர்வை உறுதிப்படுத்து" - "%d தேர்ந்தெடுக்கப்பட்டன" - "ஒன்று அல்லது அதற்கு மேற்பட்ட இணைப்புகளை அகற்றிவிட்டு, முயற்சிக்கவும்." - "நீங்கள் செய்தியை அனுப்ப முயற்சிக்கலாம், ஆனால் ஒன்று அல்லது அதற்கு மேற்பட்ட இணைப்புகளை நீக்கும் வரை இது வழங்கப்படாமல் இருக்கலாம்." - "ஒரு செய்தியில் ஒரு வீடியோவை மட்டுமே நீங்கள் அனுப்ப முடியும். கூடுதல் வீடியோக்களை அகற்றிவிட்டு, மீண்டும் முயற்சிக்கவும்." - "செய்தியிடலால் இணைப்பை ஏற்ற முடியவில்லை." - "எப்படியேனும் அனுப்பு" - "உரையாடலைத் தொடங்க முடியவில்லை" - "%1$s (%2$s)" - "%s தேர்ந்தெடுக்கப்பட்டது" - diff --git a/res/values-te-rIN/arrays.xml b/res/values-te-rIN/arrays.xml deleted file mode 100644 index c18a1b6..0000000 --- a/res/values-te-rIN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "విషయం లేదు" - "విషయం లేదు" - - - "అవును" - "వద్దు" - "సరే" - "హెహే" - "ధన్యవాదాలు" - "నేను అంగీకరిస్తున్నాను" - "బాగుంది" - "నేను ఆ పని మీదే ఉన్నాను" - "సరే, నేను మీతో తర్వాత మాట్లాడుతాను" - ":)" - ":(" - - diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml deleted file mode 100644 index 4a58e0f..0000000 --- a/res/values-te-rIN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "మెసేజింగ్" - "మెసేజింగ్" - "సంభాషణను ఎంచుకోండి" - "సెట్టింగ్‌లు" - "సందేశం పంపు" - "జోడింపును జోడించండి" - "సహాయం" - "స్వాగతం" - "దాటవేయి" - "తదుపరి >" - "తదుపరి" - "నిష్క్రమించండి" - "సెట్టింగ్‌లు >" - "సెట్టింగ్‌లు" - "సందేశ సేవకు SMS, ఫోన్ మరియు పరిచయాల ప్రాప్యత అనుమతి ఇవ్వడం అవసరం." - "మీరు సెట్టింగ్‌లు > అనువర్తనాలు > సందేశ సేవ > అనుమతులులో అనుమతులను మార్చవచ్చు." - "మీరు సెట్టింగ్‌లు, అనువర్తనాలు, సందేశ సేవ, అనుమతులులో అనుమతులను మార్చవచ్చు." - "తరచుగా సంప్రదించినవి" - "అన్ని పరిచయాలు" - "%sకి పంపండి" - "చిత్రాలను లేదా వీడియోను సంగ్రహించు" - "ఈ పరికరం నుండి చిత్రాలను ఎంచుకోండి" - "ఆడియోను రికార్డ్ చేయి" - "ఫోటోను ఎంచుకోండి" - "మీడియా ఎంచుకోబడింది." - "మీడియా ఎంపిక తీసివేయబడింది." - "%d ఎంచుకోబడింది" - "చిత్రం %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "చిత్రం" - "ఆడియోను రికార్డ్ చేయి" - "భాగస్వామ్యం చేయి" - "ఇప్పుడే" - "ఇప్పుడు" - - %d నిమి - %d నిమి - - - %d గంటలు - %d గంట - - - %d రోజులు - %d రోజు - - - %d వారాలు - ఒక వారం - - - %d నెలలు - ఒక నెల - - - %d సంవత్సరాలు - ఒక సంవత్సరం - - "తరగతి 0 సందేశం" - "సేవ్ చేయి" - "పరికరంలో ఖాళీ స్థలం తక్కువగా ఉంది. స్థలం ఖాళీ చేయడానికి సందేశ సేవ స్వయంచాలకంగా పాత సందేశాలను తొలగిస్తుంది." - "నిల్వ స్థలం అయిపోతోంది" - "మీ పరికరంలో మరింత ఖాళీ స్థలం లభించే వరకు సందేశ సేవ సందేశాలను పంపలేకపోవచ్చు లేదా స్వీకరించలేకపోవచ్చు." - "SMS నిల్వ తక్కువగా ఉంది. మీరు సందేశాలను తొలగించాల్సి రావచ్చు." - "మీ ఫోన్ నంబర్‌ను నిర్ధారించండి" - "ఈ ఒక పర్యాయ దశ సందేశ సేవ మీ సమూహ సందేశాలను సరిగ్గా బట్వాడా చేస్తుందని నిర్ధారిస్తుంది." - "ఫోన్ నంబర్" - "మీడియాలోని అన్ని సందేశాలను తొలగించండి" - "%s కంటే మునుపు ఉన్న సందేశాలను తొలగించండి" - "%s కంటే పాత సందేశాలను స్వయంచాలకంగా తొలగించు" - "విస్మరించు" - "మీడియా ఉన్న అన్ని సందేశాలను తొలగించాలా?" - "%s కంటే పాత సందేశాలను తొలగించాలా?" - "%s కంటే పాత సందేశాలను తొలగించి, స్వయంచాలక తొలగింపుని ఆన్ చేయాలా?" - "%s చెప్పారు" - "మీరు చెప్పారు" - "%s నుండి సందేశం" - "మీరు సందేశాన్ని పంపారు" - "పంపుతోంది..." - "పంపబడలేదు. మళ్లీ ప్రయత్నించడానికి తాకండి." - "పంపబడలేదు. మళ్లీ ప్రయత్నిస్తోంది..." - "మళ్లీ పంపండి లేదా తొలగించండి" - "దయచేసి అత్యవసర సేవలకు వాయిస్ కాల్ చేయండి. మీ వచన సందేశాన్ని ప్రస్తుతం బట్వాడా చేయడం సాధ్యపడలేదు." - "విఫలమైంది" - "డౌన్‌లోడ్ చేయడానికి కొత్త MMS సందేశం" - "కొత్త MMS సందేశం" - "డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు" - "మళ్లీ ప్రయత్నించడానికి తాకండి" - "డౌన్‌లోడ్ చేయడానికి తాకండి" - "డౌన్‌లోడ్ చేయండి లేదా తొలగించండి" - "డౌన్‌లోడ్ చేస్తోంది…" - "సందేశం గడువు ముగిసింది లేదా అందుబాటులో లేదు" - "పరిమాణం: %1$s, గడువు: %2$s" - "పంపడం సాధ్యపడదు. చెల్లని స్వీకర్త." - "నెట్‌వర్క్‌లో సేవ సక్రియం చేయబడలేదు" - "నెట్‌వర్క్ సమస్య కారణంగా పంపడం సాధ్యపడలేదు" - "సందేశం గడువు ముగిసింది లేదా అందుబాటులో లేదు" - "(విషయం లేదు)" - "పంపినవారు తెలియదు" - "బట్వాడా చేయబడింది" - "%2$s నుండి %1$s సందేశాన్ని డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు." - "మెమరీ తక్కువగా ఉన్న కారణంగా డేటాబేస్ నిర్వహణను పూర్తి చేయడం సాధ్యపడలేదు" - "సందేశం పంపబడలేదు" - "సందేశ సేవలో కొన్ని సందేశాలు పంపబడలేదు" - - %d సంభాషణల్లో %d సందేశాలు - ఒక సంభాషణలో %d సందేశాలు - - "సందేశం డౌన్‌లోడ్ కాలేదు" - "సందేశ సేవలో కొన్ని సందేశాలు డౌన్‌లోడ్ కాలేదు" - - %d సంభాషణల్లో %d సందేశాలు - ఒక సంభాషణలో %d సందేశాలు - - "%1$sకి సందేశం పంపబడలేదు" - "దయచేసి అత్యవసర సేవలకు వాయిస్ కాల్ చేయండి. మీరు %1$sకి పంపిన వచన సందేశాన్ని ప్రస్తుతం బట్వాడా చేయడం సాధ్యపడలేదు." - - %d కొత్త సందేశాలు - కొత్త సందేశం - - "ప్రారంభించు" - "కెమెరా అందుబాటులో లేదు" - "కెమెరా అందుబాటులో లేదు" - "వీడియో సంగ్రహణ అందుబాటులో లేదు" - "మీడియాను సేవ్ చేయడం సాధ్యపడదు" - "చిత్రం తీయడం సాధ్యపడలేదు" - "వెనుకకు" - "ఆర్కైవ్ చేయబడినవి" - "తొలగించు" - "ఆర్కైవ్ చేయి" - "ఆర్కైవ్ నుండి తీసివేయి" - "నోటిఫికేషన్‌లను ఆఫ్ చేయి" - "నోటిఫికేషన్‌లను ఆన్ చేయి" - "పరిచయాన్ని జోడించు" - "డౌన్‌లోడ్ చేయి" - "పంపు" - "తొలగించు" - "ఈ సందేశాన్ని తొలగించాలా?" - "ఈ చర్య రద్దు చేయబడదు." - "తొలగించు" - - ఈ సంభాషణలను తొలగించాలా? - ఈ సంభాషణను తొలగించాలా? - - "తొలగించు" - "రద్దు చేయి" - "వీరికి" - "బహుళ చిత్రాలను ఎంచుకోండి" - "ఎంపికను నిర్ధారించు" - "+%d" - "ఆడియోను రికార్డ్ చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి." - "ఆడియోను ప్లే చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి." - "ఆడియోను సేవ్ చేయడం సాధ్యపడలేదు. మళ్లీ ప్రయత్నించండి." - "తాకి ఉంచండి" - ", " - " " - ": " - " " - "చిత్రం" - "ఆడియో క్లిప్" - "వీడియో" - "పరిచయ కార్డు" - "డౌన్‌లోడ్ చేయి" - "SMS ద్వారా ప్రత్యుత్తరం ఇవ్వండి" - "MMSలో ప్రత్యు." - "ప్రత్యుత్తరం పంపండి" - - %d మంది పాల్గొన్నారు - %d పాల్గొన్నారు - - "నేను" - "పరిచయం బ్లాక్ చేయబడింది & ఆర్కైవ్ చేయబడింది" - "పరిచయం అన్‌బ్లాక్ చేయబడింది & అన్ఆర్కైవ్ చేయబడింది" - "%d ఆర్కైవ్ చేయబడింది/ఆర్కైవ్ చేయబడ్డాయి" - "%d ఆర్కైవ్ నుండి తీసివేయబడింది/ఆర్కైవ్ నుండి తీసివేయబడ్డాయి" - "నోటిఫికేషన్‌లు ఆఫ్ చేయబడ్డాయి" - "నోటిఫికేషన్‌లు ఆన్ చేయబడ్డాయి" - "అంతా సెట్ చేశారు. మళ్లీ పంపు తాకండి." - "సందేశ సేవ డిఫాల్ట్ SMS అనువర్తనంగా విజయవంతంగా సెట్ చేయబడింది." - - జోడింపులను విస్మరించండి - జోడింపును విస్మరించండి - - "ఆడియో జోడింపు" - "ఆడియో జోడింపుని ప్లే చేయండి" - "పాజ్ చేయి" - "%s నుండి సందేశం: %s." - "%s నుండి %s సందేశం పంపడంలో విఫలమైంది. సమయం: %s." - "%s నుండి %s సందేశం వచ్చింది. సమయం: %s." - "%sకి %s సందేశం పంపబడలేదు. సమయం: %s." - "%sకి %s సందేశం పంపబడుతోంది. సమయం: %s." - "%sకి %s సందేశం పంపడంలో విఫలమైంది. సమయం: %s." - "%sకి %s సందేశం పంపబడింది. సమయం: %s." - "%s నుండి %s సందేశం పంపడంలో విఫలమైంది. సమయం: %s. %s." - "%s నుండి %s సందేశం వచ్చింది. సమయం: %s. %s." - "%sకి %s సందేశం పంపబడలేదు. సమయం: %s." - "%sకి %s సందేశం పంపబడుతోంది. సమయం: %s." - "%sకి %s సందేశం పంపడంలో విఫలమైంది. సమయం: %s." - "%sకి %s సందేశం పంపబడింది. సమయం: %s." - "సందేశం పంపడం విఫలమైంది. మళ్లీ ప్రయత్నించడానికి తాకండి." - "%sతో సంభాషణ" - "విషయాన్ని తొలగించండి" - "వీడియోను క్యాప్చర్ చేయండి" - "నిలకడగా ఉన్న చిత్రాన్ని క్యాప్చర్ చేయండి" - "చిత్రం తీయండి" - "వీడియో రికార్డింగ్‌ను ప్రారంభించండి" - "కెమెరాను పూర్తి స్క్రీన్‌కి మార్చండి" - "ముందు మరియు వెనుక కెమెరా మోడ్‌ల మధ్య మారండి" - "రికార్డింగ్‌ను ఆపివేసి, వీడియోను చేర్చండి" - "వీడియో రికార్డ్ చేయడాన్ని ఆపివేయండి" - "సందేశ సేవ ఫోటోలు" - - %d ఫోటోలు \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి - %d ఫోటో \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడింది - - - %d వీడియోలు \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి - %d వీడియో \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడింది - - - %d జోడింపులు \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి - %d జోడింపు \"%s\" ఆల్బమ్‌కి సేవ్ చేయబడింది - - - %d జోడింపులు \"డౌన్‌లోడ్‌లు\"కి సేవ్ చేయబడ్డాయి - %d జోడింపు \"డౌన్‌లోడ్‌లు\"కి సేవ్ చేయబడింది - - - %d జోడింపులు సేవ్ చేయబడ్డాయి - %d జోడింపు సేవ్ చేయబడింది - - - %d జోడింపులను సేవ్ చేయలేకపోయింది - %d జోడింపును సేవ్ చేయలేకపోయింది - - "MMS జోడింపు సేవ్ చేయబడింది" - "సెట్టింగ్‌లు" - "ఆర్కైవ్ చేయబడింది" - "మూసివేయి" - "MMS" - "అధునాతనం" - "డీబగ్" - "నోటిఫికేషన్‌లు" - "ధ్వని" - "నిశ్శబ్దం" - "వైబ్రేట్" - "బ్లాక్ చేయబడింది" - "SMS బట్వాడా నివేదికలు" - "మీరు పంపే ప్రతి SMS కోసం బట్వాడా నివేదికను అభ్యర్థించండి" - "స్వయంచాలకంగా తిరిగి పొందడం" - "MMS సందేశాలను స్వయంచాలకంగా పునరుద్ధరించండి" - "రోమింగ్‌లో ఉన్నప్పుడు స్వీయ-పునరుద్ధరణ" - "రోమింగ్‌లో ఉన్నప్పుడు MMSని స్వయంచాలకంగా తిరిగి పొందుతుంది" - "సమూహ సందేశాలు" - "ఒక సందేశాన్ని బహుళ స్వీకర్తలకు పంపాల్సినప్పుడు MMSను ఉపయోగించండి" - "డిఫాల్ట్ SMS అనువర్తనం" - "డిఫాల్ట్ SMS అనువర్తనం" - - - "మీ ఫోన్ నంబర్" - "తెలియదు" - "అవుట్‌గోయింగ్ సందేశ ధ్వనులు" - "SMSను డంప్ చేయండి" - "స్వీకరించబడిన SMS ప్రాసెస్ చేయబడని డేటాను బాహ్య నిల్వ ఫైల్‌లోకి డంప్ చేయండి" - "MMSను డంప్ చేయండి" - "స్వీకరించబడిన MMS ప్రాసెస్ చేయబడని డేటాను బాహ్య నిల్వ ఫైల్‌లోకి డంప్ చేయండి" - "వైర్‌లెస్ హెచ్చరికలు" - "సందేశ ఎంపికలు" - "వచనాన్ని కాపీ చేయి" - "వివరాలను వీక్షించండి" - "తొలగించు" - "ఫార్వార్డ్ చేయండి" - "సందేశ వివరాలు" - "రకం: " - "వచన సందేశం" - "మల్టీమీడియా సందేశం" - "వీరి నుండి: " - "వీరికి: " - "పంపినది: " - "స్వీకరించినది: " - "విషయం: " - "పరిమాణం: " - "ప్రాధాన్యత: " - "SIM: " - "అధికం" - "సాధారణం" - "తక్కువ" - "SIM %s" - "లేఖరి చిరునామా దాచబడింది" - "జోడింపులు లోడ్ అవుతున్నప్పుడు సందేశాన్ని పంపలేరు." - "జోడింపుని లోడ్ చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి." - "నెట్‌వర్క్ సిద్ధంగా లేదు. మళ్లీ ప్రయత్నించండి." - "వచనాన్ని తొలగించు" - "వచనం మరియు సంఖ్యలను నమోదు చేయడానికి వాటిని అటుఇటు మార్చు" - "మరింతమంది పాల్గొనేవారిని జోడించు" - "పాల్గొనేవారిని నిర్ధారించండి" - "కొత్త సంభాషణను ప్రారంభించండి" - "ఈ అంశాన్ని ఎంచుకోండి" - "వీడియోను ప్లే చేయండి" - "వ్యక్తులు & ఎంపికలు" - "డీబగ్" - "వ్యక్తులు & ఎంపికలు" - "సాధారణం" - "ఈ సంభాషణలో పాల్గొన్న వ్యక్తులు" - "కాల్ చేయి" - "సందేశాన్ని పంపండి" - "సందేశాన్ని <small>%s నుండి</small><br/>పంపు" - - ఫోటోలను పంపండి - ఫోటోను పంపండి - - - ఆడియోలను పంపండి - ఆడియోను పంపండి - - - వీడియోలను పంపండి - వీడియోను పంపండి - - - పరిచయ కార్డ్‌లను పంపండి - పరిచయ కార్డ్‌ను పంపండి - - - జోడింపులను పంపండి - జోడింపుని పంపండి - - - %d జోడింపులు పంపడానికి సిద్ధంగా ఉన్నాయి - ఒక జోడింపు పంపడానికి సిద్ధంగా ఉంది - - "అభిప్రాయం పంపండి" - "Google Play Storeలో వీక్షించండి" - "సంస్కరణ సమాచారం" - "సంస్కరణ %1$s" - "ఓపెన్ సోర్స్ లైసెన్స్‌లు" - "నోటిఫికేషన్‌లు" - "జోడింపు పరిమితిని చేరుకున్నారు" - "జోడింపుని లోడ్ చేయడంలో విఫలమైంది." - "పరిచయాలకు జోడించాలా?" - "పరిచయాన్ని జోడించు" - "విషయం" - "విషయం: " - "%s%s" - "పరిచయ కార్డ్‌ను లోడ్ చేస్తోంది" - "పరిచయ కార్డ్‌ను లోడ్ చేయడం సాధ్యపడలేదు" - "పరిచయ కార్డ్‌ను వీక్షించండి" - - %d పరిచయాలు - %d పరిచయం - - "పరిచయ కార్డ్‌లు" - "పుట్టినరోజు" - "గమనికలు" - "సందేశాన్ని ఫార్వార్డ్ చేయండి" - "ప్రత్యుత్తరం" - "+1" - "+%d" - "SMS నిలిపివేయబడింది" - "పంపడానికి, సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి" - "సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి" - "మార్చు" - "సందేశాలు స్వీకరించడానికి, సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి" - "SMS సందేశాలు పంపడానికి ప్రాధాన్య SIM ఏదీ ఎంచుకోలేదు" - "ఈ అనువర్తనాన్ని పరికరం యజమాని అనుమతించలేదు." - "సరే" - "సంభాషణలో చాలా ఎక్కువ మంది పాల్గొన్నారు" - - పరిచయాలు చెల్లవు - పరిచయం చెల్లదు - - "కెమెరా చిత్రాన్ని లోడ్ చేయడం సాధ్యపడలేదు" - "మీరు: " - "%s: " - "చిత్తుప్రతి" - "మీరు కొత్త సంభాషణను ప్రారంభిస్తే, అది ఇక్కడ జాబితా కావడం మీకు కనిపిస్తుంది" - "ఆర్కైవ్ చేసిన సంభాషణలు ఇక్కడ కనిపిస్తాయి" - "సంభాషణలను లోడ్ చేస్తోంది…" - "చిత్రం" - "ఆడియో క్లిప్" - "వీడియో" - "పరిచయ కార్డ్" - "MMS" - "చర్య రద్దు చేయి" - "మళ్లీ ప్రయత్నించు" - "కొత్త సందేశాన్ని ప్రారంభించడానికి పరిచయం పేరు లేదా ఫోన్ నంబర్‌ని నమోదు చేయండి" - "బ్లాక్ చేయి" - "%sని బ్లాక్ చేయి" - "%sని అన్‌బ్లాక్ చేయి" - "%sని బ్లాక్ చేయాలా?" - "మీరు ఈ నంబర్ నుండి సందేశాలను స్వీకరించడం కొనసాగుతుంది కానీ ఇకపై వాటి గురించి తెలియజేయబడదు. ఈ సంభాషణ ఆర్కైవ్ చేయబడుతుంది." - "బ్లాక్ చేయబడిన పరిచయాలు" - "అన్‌బ్లాక్ చేయి" - "బ్లాక్ చేయబడిన పరిచయాలు" - "పత్రం లైబ్రరీ నుండి చిత్రాన్ని ఎంచుకోండి" - "సందేశం పంపబడుతోంది" - "సందేశం పంపబడింది" - "సెల్యులార్ డేటా ఆఫ్ చేయబడింది. మీ సెట్టింగ్‌లను తనిఖీ చేయండి." - "ఎయిర్‌ప్లైన్ మోడ్‌లో సందేశాలు పంపడం సాధ్యపడదు" - "సందేశం పంపడం సాధ్యపడలేదు" - "సందేశం డౌన్‌లోడ్ చేయబడింది" - "సెల్యులార్ డేటా ఆఫ్ చేయబడింది. మీ సెట్టింగ్‌లను తనిఖీ చేయండి." - "ఎయిర్‌ప్లైన్ మోడ్‌లో సందేశాలను డౌన్‌లోడ్ చేయడం సాధ్యపడదు" - "సందేశాన్ని డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు" - "సున్నా" - "ఒకటి" - "రెండు" - "మూడు" - "నాలుగు" - "ఐదు" - "ఆరు" - "ఏడు" - "ఎనిమిది" - "తొమ్మిది" - "%1$sతో సందేశం పంపలేరు, దోషం %2$d" - "తెలియని క్యారియర్‌తో సందేశం పంపలేరు, లోపం %1$d" - "ఫార్వా.: %s" - "సందేశం పంపబడలేదు: నెట్‌వర్క్‌లో సేవ సక్రియం కాలేదు" - "సందేశం పంపబడలేదు: గమ్యస్థాన చిరునామా చెల్లదు" - "సందేశం పంపబడలేదు: సందేశం చెల్లదు" - "సందేశం పంపబడలేదు: మద్దతు లేని కంటెంట్" - "సందేశం పంపబడలేదు: మద్దతు లేని సందేశం" - "సందేశం పంపబడలేదు: చాలా పెద్దది" - "కొత్త సందేశం" - "వీక్షించండి" - "చిత్రం" - "సముచిత అనువర్తనాన్ని కనుగొనడం సాధ్యపడలేదు" - "స్వీకర్తను తీసివేయండి" - "కొత్త సందేశం" - "రద్దు చేయి" - "ప్రాప్యత స్థానాన్ని సవరించు" - "సెట్ చేయలేదు" - "పేరు" - "APN" - "MMSC" - "MMS ప్రాక్సీ" - "MMS పోర్ట్" - "MCC" - "MNC" - "APN రకం" - "APNను తొలగించు" - "కొత్త APN" - "సేవ్ చేయి" - "విస్మరించు" - "పేరు ఫీల్డ్ ఖాళీగా ఉండకూడదు." - "APN ఖాళీగా ఉండకూడదు." - "MCC ఫీల్డ్‌లో తప్పనిసరిగా 3 అంకెలు ఉండాలి." - "MNC ఫీల్డ్‌లో తప్పనిసరిగా 2 లేదా 3 అంకెలు ఉండాలి." - "డిఫాల్ట్ APN సెట్టింగ్‌లను పునరుద్ధరిస్తోంది." - "డిఫాల్ట్‌కు రీసెట్ చేయి" - "డిఫాల్ట్ APN సెట్టింగ్‌లను రీసెట్ చేయడం పూర్తయింది." - "శీర్షిక లేదు" - "ప్రాప్యత స్థానం పేర్లు" - "APNలు" - "కొత్త APN" - "ప్రాప్యత స్థానం పేరు సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు" - "క్లిప్‌బోర్డ్‌కు కాపీ చేయాలా?" - "కాపీ చేయి" - "%sకి" - "సాధారణం" - "అధునాతనం" - "సాధారణ సెట్టింగ్‌లు" - "అధునాతన సెట్టింగ్‌లు" - "\"%s\" SIM" - "స్వీకర్తలందరికీ వేర్వేరుగా SMS సందేశాలను పంపండి. ఏవైనా ప్రత్యుత్తరాలను మీరు మాత్రమే పొందుతారు" - "స్వీకర్తలందరికీ ఒకే MMS పంపండి" - "తెలియని నంబర్" - "కొత్త సందేశం" - "కొత్త సందేశం." - "SIM ఎంపిక సాధనం" - "%1$s ఎంచుకోబడింది, SIM ఎంపిక" - "విషయాన్ని సవరించు" - "SIM ఎంచుకోండి లేదా విషయాన్ని సవరించండి" - "ఆడియోను రికార్డ్ చేయడానికి తాకి, అలాగే ఉంచండి" - "కొత్త సంభాషణను ప్రారంభించండి" - "సందేశ సేవ" - "సందేశ సేవ జాబితా" - "సందేశ సేవ" - "కొత్త సందేశం" - "సంభాషణ జాబితా" - "సంభాషణలను లోడ్ చేస్తోంది" - "సందేశాలు లోడ్ అవుతున్నాయి" - "మరిన్ని సంభాషణలను వీక్షించండి" - "మరిన్ని సందేశాలను వీక్షించండి" - "సంభాషణ తొలగించబడింది" - "సంభాషణ తొలగించబడింది. విభిన్న సందేశ సేవ సంభాషణను చూపడానికి తాకండి" - "బ్లాక్ చేయబడ్డారు" - "అన్‌బ్లాక్ చేయబడ్డారు" - "నిల్వ స్థలంలో ఖాళీ తక్కువగా ఉంది. కొంత డేటా కోల్పోవచ్చు." - "జోడింపులను ఎంచుకోండి" - "ఎంపికను నిర్ధారించు" - "%d ఎంచుకోబడింది/ఎంచుకోబడ్డాయి" - "దయచేసి ఒకటి లేదా అంతకంటే ఎక్కువ జోడింపులను తీసివేసి, మళ్లీ ప్రయత్నించండి." - "మీరు మీ సందేశం పంపడాన్ని ప్రయత్నించవచ్చు, కానీ ఒకటి లేదా అంతకంటే ఎక్కువ జోడింపులను తీసివేస్తే మినహా అది బట్వాడా చేయబడదు." - "మీరు ఒక సందేశానికి ఒక వీడియోను మాత్రమే పంపగలరు. దయచేసి అదనపు వీడియోలను తీసివేసి, ఆపై మళ్లీ ప్రయత్నించండి." - "సందేశ సేవ జోడింపును లోడ్ చేయడంలో విఫలమైంది." - "ఏదేమైనా పంపు" - "సంభాషణను ప్రారంభించలేరు" - "%1$s (%2$s)" - "%s ఎంచుకోబడింది" - diff --git a/res/values-th/arrays.xml b/res/values-th/arrays.xml deleted file mode 100644 index 749516e..0000000 --- a/res/values-th/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "ไม่มีเรื่อง" - "ไม่มีชื่อเรื่อง" - - - "ใช่" - "ไม่ใช่" - "ตกลง" - "อิอิ" - "ขอบคุณ" - "ฉันเห็นด้วย" - "เยี่ยมเลย" - "เดินทางอยู่" - "ตกลง ฉันจะติดต่อกลับมาในภายหลัง" - ":)" - ":(" - - diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml deleted file mode 100644 index d191be9..0000000 --- a/res/values-th/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "การรับส่งข้อความ" - "การรับส่งข้อความ" - "เลือกการสนทนา" - "การตั้งค่า" - "ส่งข้อความ" - "เพิ่มไฟล์แนบ" - "ความช่วยเหลือ" - "ยินดีต้อนรับ" - "ข้าม" - "ถัดไป >" - "ถัดไป" - "ออก" - "การตั้งค่า >" - "การตั้งค่า" - "การรับส่งข้อความต้องใช้สิทธิ์เข้าถึง SMS, โทรศัพท์ และรายชื่อติดต่อ" - "คุณสามารถเปลี่ยนแปลงสิทธิ์ได้ในการตั้งค่า > แอป > การรับส่งข้อความ > สิทธิ์" - "คุณสามารถเปลี่ยนแปลงสิทธิ์ได้ในการตั้งค่า แอป การรับส่งข้อความ และสิทธิ์" - "ผู้ที่ติดต่อบ่อย" - "รายชื่อติดต่อทั้งหมด" - "ส่งไปยัง %s" - "จับภาพหรือวิดีโอ" - "เลือกรูปภาพจากอุปกรณ์นี้" - "บันทึกเสียง" - "เลือกรูปภาพ" - "เลือกสื่อแล้ว" - "ไม่มีการเลือกสื่อ" - "เลือกไว้ %d รายการ" - "ภาพเมื่อวันที่ %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "ภาพ" - "บันทึกเสียง" - "แชร์" - "เดี๋ยวนี้เอง" - "ตอนนี้" - - %d นาที - %d นาที - - - %d ชั่วโมง - %d ชั่วโมง - - - %d วัน - %d วัน - - - %d สัปดาห์ - 1 สัปดาห์ - - - %d เดือน - 1 เดือน - - - %d ปี - 1 ปี - - "ข้อความคลาส 0" - "บันทึก" - "อุปกรณ์มีพื้นที่เหลือน้อย การรับส่งข้อความจะลบข้อความเก่าโดยอัตโนมัติเพื่อเพิ่มพื้นที่ว่าง" - "พื้นที่จัดเก็บเหลือน้อย" - "การรับส่งข้อความอาจไม่ส่งหรือรับข้อความจนกว่าจะมีพื้นที่เพิ่มมากขึ้นบนอุปกรณ์" - "พื้นที่จัดเก็บ SMS เหลือน้อย คุณอาจต้องลบข้อความ" - "ยืนยันหมายเลขโทรศัพท์" - "ขั้นตอนที่ทำเพียงครั้งเดียวนี้จะช่วยให้การรับส่งข้อความส่งข้อความของกลุ่มได้อย่างถูกต้อง" - "หมายเลขโทรศัพท์" - "ลบข้อความทั้งหมดที่มีสื่อ" - "ลบข้อความที่เก่ากว่า %s" - "ลบข้อความที่เก่ากว่า %s โดยอัตโนมัติ" - "ไม่สนใจ" - "ลบข้อความทั้งหมดที่มีสื่อใช่ไหม" - "ลบข้อความที่เก่ากว่า %s ใช่ไหม" - "ลบข้อความที่เก่ากว่า %s และเปิดการลบอัตโนมัติใช่ไหม" - "%s กล่าว" - "คุณกล่าว" - "ข้อความจาก %s" - "คุณส่งข้อความแล้ว" - "กำลังส่ง…" - "ไม่สามารถส่ง แตะเพื่อลองอีกครั้ง" - "ไม่สามารถส่ง ลองอีกครั้ง…" - "ส่งอีกครั้งหรือลบ" - "โปรดโทรไปที่ฝ่ายบริการฉุกเฉิน ระบบไม่สามารถส่งข้อความของคุณได้ในขณะนี้" - "ล้มเหลว" - "มีข้อความ MMS ใหม่ให้ดาวน์โหลด" - "ข้อความ MMS ใหม่" - "ไม่สามารถดาวน์โหลดได้" - "แตะเพื่อลองอีกครั้ง" - "แตะเพื่อดาวน์โหลด" - "ดาวน์โหลดหรือลบ" - "กำลังดาวน์โหลด…" - "ข้อความหมดอายุหรือไม่สามารถใช้ได้" - "ขนาด: %1$s หมดอายุ: %2$s" - "ไม่สามารถส่งได้ ผู้รับไม่ถูกต้อง" - "ไม่ได้เปิดใช้บริการบนเครือข่าย" - "ไม่สามารถส่งได้เนื่องจากปัญหาของเครือข่าย" - "ข้อความหมดอายุหรือไม่สามารถใช้ได้" - "(ไม่มีเรื่อง)" - "ไม่ทราบผู้ส่ง" - "ส่งแล้ว" - "ไม่สามารถดาวน์โหลดข้อความ %1$s จาก %2$s" - "ไม่สามารถดำเนินการฐานข้อมูลให้เสร็จสิ้นเนื่องจากมีหน่วยความจำเหลือน้อย" - "ไม่ได้ส่งข้อความ" - "ไม่ได้ส่งข้อความบางรายการในการรับส่งข้อความ" - - %d ข้อความใน %d การสนทนา - %d ข้อความใน 1 การสนทนา - - "ข้อความไม่ได้รับการดาวน์โหลด" - "ไม่ได้ดาวน์โหลดข้อความบางรายการในการรับส่งข้อความ" - - %d ข้อความใน %d การสนทนา - %d ข้อความใน 1 การสนทนา - - "ไม่ได้ส่งข้อความถึง %1$s" - "โปรดโทรไปที่ฝ่ายบริการฉุกเฉิน ระบบไม่สามารถส่งข้อความของคุณไปยัง %1$s ได้ในขณะนี้" - - %d ข้อความใหม่ - ข้อความใหม่ - - "เริ่ม" - "กล้องไม่สามารถใช้ได้" - "กล้องไม่สามารถใช้ได้" - "การจับภาพวิดีโอไม่พร้อมใช้งาน" - "ไม่สามารถบันทึกสื่อได้" - "ไม่สามารถถ่ายภาพ" - "กลับ" - "เก็บถาวรแล้ว" - "ลบ" - "เก็บถาวร" - "ยกเลิกการเก็บถาวร" - "ปิดการแจ้งเตือน" - "เปิดการแจ้งเตือน" - "เพิ่มรายชื่อติดต่อ" - "ดาวน์โหลด" - "ส่ง" - "ลบ" - "ลบข้อความนี้ไหม" - "การดำเนินการนี้ไม่สามารถยกเลิกได้" - "ลบ" - - ลบการสนทนาเหล่านี้ใช่ไหม - ลบการสนทนานี้ใช่ไหม - - "ลบ" - "ยกเลิก" - "ถึง" - "เลือกภาพหลายภาพ" - "ยืนยันการเลือก" - "+%d" - "ไม่สามารถบันทึกเสียงได้ ลองอีกครั้ง" - "ไม่สามารถเล่นเสียงได้ ลองอีกครั้ง" - "ไม่สามารถบันทึกเสียงได้ ลองอีกครั้ง" - "แตะค้างไว้" - ", " - " " - ": " - " " - "ภาพ" - "คลิปเสียง" - "วิดีโอ" - "บัตรติดต่อ" - "ดาวน์โหลด" - "ตอบกลับทาง SMS" - "ตอบกลับผ่าน MMS" - "ตอบ" - - ผู้เข้าร่วม %d คน - ผู้เข้าร่วม %d คน - - "ฉัน" - "บล็อกและเก็บผู้ติดต่อแล้ว" - "เลิกบล็อกและเลิกเก็บรายชื่อติดต่อแล้ว" - "เก็บแล้ว %d รายการ" - "เลิกเก็บแล้ว %d รายการ" - "ปิดการแจ้งเตือนแล้ว" - "เปิดการแจ้งเตือนแล้ว" - "ตั้งค่าเรียบร้อย แตะ \"ส่ง\" อีกครั้ง" - "ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้นเรียบร้อยแล้ว" - - ยกเลิกไฟล์แนบ - ยกเลิกไฟล์แนบ - - "ไฟล์แนบเสียง" - "เล่นไฟล์เสียงที่แนบอยู่" - "หยุดชั่วคราว" - "ข้อความจาก %s: %s" - "ไม่สามารถรับข้อความจาก %s: %s เวลา: %s" - "ข้อความจาก %s: %s เวลา: %s" - "ข้อความที่ยังไม่ได้ส่งถึง %s: %s เวลา: %s" - "กำลังส่งข้อความถึง %s: %s เวลา: %s" - "ไม่สามารถส่งข้อความถึง %s: %s เวลา: %s" - "ส่งข้อความถึง %s: %s เวลา: %s" - "ไม่สามารถรับข้อความจาก %s: %s เวลา: %s %s" - "ข้อความจาก %s: %s เวลา: %s %s" - "ข้อความที่ยังไม่ได้ส่งถึง %s: %s เวลา: %s" - "กำลังส่งข้อความถึง %s: %s เวลา: %s" - "ไม่สามารถส่งข้อความถึง %s: %s เวลา: %s" - "ส่งข้อความถึง %s: %s เวลา: %s" - "ข้อความล้มเหลว แตะเพื่อลองใหม่" - "การสนทนากับ %s" - "ลบเรื่อง" - "จับภาพวิดีโอ" - "จับภาพนิ่ง" - "ถ่ายภาพ" - "เริ่มบันทึกวิดีโอ" - "สลับไปยังกล้องแบบเต็มหน้าจอ" - "สลับระหว่างกล้องหน้าและกล้องหลัง" - "หยุดการบันทึกและแนบวิดีโอ" - "หยุดการบันทึกวิดีโอ" - "รูปภาพในการรับส่งข้อความ" - - บันทึก %d ภาพไปยังอัลบั้ม \"%s\" แล้ว - บันทึก %d ภาพไปยังอัลบั้ม \"%s\" แล้ว - - - บันทึกวิดีโอ %d รายการไปยังอัลบั้ม \"%s\" แล้ว - บันทึกวิดีโอ %d รายการไปยังอัลบั้ม \"%s\" แล้ว - - - บันทึก %d ไฟล์แนบไปยังอัลบั้ม \"%s\" แล้ว - บันทึก %d ไฟล์แนบไปยังอัลบั้ม \"%s\" แล้ว - - - บันทึก %d ไฟล์แนบไปยัง \"ดาวน์โหลด\" แล้ว - บันทึก %d ไฟล์แนบไปยัง \"ดาวน์โหลด\" แล้ว - - - บันทึกไฟล์แนบ %d รายการแล้ว - บันทึกไฟล์แนบ %d รายการแล้ว - - - ไม่สามารถบันทึกไฟล์แนบ %d รายการ - ไม่สามารถบันทึกไฟล์แนบ %d รายการ - - "ไฟล์แนบของ MMS ที่บันทึกไว้" - "การตั้งค่า" - "เก็บถาวรแล้ว" - "ปิด" - "MMS" - "ขั้นสูง" - "แก้ไขข้อบกพร่อง" - "การแจ้งเตือน" - "เสียง" - "เงียบ" - "สั่น" - "ถูกบล็อก" - "รายงานการส่ง SMS" - "ขอรายงานการส่ง SMS แต่ละครั้ง" - "เรียกอัตโนมัติ" - "เรียกดูข้อความ MMS อัตโนมัติ" - "เรียกข้อความอัตโนมัติขณะโรมมิ่ง" - "ดึง MMS โดยอัตโนมัติเมื่อโรมมิ่ง" - "การรับส่งข้อความแบบกลุ่ม" - "ใช้ MMS ส่งข้อความเดียวเมื่อมีผู้รับหลายราย" - "แอป SMS เริ่มต้น" - "แอป SMS เริ่มต้น" - - - "หมายเลขโทรศัพท์ของคุณ" - "ไม่รู้จัก" - "เสียงข้อความขาออก" - "ถ่ายโอน SMS" - "ถ่ายโอนข้อมูลดิบของ SMS ที่ได้รับลงในไฟล์พื้นที่จัดเก็บข้อมูลภายนอก" - "ถ่ายโอน MMS" - "ถ่ายโอนข้อมูลดิบของ MMS ที่ได้รับลงในไฟล์พื้นที่จัดเก็บข้อมูลภายนอก" - "การแจ้งเตือนแบบไร้สาย" - "ตัวเลือกข้อความ" - "คัดลอกข้อความ" - "ดูรายละเอียด" - "ลบ" - "ส่งต่อ" - "รายละเอียดข้อความ" - "ประเภท: " - "ข้อความ" - "ข้อความมัลติมีเดีย" - "จาก: " - "ถึง: " - "ส่งแล้ว: " - "ได้รับ: " - "เรื่อง: " - "ขนาด: " - "ลำดับความสำคัญ: " - "ซิม: " - "สูง" - "ปกติ" - "ต่ำ" - "ซิม %s" - "ที่อยู่ผู้ส่งที่ซ่อนไว้" - "ไม่สามารถส่งข้อความเสียงขณะโหลดเอกสารแนบ" - "ไม่สามารถโหลดไฟล์แนบได้ ลองอีกครั้ง" - "เครือข่ายยังไม่พร้อม ลองอีกครั้ง" - "ลบข้อความ" - "สลับระหว่างการป้อนข้อความและตัวเลข" - "เพิ่มผู้เข้าร่วมเพิ่มเติม" - "ยืนยันผู้เข้าร่วม" - "เริ่มต้นการสนทนาใหม่" - "เลือกรายการนี้" - "เล่นวิดีโอ" - "ผู้คนและตัวเลือก" - "แก้ไขข้อบกพร่อง" - "ผู้คนและตัวเลือก" - "ทั่วไป" - "ผู้เข้าร่วมการสนทนานี้" - "โทรออก" - "ส่งข้อความ" - "ส่งข้อความ<br/><small>จาก %s</small>" - - ส่งรูปภาพ - ส่งรูปภาพ - - - ส่งไฟล์เสียง - ส่งไฟล์เสียง - - - ส่งวิดีโอ - ส่งวิดีโอ - - - ส่งการ์ดรายชื่อติดต่อ - ส่งการ์ดรายชื่อติดต่อ - - - ส่งไฟล์แนบ - ส่งไฟล์แนบ - - - %d ไฟล์แนบพร้อมส่ง - 1 ไฟล์แนบพร้อมส่ง - - "ส่งความคิดเห็น" - "ดูใน Google Play สโตร์" - "ข้อมูลเวอร์ชัน" - "เวอร์ชัน %1$s" - "สัญญาอนุญาตสำหรับโอเพนซอร์ส" - "การแจ้งเตือน" - "ถึงขีดจำกัดของไฟล์แนบแล้ว" - "ไม่สามารถโหลดไฟล์แนบได้" - "เพิ่มในรายชื่อติดต่อใช่ไหม" - "เพิ่มรายชื่อติดต่อ" - "เรื่อง" - "เรื่อง: " - "%s%s" - "กำลังโหลดบัตรติดต่อ" - "ไม่สามารถโหลดการ์ดรายชื่อติดต่อได้" - "ดูบัตรติดต่อ" - - รายชื่อติดต่อ %d ราย - รายชื่อติดต่อ %d ราย - - "การ์ดรายชื่อติดต่อ" - "วันเกิด" - "บันทึก" - "ส่งต่อข้อความ" - "ตอบ" - "+1" - "+%d" - "ปิดใช้ SMS แล้ว" - "ในการส่งข้อความ ให้ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น" - "ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น" - "เปลี่ยน" - "ในการรับข้อความ ให้ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น" - "ยังไม่ได้เลือกซิมสำหรับการส่งข้อความ SMS" - "เจ้าของอุปกรณ์ไม่อนุญาตแอปนี้" - "ตกลง" - "มีผู้เข้าร่วมในการสนทนามากเกินไป" - - รายชื่อติดต่อไม่ถูกต้อง - รายชื่อติดต่อไม่ถูกต้อง - - "ไม่สามารถโหลดภาพจากกล้องได้" - "คุณ: " - "%s: " - "ข้อความร่าง" - "เมื่อคุณเริ่มการสนทนาใหม่ คุณจะเห็นการสนทนาใหม่ที่นี่" - "การสนทนาที่เก็บไว้จะปรากฏที่นี่" - "กำลังโหลดการสนทนา…" - "ภาพ" - "คลิปเสียง" - "วิดีโอ" - "บัตรติดต่อ" - "MMS" - "เลิกทำ" - "ลองอีกครั้ง" - "ป้อนชื่อผู้ติดต่อหรือหมายเลขโทรศัพท์เพื่อเริ่มเขียนข้อความใหม่" - "บล็อก" - "บล็อก %s" - "เลิกบล็อก %s" - "ต้องการบล็อก %s ไหม" - "คุณจะยังคงได้รับข้อความจากหมายเลขนี้ต่อไปแต่จะไม่ได้รับการแจ้งเตือนอีก ระบบจะเก็บการสนทนานี้ไว้" - "รายชื่อที่บล็อกไว้" - "เลิกบล็อก" - "รายชื่อที่บล็อกไว้" - "เลือกรูปภาพจากห้องสมุดเอกสาร" - "กำลังส่งข้อความ" - "ส่งข้อความแล้ว" - "ข้อมูลมือถือปิดอยู่ โปรดตรวจสอบการตั้งค่า" - "ไม่สามารถส่งข้อความในโหมดบนเครื่องบินได้" - "ไม่สามารถส่งข้อความนี้" - "ดาวน์โหลดข้อความแล้ว" - "ข้อมูลมือถือปิดอยู่ โปรดตรวจสอบการตั้งค่า" - "ไม่สามารถดาวน์โหลดข้อความในโหมดบนเครื่องบินได้" - "ไม่สามารถดาวน์โหลดข้อความได้" - "ศูนย์" - "หนึ่ง" - "สอง" - "สาม" - "สี่" - "ห้า" - "หก" - "เจ็ด" - "แปด" - "เก้า" - "ไม่สามารถส่งข้อความผ่าน %1$s ข้อผิดพลาด %2$d" - "ไม่สามารถส่งข้อความผ่านผู้ให้บริการที่ไม่รู้จัก ข้อผิดพลาด %1$d" - "ส่งต่อ: %s" - "ไม่มีการส่งข้อความ เพราะยังไม่เปิดใช้งานบริการบนเครือข่าย" - "ไม่มีการส่งข้อความ: ที่อยู่ปลายทางไม่ถูกต้อง" - "ไม่มีการส่งข้อความ: ข้อความไม่ถูกต้อง" - "ไม่มีการส่งข้อความ: ระบบไม่สนับสนุนเนื้อหา" - "ไม่มีการส่งข้อความ: ระบบไม่สนับสนุนข้อความ" - "ไม่ได้ส่งข้อความ: ขนาดใหญ่เกินไป" - "ข้อความใหม่" - "ดู" - "รูปภาพ" - "ไม่สามารถหาแอปพลิเคชันที่เหมาะสม" - "นำผู้รับออก" - "ข้อความใหม่" - "ยกเลิก" - "แก้ไขจุดเข้าใช้งาน" - "ไม่ได้ตั้งค่า" - "ชื่อ" - "APN" - "MMSC" - "พร็อกซี MMS" - "พอร์ต MMS" - "MCC" - "MNC" - "ประเภท APN" - "ลบ APN" - "APN ใหม่" - "บันทึก" - "ยกเลิก" - "ต้องใส่ข้อมูลในช่องชื่อ" - "ต้องใส่ข้อมูล APN" - "ช่อง MCC ต้องเป็นเลข 3 หลัก" - "ช่อง MNC ต้องเป็นเลข 2 หรือ 3 หลัก" - "กำลังคืนการตั้งค่า APN เริ่มต้น" - "รีเซ็ตเป็นค่าเริ่มต้น" - "รีเซ็ตการตั้งค่า APN กลับเป็นค่าเริ่มต้นเรียบร้อยแล้ว" - "ไม่มีชื่อ" - "ชื่อจุดเข้าใช้งาน" - "APN" - "APN ใหม่" - "การตั้งค่าจุดเข้าใช้งานไม่สามารถใช้ได้สำหรับผู้ใช้รายนี้" - "คัดลอกไปยังคลิปบอร์ดไหม" - "คัดลอก" - "ถึง %s" - "ทั่วไป" - "ขั้นสูง" - "การตั้งค่าทั่วไป" - "การตั้งค่าขั้นสูง" - "ซิม \"%s\"" - "ส่งข้อความ SMS ส่วนบุคคลให้กับผู้รับทุกคน เฉพาะคุณที่จะได้รับการตอบกลับ" - "ส่ง MMS เดี่ยวให้กับผู้รับทุกคน" - "ไม่ทราบหมายเลข" - "ข้อความใหม่" - "ข้อความใหม่" - "ตัวเลือกซิม" - "เลือก %1$s แล้ว ตัวเลือกซิม" - "แก้ไขเรื่อง" - "เลือกซิมหรือแก้ไขเรื่อง" - "แตะค้างไว้เพื่อบันทึกเสียง" - "เริ่มต้นการสนทนาใหม่" - "การรับส่งข้อความ" - "รายการสนทนาในการรับส่งข้อความ" - "การรับส่งข้อความ" - "ข้อความใหม่" - "รายการการสนทนา" - "กำลังโหลดการสนทนา" - "กำลังโหลดข้อความ" - "ดูการสนทนาเพิ่มเติม" - "ดูข้อความเพิ่มเติม" - "ลบการสนทนาแล้ว" - "ลบการสนทนาแล้ว แตะเพื่อแสดงการสนทนาอื่นๆ ในการรับส่งข้อความ" - "ถูกบล็อก" - "เลิกบล็อกแล้ว" - "พื้นที่เก็บข้อมูลเหลือน้อย ข้อมูลบางอย่างอาจสูญหายไป" - "เลือกไฟล์แนบ" - "ยืนยันการเลือก" - "เลือกไว้ %d รายการ" - "โปรดนำไฟล์แนบออกอย่างน้อย 1 รายการ แล้วลองอีกครั้ง" - "คุณสามารถลองส่งข้อความ แต่ข้อความอาจส่งไม่ได้หากคุณไม่ได้นำไฟล์แนบออกอย่างน้อย 1 รายการ" - "คุณสามารถส่งวิดีโอได้ 1 รายการต่อข้อความเท่านั้น โปรดนำวิดีโอเพิ่มเติมออกและลองอีกครั้ง" - "การรับส่งข้อความไม่สามารถโหลดไฟล์แนบ" - "ส่งเลย" - "ไม่สามารถเริ่มการสนทนาได้" - "%1$s (%2$s)" - "เลือก %s แล้ว" - diff --git a/res/values-tl/arrays.xml b/res/values-tl/arrays.xml deleted file mode 100644 index 6f30a29..0000000 --- a/res/values-tl/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "walang paksa" - "nosubject" - - - "Oo" - "Hindi" - "OK" - "Hehe" - "Salamat" - "Sang-ayon ako" - "Ayos" - "Papunta na ako" - "OK, balikan na lang kita mamaya" - ":)" - ":(" - - diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml deleted file mode 100644 index dddb04b..0000000 --- a/res/values-tl/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Pagmemensahe" - "Pagmemensahe" - "Pumili ng pag-uusap" - "Mga Setting" - "Magpadala ng Mensahe" - "Magdagdag ng attachment" - "Tulong" - "Maligayang Pagdating" - "Laktawan" - "Susunod >" - "Susunod" - "Lumabas" - "Mga Setting >" - "Mga Setting" - "Kailangan ng pahintulot ng Pagmemensahe sa SMS, Telepono at Mga Contact." - "Maaari mong baguhin ang mga pahintulot sa Mga Setting > Mga App > Pagmemensahe > Mga Pahintulot." - "Maaari mong baguhin ang mga pahintulot sa Mga Setting, Mga App, Pagmemensahe, Mga Pahintulot." - "Mga madalas makaugnayan" - "Lahat ng contact" - "Ipadala sa %s" - "Kumuha ng mga larawan o video" - "Pumili ng mga larawan mula sa device na ito" - "I-record ang audio" - "Pumili ng larawan" - "Napili ang media." - "Inalis sa pagkakapili ang media." - "%d ang pinili" - "larawan %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "larawan" - "I-record ang audio" - "Ibahagi" - "Ngayon lang" - "Ngayon" - - %d min - %d na min - - - %d oras - %d na oras - - - %d araw - %d na araw - - - %d linggo - %d na linggo - - - %d buwan - %d na buwan - - - %d taon - %d na taon - - "Class 0 na mensahe" - "I-save" - "Kaunti na lang ang espasyo ng device. Awtomatikong ide-delete ng Pagmemensahe ang mga mas lumang mensahe upang magbakante ng espasyo." - "Nauubos na ang espasyo ng storage" - "Maaaring hindi makatanggap o makapagpadala ng mga mensahe ang Pagmemensahe hanggang sa maging available ang higit pang espasyo sa iyong device." - "Kaunti na ang storage ng SMS. Maaaring kailanganin mong magtanggal ng mga mensahe." - "Kumpirmahin ang numero ng iyong telepono" - "Titiyakin ng isang beses na hakbang na ito na maihahatid nang maayos ng Pagmemensahe ang iyong mga mensahe sa pangkat." - "Numero ng telepono" - "Tanggalin ang lahat ng mensahe na may media" - "Tangglain ang mga mensaheng lampas na ng %s" - "Awtomatikong i-delete ang mga mensaheng mas luma sa %s" - "Huwag Pansinin" - "I-delete ang lahat ng mensaheng may media?" - "I-delete ang mga mensaheng mas luma sa %s?" - "Awtomatikong i-delete ang mga mensaheng mas luma sa %s at i-on ang awtomatikong pag-delete?" - "Sinabi ni %s" - "Sinabi mo" - "Mensahe mula kay %s" - "Nagpadala ka ng mensahe" - "Ipinapadala..." - "Hindi naipadala. Pindutin upang subukang muli.." - "Hindi naipadala. Sinusubukang muli…" - "Ipadalang muli o i-delete" - "Mangyaring mag-voice call sa mga pang-emergency na serbisyo. Sa ngayon, hindi maipadala ang iyong text message." - "Hindi nagawa" - "Bagong MMS message na ida-download" - "Bagong MMS message" - "Hindi mai-download" - "Pindutin upang subukang muli" - "Pindutin upang i-download" - "I-download o i-delete" - "Nagda-download..." - "Nag-expire o hindi available ang mensahe" - "laki: %1$s, expiration: %2$s" - "Hindi maipadala. Di-wasto ang tatanggap." - "Hindi naka-activate sa network ang serbisyo" - "Hindi maipadala dahil sa problema sa network" - "Nag-expire o hindi available ang mensahe" - "(Walang paksa)" - "Hindi kilalang nagpadala" - "Naihatid" - "Hindi ma-download ang mensaheng %1$s mula kay %2$s." - "Hindi makumpleto ang operation sa database dahil sa mababang memory" - "Hindi naipadala ang mensahe" - "Hindi naipadala sa Pagmemensahe ang ilang mensahe" - - %d mensahe sa %d pag-uusap - %d na mensahe sa %d na pag-uusap - - "Hindi na-download ang mensahe" - "Hindi na-download sa Pagmemensahe ang ilang mensahe" - - %d mensahe sa %d pag-uusap - %d na mensahe sa %d na pag-uusap - - "Hindi naipadala ang mensahe sa %1$s" - "Mangyaring mag-voice call sa mga pang-emergency na serbisyo. Sa ngayon, hindi maipadala ang iyong text message sa %1$s." - - %d bagong mensahe - %d na bagong mensahe - - "Magsimula" - "Hindi available ang camera" - "Hindi available ang camera" - "Hindi available ang pagkuha ng video" - "Hindi mai-save ang media" - "Hindi makakakuha ng larawan" - "Bumalik" - "Naka-archive" - "I-delete" - "I-archive" - "Alisin sa archive" - "I-off ang mga notification" - "I-on ang mga notification" - "Magdagdag ng contact" - "I-download" - "Ipadala" - "I-delete" - "I-delete ang mensaheng ito?" - "Hindi maaaring bawiin ang pagkilos na ito." - "I-delete" - - I-delete ang mga pag-uusap na ito? - I-delete ang mga pag-uusap na ito? - - "Tanggalin" - "Kanselahin" - "Para kay" - "Pumili ng maramihang larawan" - "Kumpirmahin ang pinili" - "+%d" - "Hindi mai-record ang audio. Subukang muli." - "Hindi mai-play ang audio. Subukang muli." - "Hindi mai-save ang audio. Subukang muli." - "Pindutin nang matagal" - ", " - " " - ": " - " " - "Larawan" - "Audio clip" - "Video" - "Contact card" - "I-download" - "Tumugon sa pamamagitan ng SMS" - "Tumugon sa pamamagitan ng MMS" - "Tumugon" - - %d kalahok - %d na kalahok - - "Ako" - "Na-block at na-archive ang contact" - "Naka-unblock at hindi naka-archive ang contact" - "%d ang na-archive" - "%d ang inalis sa pagkaka-archive" - "Naka-off ang mga notification" - "Naka-on ang mga notification" - "Handa na ang lahat. Pindutin muli ang Ipadala." - "Matagumpay na naitakda ang Pagmemensahe bilang default na SMS app." - - I-discard ang mga attachment - I-discard ang mga attachment - - "Attachment na audio" - "I-play ang attachment na audio" - "I-pause" - "Mensahe mula kay %s: %s." - "Hindi naipadalang mensahe mula kay %s: %s. Oras: %s." - "Mensahe mula kay %s: %s. Oras: %s." - "Hindi naipadalang mensahe kay %s: %s. Oras: %s." - "Ipinapadala ang mensahe kay %s: %s. Oras: %s." - "Hindi naipadalang mensahe kay %s: %s. Oras: %s." - "Mensahe kay %s: %s. Oras: %s." - "Hindi naipadalang mensahe mula kay %s: %s. Oras: %s. %s." - "Mensahe mula kay %s: %s. Oras: %s. %s." - "Hindi naipadalang mensahe sa %s: %s. Oras: %s." - "Ipinapadala ang mensahe sa %s: %s. Oras: %s." - "Hindi naipadalang mensahe sa %s: %s. Oras: %s." - "Mensahe sa %s: %s. Oras: %s." - "Hindi naipadalang mensahe. Pindutin upang subukang muli." - "Pag-uusap kasama sina %s" - "I-delete ang paksa" - "Kumuha ng video" - "Kumuha ng still na larawan" - "Kumuha ng larawan" - "Simulan ang pag-record ng video" - "Lumipat sa camera na full screen" - "Lumipat sa pagitan ng camera sa harap at likod" - "Ihinto ang pagre-record at i-attach ang video" - "Itigil ang pagre-record ng video" - "Mga larawan sa pagmemensahe" - - %d larawan ang naka-save sa album na \"%s\" - %d na larawan ang naka-save sa album na \"%s\" - - - %d video ang naka-save sa album na \"%s\" - %d na video ang naka-save sa album na \"%s\" - - - %d attachment ang naka-save sa album na \"%s\" - %d na attachment ang naka-save sa album na \"%s\" - - - %d attachment ang naka-save sa \"Mga Download\" - %d na attachment ang naka-save sa \"Mga Download\" - - - %d attachment ang na-save - %d na attachment ang na-save - - - Hindi ma-save ang %d attachment - Hindi ma-save ang %d na attachment - - "Na-save na MMS attachment" - "Mga Setting" - "Naka-archive" - "Isara" - "MMS" - "Advanced" - "I-debug" - "Mga Notification" - "Tunog" - "Naka-silent" - "Mag-vibrate" - "Naka-block" - "Mga ulat sa pagpapadala ng SMS" - "Humiling ng ulat sa paghahatid para sa bawat SMS na iyong ipinapadala" - "I-auto-retrieve" - "Awtomatikong kunin ang mga mensaheng MMS" - "Awtomatikong pagkuha sa roaming" - "Awtomatikong kunin ang MMS kapag naka-roaming" - "Panggrupong pagpapadala ng mensahe" - "Gamitin ang MMS upang magpadala ng iisang mensahe kapag may maraming recipient" - "Default na SMS app" - "Default na SMS app" - - - "Numero ng iyong telepono" - "Hindi kilala" - "Mga tunog ng papalabas na mensahe" - "Itapon ang SMS" - "Itapon ang raw data ng natanggap na SMS sa file ng external storage" - "Itapon ang MMS" - "Itapon ang raw data ng natanggap na MMS sa file ng external storage" - "Mga wireless na alerto" - "Mga opsyon sa mensahe" - "Kopyahin ang text" - "Tingnan ang mga detalye" - "I-delete" - "Ipasa" - "Mga detalye ng mensahe" - "Uri: " - "Text message" - "Multimedia message" - "Mula kay: " - "Para kay: " - "Ipinadala: " - "Natanggap: " - "Paksa: " - "Laki: " - "Priyoridad: " - "SIM: " - "Mataas" - "Normal" - "Mababa" - "SIM %s" - "Nakatago ang address ng nagpadala" - "Hindi maaaring magpadala ng mensahe habang naglo-load ng mga attachment." - "Hindi mai-load ang attachment. Subukang muli." - "Hindi pa handa ang network. Subukang muli." - "I-delete ang text" - "Lumipat sa pagitan ng paglagay ng text at mga numero" - "Magdagdag ng higit pang mga kalahok" - "Kumpirmahin ang mga kalahok" - "Magsimula ng bagong pag-uusap" - "Piliin ang item na ito" - "I-play ang video" - "Mga tao at opsyon" - "I-debug" - "Mga tao at opsyon" - "Pangkalahatan" - "Mga tao sa pag-uusap na ito" - "Tumawag" - "Magpadala ng mensahe" - "Magpadala ng mensahe<br/><small>mula sa %s</small>" - - Ipadala ang mga larawan - Ipadala ang mga larawan - - - Ipadala ang mga audio - Ipadala ang mga audio - - - Ipadala ang mga video - Ipadala ang mga video - - - Ipadala ang mga contact card - Ipadala ang mga contact card - - - Ipadala ang mga attachment - Ipadala ang mga attachment - - - %d attachment ang handa nang ipadala - %d na attachment ang handa nang ipadala - - "Magpadala ng feedback" - "Tingnan sa Google Play Store" - "Impormasyon sa bersyon" - "Bersyon %1$s" - "Mga open source na lisensya" - "Mga Notification" - "Naabot na ang limitasyon ng attachment" - "Hindi na-load ang attachment." - "Idagdag sa Mga Contact?" - "Magdagdag ng Contact" - "Subject" - "Subject: " - "%s%s" - "Nilo-load ang contact card" - "Hindi mai-load ang contact card" - "Tingnan ang contact card" - - %d contact - %d na contact - - "Mga contact card" - "Kaarawan" - "Mga Tala" - "Ipasa ang mensahe" - "Tumugon" - "+1" - "+%d" - "Naka-disable ang SMS" - "Upang ipadala, itakda ang Pagmemensahe bilang default na SMS app" - "Itakda ang Pagmemensahe bilang default na SMS app" - "Baguhin" - "Upang makatanggap ng mga mensahe, itakda ang Pagmemensahe bilang default na SMS app" - "Walang piniling gustong SIM para sa pagpapadala ng mga mensaheng SMS" - "Hindi pinapayagan ang app na ito ng may-ari ng device." - "OK" - "Masyadong maraming kalahok sa isang pag-uusap" - - Mga di-wastong contact - Mga di-wastong contact - - "Hindi mai-load ang larawan ng camera" - "Ikaw: " - "%s: " - "Draft" - "Sa sandaling magsimula ka ng bagong pag-uusap, makikita mo ito na nakalista rito" - "Lumalabas dito ang mga naka-archive na pag-uusap" - "Nilo-load ang mga pag-uusap…" - "Larawan" - "Audio clip" - "Video" - "Card sa pakikipag-ugnayan" - "MMS" - "I-undo" - "Subukang muli" - "Maglagay ng pangalan ng contact o numero ng telepono upang magsimula ng isang bagong mensahe" - "I-block" - "I-block ang %s" - "Alisin sa pagkaka-block ang %s" - "I-block ang %s?" - "Patuloy kang makakatanggap ng mga mensahe mula sa numerong ito ngunit hindi ka na aabisuhan. Maa-archive ang pag-uusap na ito." - "Mga na-block na contact" - "ALISIN SA PAGKAKA-BLOCK" - "Mga na-block na contact" - "Pumili ng larawan mula sa library ng dokumento" - "Ipinapadala ang mensahe" - "Naipadala na ang mensahe" - "Naka-off ang cellular data. Suriin ang iyong mga setting." - "Hindi maaaring magpadala ng mga mensahe sa Airplane mode" - "Hindi maipadala ang mensahe" - "Na-download na ang mensahe" - "Naka-off ang cellular data. Tingnan ang iyong mga setting." - "Hindi maaaring mag-download ng mga mensahe sa Airplane mode" - "Hindi ma-download ang mensahe" - "Zero" - "Isa" - "Dalawa" - "Tatlo" - "Apat" - "Lima" - "Anim" - "Pito" - "Walo" - "Siyam" - "Hindi maipadala ang mensahe gamit ang %1$s, error na %2$d" - "Hindi maipadala ang mensahe gamit ang hindi nakikilalang carrier, error na %1$d" - "Fwd: %s" - "Hindi napadala ang mensahe: hindi naka-activate sa network ang serbisyo" - "Hindi naipadala ang mensahe: di-wastong patutunguhang address" - "Hindi naipadala ang mensahe: di-wastong mensahe" - "Hindi naipadala ang mensahe: hindi sinusuportahang content" - "Hindi naipadala ang mensahe: hindi sinusuportahang mensahe" - "Hindi napadala ang mensahe: masyadong malaki" - "Bagong mensahe" - "Tingnan" - "Larawan" - "Hindi makahanap ng naaangkop na application" - "Alisin ang tatanggap" - "Bagong mensahe" - "Kanselahin" - "I-edit ang access point" - "Hindi nakatakda" - "Pangalan" - "APN" - "MMSC" - "Proxy ng MMS" - "Port ng MMS" - "MCC" - "MNC" - "Uri ng APN" - "I-delete ang APN" - "Bagong APN" - "I-save" - "I-discard" - "Hindi maaaring walang laman ang field ng Pangalan." - "Hindi maaaring walang laman ang APN." - "Dapat na 3 digit ang field na MCC." - "Dapat na 2 o 3 digit ang field ng MNC." - "Nire-restore ang default na mga setting ng APN" - "I-reset sa default" - "Nakumpleto na ang pag-reset sa default na mga setting ng APN." - "Walang pamagat" - "Mga Access Point Name" - "Mga APN" - "Bagong APN" - "Hindi available ang mga setting ng Access Point Name para sa user na ito" - "Kopyahin sa clipboard?" - "Kopyahin" - "sa %s" - "Pangkalahatan" - "Advanced" - "Mga pangkalahatang setting" - "Mga advanced na setting" - "\"%s\" SIM" - "Magpadala ng mga indibidwal na mensaheng SMS sa lahat ng tatanggap. Ikaw lang ang makakatanggap ng anumang mga tugon" - "Magpadala ng iisang MMS sa lahat ng tatanggap" - "Hindi kilalang numero" - "Bagong mensahe" - "Bagong mensahe." - "SIM selector" - "Pinili ang %1$s, selector ng SIM" - "I-edit ang paksa" - "Pumili ng SIM o i-edit ang paksa" - "Pindutin nang matagal upang i-record ang audio" - "Magsimula ng bagong pag-uusap" - "Pagmemensahe" - "Listahan ng Pagmemensahe" - "Pagmemensahe" - "Bagong mensahe" - "Listahan ng pag-uusap" - "Nilo-load ang mga pag-uusap" - "Nilo-load ang mga mensahe" - "Tumingin ng higit pang pag-uusap" - "Tingnan ang higit pang mga mensahe" - "Na-delete na ang pag-uusap" - "Na-delete na ang pag-uusap. Pindutin upang magpakita ng iba\'t ibang pag-uusap sa Pagmemensahe" - "Na-block" - "Na-unblock" - "Kaunti na lang ang espasyo sa storage. Maaaring mawala ang ilang data." - "Pumili ng mga attachment" - "Kumpirmahin ang napili" - "%d ang napili" - "Mangyaring mag-alis ng isa o higit pang mga attachment at subukang muli." - "Maaari mong subukang ipadala ang iyong mensahe, ngunit maaaring hindi ito maipadala maliban kung mag-aalis ka ng isa o higit pang mga attachment." - "Maaari ka lang magpadala ng isang video sa bawat mensahe. Mangyaring alisin ang mga karagdagang video at subukang muli." - "Hindi na-load ng pagmemensahe ang attachment." - "Ipadala pa rin" - "Hindi makapagsimula ng pag-uusap" - "%1$s (%2$s)" - "%s ang napili" - diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml deleted file mode 100644 index d0b4e84..0000000 --- a/res/values-tr/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "konu yok" - "konu yok" - - - "Evet" - "Hayır" - "Tamam" - "Olur" - "Teşekkürler" - "Kabul ediyorum" - "Güzel" - "Yoldayım" - "Tamam, sana sonra dönerim" - ":)" - ":(" - - diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml deleted file mode 100644 index 6ea1cbe..0000000 --- a/res/values-tr/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Mesajlaşma" - "Mesajlaşma" - "İleti dizisini seç" - "Ayarlar" - "İleti Gönder" - "Dosya ekle" - "Yardım" - "Hoş geldiniz" - "Atla" - "Sonraki >" - "Sonraki" - "Çık" - "Ayarlar >" - "Ayarlar" - "Mesajlaşma\'nın SMS, Telefon ve Kişiler için izninize ihtiyacı var." - "İzinleri; Ayarlar > Uygulamalar > Mesajlaşma > İzinler\'de değiştirebilirsiniz." - "İzinleri; Ayarlar, Uygulamalar, Mesajlaşma, İzinler\'de değiştirebilirsiniz." - "Sık kullanılanlar" - "Tüm kişiler" - "Şuraya gönder: %s" - "Fotoğraf veya video çekin" - "Bu cihazdan resim seç" - "Ses kaydedin" - "Fotoğraf seç" - "Medya seçildi." - "Medya seçilmedi." - "%d öğe seçildi" - "resim %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "resim" - "Ses kaydedin" - "Paylaş" - "Şimdi" - "Şimdi" - - %d dk. - %d dk. - - - %d saat - %d saat - - - %d gün - %d gün - - - %d hafta - bir hafta - - - %d ay - bir ay - - - %d yıl - bir yıl - - "Sınıf 0 iletisi" - "Kaydet" - "Cihazınızda boş alan az. Mesajlaşma boş alan açmak için daha eski iletileri otomatik olarak silecektir." - "Depolama alanı bitiyor" - "Mesajlaşma, cihazınızda daha fazla yer açılana kadar ileti gönderip almayabilir." - "SMS depolama alanı azaldı. İletileri silmeniz gerekebilir." - "Telefon numaranızı doğrulayın" - "Bu bir kerelik adım, Mesajlaşma\'nın, grup iletilerini düzgün olarak teslim etmesini sağlayacaktır." - "Telefon numarası" - "Medya içeren tüm iletileri sil" - "Şundan eski iletileri sil: %s" - "Şundan daha eski iletileri otomatik olarak sil: %s" - "Yoksay" - "Medya içeren tüm iletiler silinsin mi?" - "Şundan daha eski iletiler silinsin mi: %s?" - "Şundan daha eski iletiler silinip otomatik silme etkinleştirilsin mi: %s?" - "%s şunu dedi:" - "Şunu dediniz:" - "%s adlı kişiden ileti" - "Bir ileti gönderdiniz" - "Gönderiliyor..." - "Gönderilmedi. Tekrar denemek için dokunun." - "Gönderilmedi. Tekrar deniyor…" - "Yeniden gönderin veya silin" - "Lütfen acil durum servislerine telefon edin. Kısa mesajınız şu anda teslim edilemedi." - "Başarısız oldu" - "İndirilecek yeni MMS iletisi" - "Yeni MMS iletisi" - "İndirilemedi" - "Tekrar denemek için dokunun" - "İndirmek için dokunun" - "İndirin veya silin" - "İndiriliyor..." - "İletinin süresi doldu veya kullanılamıyor" - "boyut: %1$s, süre sonu: %2$s" - "Gönderilemiyor. Alıcı geçersiz." - "Hizmet, ağda etkinleştirilmemiş" - "Ağ sorunu nedeniyle gönderilemedi" - "İletinin süresi doldu veya kullanılamıyor" - "(Konu yok)" - "Bilinmeyen gönderen" - "Teslim edildi" - "%2$s tarafından gönderilen %1$s konulu ileti indirilemedi." - "Yetersiz bellek nedeniyle veritabanı işlemi tamamlanamadı" - "İleti gönderilmedi" - "Bazı iletiler Mesajlaşma\'da gönderilmedi" - - %d ileti dizisinde %d ileti - Bir ileti dizisinde %d ileti - - "İleti indirilmedi" - "Bazı iletiler Mesajlaşma\'dan indirilmedi" - - %d ileti dizisinde %d ileti - Bir ileti dizisinde %d ileti - - "%1$s numaralı telefona ileti gönderilemedi" - "Lütfen acil durum servislerine telefon edin. %1$s numaralı telefona gönderdiğiniz kısa mesajınız şu anda teslim edilemedi." - - %d yeni ileti - Yeni ileti - - "Başlat" - "Kamera kullanılamıyor" - "Kamera kullanılamıyor" - "Video çekimi kullanılamıyor" - "Medya kaydedilemiyor" - "Resim çekilemiyor" - "Geri" - "Arşivlenen" - "Sil" - "Arşivle" - "Arşivden kaldır" - "Bildirimleri devre dışı bırak" - "Bildirimleri aç" - "Kişi ekle" - "İndir" - "Gönder" - "Sil" - "Bu ileti silinsin mi?" - "Bu işlem geri alınamaz." - "Sil" - - Bu ileti dizileri silinsin mi? - Bu ileti dizisi silinsin mi? - - "Sil" - "İptal" - "Alıcı:" - "Birden fazla resim seç" - "Seçimi onayla" - "+%d" - "Ses kaydedilemiyor. Tekrar deneyin." - "Ses çalınamıyor. Tekrar deneyin." - "Ses kaydedilemedi. Tekrar deneyin." - "Dokunup basılı tutun" - ", " - " " - ": " - " " - "Resim" - "Ses klibi" - "Video" - "Kişi kartı" - "İndir" - "SMS ile yanıtla" - "MMS ile yanıtla" - "Yanıtla" - - %d katılımcı - %d katılımcı - - "Ben" - "Kişi engellendi ve arşivlendi" - "Kişinin engellemesi kaldırıldı ve kişi arşivden geri alındı" - "%d ileti dizisi arşivlendi" - "%d ileti dizisi arşivden kaldırıldı" - "Bildirimler kapalı" - "Bildirimler açık" - "Tümü ayarlandı. Gönder\'e tekrar dokunun." - "Mesajlaşma, başarılı bir şekilde varsayılan SMS uygulaması olarak ayarlandı." - - Ekleri sil - Eki sil - - "Ses dosyası eki" - "Ekli ses dosyasını çal" - "Duraklat" - "%s adlı kişiden ileti: %s." - "Şu ileti %s tarafından gönderilemedi: %s. Saat: %s." - "Şu ileti %s tarafından gönderildi: %s. Saat: %s." - "Şu ileti %s adlı kişiye gönderilmedi: %s. Saat: %s." - "Şu ileti %s adlı kişiye gönderiliyor: %s. Saat: %s." - "Şu ileti %s adlı kişiye gönderilemedi: %s. Saat: %s." - "Şu ileti %s adlı kişiye gönderildi: %s. Saat: %s." - "Şu ileti %s tarafından gönderilemedi: %s. Saat: %s. %s." - "Şu ileti %s tarafından gönderildi: %s. Saat: %s. %s." - "Şu ileti %s adlı gruba gönderilmedi: %s. Saat: %s." - "Şu ileti %s adlı gruba gönderiliyor: %s. Saat: %s." - "Şu ileti %s adlı gruba gönderilemedi: %s. Saat: %s." - "Şu ileti %s adlı gruba gönderildi: %s. Saat: %s." - "İleti başarısız oldu. Yeniden denemek için dokunun." - "%s ile ileti dizisi" - "Konuyu sil" - "Video çek" - "Fotoğraf çek" - "Fotoğraf çek" - "Video kaydetmeye başla" - "Tam ekran kameraya geç" - "Ön ve arka kamera arasında geçiş yap" - "Kaydı durdur ve video ekle" - "Video kaydını durdur" - "Mesajlaşma fotoğrafları" - - %d fotoğraf, \"%s\" albümüne kaydedildi - %d fotoğraf, \"%s\" albümüne kaydedildi - - - %d video, \"%s\" albümüne kaydedildi - %d video, \"%s\" albümüne kaydedildi - - - Ekli %d dosya, \"%s\" albümüne kaydedildi - Ekli %d dosya, \"%s\" albümüne kaydedildi - - - Ekli %d dosya, \"İndirilenler\"e kaydedildi - Ekli %d dosya, \"İndirilenler\"e kaydedildi - - - %d ek kaydedildi - %d ek kaydedildi - - - %d ek kaydedilemedi - %d ek kaydedilemedi - - "MMS eki kaydedildi" - "Ayarlar" - "Arşivlenen" - "Kapat" - "MMS" - "Gelişmiş" - "Hata Ayıklama" - "Bildirimler" - "Ses" - "Sessiz" - "Titreşim" - "Engellendi" - "SMS iletim raporları" - "Gönderilen her SMS için bir iletim raporu iste" - "Otomatik al" - "MMS iletilerini otomatik olarak al" - "Dolaşımda otomatik al" - "Dolaşımdayken MMS\'leri otomatik olarak al" - "Grup mesajları" - "Birden fazla alıcıya tek bir ileti göndermek için MMS kullanın" - "Varsayılan SMS uygulaması" - "Varsayılan SMS uygulaması" - - - "Telefon numaranız" - "Bilinmiyor" - "Giden ileti sesleri" - "SMS verilerini aktar" - "Alınan ham SMS verilerini harici bir depolama dosyasına aktarın" - "MMS verilerini aktar" - "Alınan ham MMS verilerini harici bir depolama dosyasına aktarın" - "Kablosuz uyarıları" - "İleti seçenekleri" - "Metni kopyala" - "Ayrıntıları görüntüle" - "Sil" - "Yönlendir" - "İleti ayrıntıları" - "Tür: " - "Kısa mesaj" - "Multimedya iletisi" - "Gönderen: " - "Alıcı: " - "Gönderilme tarihi: " - "Alınma tarihi: " - "Konu: " - "Boyut: " - "Öncelik: " - "SIM: " - "Yüksek" - "Normal" - "Düşük" - "SIM %s" - "Gizli gönderen adresi" - "Ekler yüklenirken ileti gönderilemez." - "Ek yüklenemiyor. Tekrar deneyin." - "Ağ hazır değil. Tekrar deneyin." - "Metni sil" - "Metin ve sayı girişi arasında geçiş yap" - "Daha fazla katılımcı ekle" - "Katılımcıları onayla" - "Yeni ileti dizisi başlat" - "Bu öğeyi seçin" - "Videoyu oynat" - "Kişiler ve seçenekler" - "Hata ayıkla" - "Kişiler ve seçenekler" - "Genel" - "Bu görüşmedeki kişiler" - "Çağrı yap" - "İleti gönder" - "Mesajı şu karttan gönder:<br/><small>%s</small>" - - Fotoğrafları gönder - Fotoğrafı gönder - - - Ses dosyalarını gönder - Ses dosyasını gönder - - - Videoları gönder - Videoyu gönder - - - Kişi kartlarını gönder - Kişi kartını gönder - - - Ekleri gönder - Eki gönder - - - %d ek gönderilmeye hazır - Bir ek gönderilmeye hazır - - "Geri bildirim gönder" - "Google Play Store\'da görüntüle" - "Sürüm bilgisi" - "Sürüm %1$s" - "Açık kaynak lisansları" - "Bildirimler" - "Ek sınırına ulaşıldı" - "Ek yüklenemedi." - "Kişilere eklensin mi?" - "Kişi Ekle" - "Konu" - "Konu: " - "%s%s" - "Kişi kartı yükleniyor" - "Kişi kartı yüklenemedi" - "Kişi kartını görüntüle" - - %d kişi - %d kişi - - "Kişi kartları" - "Doğum günü" - "Notlar" - "İletiyi yönlendir" - "Yanıtla" - "+1" - "+%d" - "SMS devre dışı bırakıldı" - "Göndermek için Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın" - "Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın" - "Değiştir" - "İletileri almak için Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın" - "SMS iletilerini göndermek için SIM tercihi yapılmadı" - "Cihaz sahibi bu uygulamaya izin vermiyor." - "Tamam" - "Bir görüşmede çok fazla katılımcı var" - - Geçersiz kişiler - Geçersiz kişi - - "Kamera görüntüsü yüklenemedi" - "Siz: " - "%s: " - "Taslak" - "Yeni bir görüşme başlattığınızda burada listelendiğini göreceksiniz" - "Arşivlenmiş ileti dizileri burada görünür" - "İleti dizileri yükleniyor..." - "Resim" - "Ses klibi" - "Video" - "Kişi kartı" - "MMS" - "Geri al" - "Yeniden dene" - "Yeni bir iletiye başlamak için bir kişi adı veya telefon numarası girin" - "Engelle" - "%s hedefini engelle" - "%s adlı kişinin engellemesini kaldır" - "%s engellensin mi?" - "Bu numaradan gelen iletileri almaya devam edecek ancak artık bildirim almayacaksınız. Bu ileti dizisi arşivlenecektir." - "Engellenen kişiler" - "ENGELLEMEYİ KALDIR" - "Engellenen kişiler" - "Doküman kitaplığından resim seç" - "İleti gönderiliyor" - "İleti gönderildi" - "Hücresel veri özelliği kapalı. Ayarlarınızı kontrol edin." - "Uçak modunda iletiler gönderilemiyor" - "İleti gönderilemedi" - "İleti indirildi" - "Hücresel veri özelliği kapalı. Ayarlarınızı kontrol edin." - "Uçak modundayken iletiler indirilemez" - "İleti indirilemedi" - "Sıfır" - "Bir" - "İki" - "Üç" - "Dört" - "Beş" - "Altı" - "Yedi" - "Sekiz" - "Dokuz" - "%1$s ile ileti gönderilemiyor. Hata kodu: %2$d" - "Bilinmeyen operatör ile ileti gönderilemiyor. Hata kodu: %1$d" - "Yön: %s" - "İleti gönderilmedi: Hizmet, ağ üzerinde etkin değil" - "İleti gönderilmedi: Geçersiz gönderim adresi" - "İleti gönderilmedi: Geçersiz ileti" - "İleti gönderilmedi: Desteklenmeyen içerik" - "İleti gönderilmedi: Desteklenmeyen ileti" - "İleti gönderilmedi: Çok büyük" - "Yeni ileti" - "Görüntüle" - "Resim" - "Uygun bir uygulama bulunamadı" - "Alıcıyı kaldır" - "Yeni ileti" - "İptal" - "Erişim noktasını düzenle" - "Ayarlanmadı" - "Ad" - "APN" - "MMSC" - "MMS proxy\'si" - "MMS bağlantı noktası" - "MCC" - "MNC" - "APN türü" - "APN\'yi sil" - "Yeni APN" - "Kaydet" - "Yoksay" - "Ad alanı boş olamaz." - "APN boş olamaz." - "MCC alanındaki değer 3 rakamdan oluşmalıdır." - "MNC alanındaki değer 2 veya 3 rakamdan oluşmalıdır." - "Varsayılan APN ayarları geri yükleniyor." - "Varsayılana sıfırla" - "Varsayılan APN ayarlarına sıfırlama tamamlandı." - "Başlıksız" - "Erişim Noktası Adları (APN)" - "APN\'ler" - "Yeni APN" - "Erişim Noktası Adı (APN) ayarları bu kullanıcı için kullanılamıyor" - "Panoya kopyalansın mı?" - "Kopyala" - "Mesajın alınacağı kart: %s" - "Genel" - "Gelişmiş" - "Genel ayarlar" - "Gelişmiş ayarlar" - "\"%s\" SIM" - "Tüm alıcılara tek tek SMS iletisi gönder. Sadece size yanıt gönderilir." - "Tüm alıcılara tek bir MMS iletisi gönder" - "Bilinmeyen numara" - "Yeni ileti" - "Yeni ileti." - "SIM seçici" - "%1$s seçildi, SIM seçici" - "Konuyu düzenle" - "SIM seçin veya konuyu düzenleyin" - "Ses kaydetmek için dokunun ve basılı tutun" - "Yeni ileti dizisi başlat" - "Mesajlaşma" - "Mesajlaşma Listesi" - "Mesajlaşma" - "Yeni ileti" - "İleti dizisi listesi" - "İleti dizileri yükleniyor" - "İletiler yükleniyor" - "Diğer ileti dizilerini görüntüle" - "Daha fazla ileti görüntüle" - "İleti dizisi silindi" - "İleti dizisi silindi. Farklı bir Mesajlaşma ileti dizisini görüntülemek için dokunun" - "Engellendi" - "Engellemesi kaldırıldı" - "Depolama alanı az. Bazı veriler kaybolabilir." - "Ekleri seçin" - "Seçimi onayla" - "%d öğe seçildi" - "Lütfen bir veya daha fazla eki kaldırın ve tekrar deneyin." - "İletinizi göndermeyi deneyebilirsiniz, ancak bir veya daha fazla eki kaldırmadığınız sürece ileti teslim edilmeyebilir." - "Her iletide yalnızca bir tane video gönderebilirsiniz. Lütfen ek videoları kaldırın ve tekrar deneyin." - "Mesajlaşma, eki yükleyemedi." - "Yine de gönder" - "Sohbet başlatılamadı" - "%1$s (%2$s)" - "%s seçildi" - diff --git a/res/values-uk/arrays.xml b/res/values-uk/arrays.xml deleted file mode 100644 index fc47a64..0000000 --- a/res/values-uk/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "без теми" - "без теми" - - - "Так" - "Ні" - "ОК" - "Ха-ха-ха" - "Дякую" - "Погоджуюсь" - "Чудово" - "У дорозі" - "Добре, я зателефоную пізніше" - ":)" - ":(" - - diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml deleted file mode 100644 index 1c44548..0000000 --- a/res/values-uk/strings.xml +++ /dev/null @@ -1,571 +0,0 @@ - - - - - "Повідомлення" - "Повідомлення" - "Виберіть ланцюжок повідомлень" - "Налаштування" - "Надіслати повідомлення" - "Вкласти файл" - "Довідка" - "Вітаємо" - "Пропустити" - "Далі >" - "Далі" - "Вийти" - "Налаштування >" - "Налаштування" - "Додатку Повідомлення потрібен доступ до SMS, телефона й контактів." - "Щоб змінити дозволи, виберіть \"Налаштування\" > \"Додатки\" > \"Повідомлення\" > \"Дозволи\"." - "Щоб змінити дозволи, виберіть \"Налаштування\" > \"Додатки\" > \"Повідомлення\" > \"Дозволи\"." - "Найчастіше на зв’язку" - "Усі контакти" - "Надіслати на номер %s" - "Зняти фото або відео" - "Вибрати зображення на цьому пристрої" - "Записати аудіо" - "Вибрати фотографію" - "Медіафайл вибрано." - "Медіафайл не вибрано." - "Вибрано %d" - "зображення: %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "зображення" - "Записати аудіо" - "Поділитися" - "Щойно" - "Зараз" - - %d хвилина - %d хвилини - %d хвилин - %d хвилини - - - %d година - %d години - %d годин - %d години - - - %d день - %d дні - %d днів - %d дня - - - %d тиждень - %d тижні - %d тижнів - %d тижня - - - %d місяць - %d місяці - %d місяців - %d місяця - - - %d рік - %d роки - %d років - %d року - - "Повідомлення класу 0" - "Зберегти" - "На пристрої замало місця. З додатка Повідомлення буде автоматично видалено старіші SMS, щоб звільнити місце." - "Закінчується пам’ять" - "Додаток Повідомлення не зможе надсилати й отримувати SMS, доки не звільниться місце на пристрої." - "Недостатньо місця для SMS. Видаліть старі повідомлення." - "Підтвердьте свій номер телефону" - "Номер потрібен, щоб надсилати групові повідомлення. Його потрібно ввести лише один раз." - "Номер телефону" - "Видалити всі повідомлення з медіафайлами" - "Видалити повідомлення, отримані понад %s тому" - "Автоматично видаляти повідомлення, отримані понад %s тому" - "Ігнорувати" - "Видалити всі повідомлення з медіафайлами?" - "Видалити повідомлення, отримані понад %s тому?" - "Видаляти повідомлення, отримані понад %s тому й увімкнути автоматичне видалення?" - "Повідомлення від користувача %s" - "Ваше повідомлення" - "Повідомлення від користувача %s" - "Ви надіслали повідомлення" - "Надсилання…" - "Не надіслано. Торкніться, щоб повторити спробу." - "Не надіслано. Повторна спроба…" - "Надіслати повторно або видалити" - "Не вдалося надіслати текстове повідомлення в екстрену службу. Спробуйте зателефонувати." - "Помилка" - "Доступне нове MMS-повідомлення" - "Нове MMS-повідомлення" - "Не вдалося завантажити" - "Торкніться, щоб повторити спробу" - "Торкніться, щоб завантажити" - "Завантажити або видалити" - "Завантаження…" - "Повідомлення прострочене або недоступне" - "розмір: %1$s, дійсне до: %2$s" - "Не вдається надіслати. Недійсний отримувач." - "Послугу не активовано в мережі" - "Не вдалося надіслати через проблему з мережею" - "Повідомлення прострочене або недоступне" - "(Без теми)" - "Невідомий відправник" - "Доставлено" - "Не вдалося завантажити повідомлення з темою \"%1$s\" від користувача %2$s." - "Не вдалося завершити дію з базою даних. Недостатньо пам’яті" - "Повідомлення не надіслано" - "Деякі повідомлення не надіслано" - - %d повідомлення в ланцюжках (%d) - %d повідомлення в ланцюжках (%d) - %d повідомлень у ланцюжках (%d) - %d повідомлення в ланцюжках (%d) - - "Повідомлення не завантажено" - "Деякі повідомлення не завантажено" - - %d повідомлення в ланцюжках (%d) - %d повідомлення в ланцюжках (%d) - %d повідомлень у ланцюжках (%d) - %d повідомлення в ланцюжках (%d) - - "Повідомлення на номер %1$s не надіслано" - "Не вдалося надіслати текстове повідомлення на номер екстреної служби %1$s. Спробуйте зателефонувати." - - %d нове повідомлення - %d нові повідомлення - %d нових повідомлень - %d нового повідомлення - - "Почати" - "Камера недоступна" - "Камера недоступна" - "Відеозйомка недоступна" - "Не вдається зберегти медіафайли" - "Не вдається сфотографувати" - "Назад" - "Архів" - "Видалити" - "Архів" - "Розархівувати" - "Вимкнути сповіщення" - "Увімкнути сповіщення" - "Додати контакт" - "Завантажити" - "Надіслати" - "Видалити" - "Видалити це повідомлення?" - "Цю дію не можна скасувати." - "Видалити" - - Видалити ці ланцюжки повідомлень? - Видалити ці ланцюжки повідомлень? - Видалити ці ланцюжки повідомлень? - Видалити ці ланцюжки повідомлень? - - "Видалити" - "Скасувати" - "Кому:" - "Вибрати кілька зображень" - "Підтвердити вибір" - "+%d" - "Не вдається записати аудіо. Повторіть спробу." - "Не вдається відтворити аудіо. Повторіть спробу." - "Не вдалося зберегти аудіо. Повторіть спробу." - "Натисніть і утримуйте" - ", " - " " - ": " - " " - "Зображення" - "Аудіокліп" - "Відео" - "Картка контакта" - "Завантажити" - "Відповісти за допомогою SMS" - "Надіслати MMS" - "Відповісти" - - %d учасник - %d учасники - %d учасників - %d учасника - - "Я" - "Користувача заблоковано та заархівовано" - "Контакт розблоковано та розархівовано" - "Заархівовано: %d" - "Розархівовано: %d" - "Сповіщення вимкнено" - "Сповіщення ввімкнено" - "Готово. Торкніться опції \"Надіслати\" знову." - "Повідомлення тепер є додатком для SMS за умовчанням." - - Видалити вкладені файли - Видалити вкладені файли - Видалити вкладені файли - Видалити вкладені файли - - "Вкладений аудіофайл" - "Відтворити аудіовкладення" - "Пауза" - "Повідомлення від користувача %s: %s." - "Не вдалося надіслати повідомлення від користувача %s: %s. Час: %s." - "Повідомлення від користувача %s: %s. Час: %s." - "Ненадіслане повідомлення групі %s: %s. Час: %s." - "Надсилається повідомлення групі %s: %s. Час: %s." - "Не вдалося надіслати повідомлення групі %s: %s. Час: %s." - "Повідомлення групі %s: %s. Час: %s." - "Не вдалося надіслати повідомлення від користувача %s: %s. Час: %s. %s." - "Повідомлення від користувача %s: %s. Час: %s. %s." - "Ненадіслане повідомлення групі %s: %s. Час: %s." - "Надсилається повідомлення групі %s: %s. Час: %s." - "Не вдалося надіслати повідомлення групі %s: %s. Час: %s." - "Повідомлення групі %s: %s. Час: %s." - "Не вдалося надіслати повідомлення. Торкніться, щоб повторити спробу." - "Учасники бесіди: %s" - "Видалити тему" - "Зняти відео" - "Зняти статичне зображення" - "Зробити знімок" - "Почати запис відео" - "Показувати зображення з камери на весь екран" - "Переключатися між камерами на передній і задній панелях" - "Зупинити запис і долучити відео" - "Зупинити запис відео" - "Фото з повідомлень" - - %d фотографію збережено в альбом \"%s\" - %d фотографії збережено в альбом \"%s\" - %d фотографій збережено в альбом \"%s\" - %d фотографії збережено в альбом \"%s\" - - - %d відео збережено в альбом \"%s\" - %d відео збережено в альбом \"%s\" - %d відео збережено в альбом \"%s\" - %d відео збережено в альбом \"%s\" - - - %d вкладений файл збережено в альбом \"%s\" - %d вкладені файли збережено в альбом \"%s\" - %d вкладених файлів збережено в альбом \"%s\" - %d вкладеного файлу збережено в альбом \"%s\" - - - %d вкладений файл збережено в папку \"Завантаження\" - %d вкладені файли збережено в папку \"Завантаження\" - %d вкладених файлів збережено в папку \"Завантаження\" - %d вкладеного файлу збережено в папку \"Завантаження\" - - - Збережено %d вкладений файл - Збережено %d вкладені файли - Збережено %d вкладених файлів - Збережено %d вкладеного файлу - - - Не вдалося зберегти %d вкладений файл - Не вдалося зберегти %d вкладені файли - Не вдалося зберегти %d вкладених файлів - Не вдалося зберегти %d вкладеного файлу - - "Вкладення з MMS збережено" - "Налаштування" - "Архів" - "Закрити" - "MMS" - "Додатково" - "Налагодження" - "Сповіщення" - "Сигнал" - "Без звуку" - "Вібросигнал" - "Заблоковано" - "Звіти про доставку SMS" - "Запитувати звіт про доставку для кожного надісланого SMS" - "Автоматичне отримання" - "Автоматично отримувати MMS-повідомлення" - "Автоотримання в роумінгу" - "Автоматично отримувати MMS у роумінгу" - "Групові повідомлення" - "Використовуйте опцію MMS, щоб надсилати одне повідомлення декільком одержувачам" - "Додаток для SMS за умовчанням" - "Додаток для SMS за умовчанням" - - - "Ваш номер телефону" - "Невідомий" - "Сигнали для вихідних повідомлень" - "Записати дані SMS" - "Записати отримані вихідні дані SMS у файл зовнішньої пам’яті" - "Записати дані MMS" - "Записати отримані вихідні дані MMS у файл зовнішньої пам’яті" - "Сповіщення через бездротові мережі" - "Параметри повідомлення" - "Копіювати текст" - "Переглянути деталі" - "Видалити" - "Переслати" - "Деталі повідомлення" - "Тип: " - "SMS" - "Мультимедійне повідомлення" - "Від: " - "Кому: " - "Надіслано: " - "Отримано: " - "Тема: " - "Розмір: " - "Пріоритет: " - "SIM-карта: " - "Високий" - "Звичайний" - "Низький" - "SIM-карта %s" - "Прихована адреса відправника" - "Неможливо надіслати повідомлення під час завантаження вкладених файлів." - "Не вдається завантажити вкладений файл. Повторіть спробу." - "Мережа не готова. Повторіть спробу." - "Видалити текст" - "Переключення між введенням тексту та числами" - "Додати більше учасників" - "Підтвердити учасників" - "Почати нову бесіду" - "Вибрати цей елемент" - "Відтворити відео" - "Учасники й опції" - "Налагодження" - "Учасники й опції" - "Загальні" - "Учасники цієї бесіди" - "Телефонувати" - "Надіслати повідомлення" - "Надіслати повідомлення<br/><small>із SIM-карти %s</small>" - - Надіслати фото - Надіслати фото - Надіслати фото - Надіслати фото - - - Надіслати аудіозаписи - Надіслати аудіозаписи - Надіслати аудіозаписи - Надіслати аудіозаписи - - - Надіслати відео - Надіслати відео - Надіслати відео - Надіслати відео - - - Надіслати картки контактів - Надіслати картки контактів - Надіслати картки контактів - Надіслати картки контактів - - - Надіслати вкладені файли - Надіслати вкладені файли - Надіслати вкладені файли - Надіслати вкладені файли - - - Можна надсилати %d вкладений файл - Можна надсилати %d вкладені файли - Можна надсилати %d вкладених файлів - Можна надсилати %d вкладеного файлу - - "Надіслати відгук" - "Переглянути в магазині Google Play" - "Інформація про версію" - "Версія %1$s" - "Ліцензії відкритого коду" - "Сповіщення" - "Ви вклали максимальну кількість файлів" - "Не вдалося завантажити вкладений файл." - "Додати в контакти?" - "Додати контакт" - "Тема" - "Тема: " - "%s. %s" - "Завантаження картки контакта" - "Не вдалося завантажити картку контакта" - "Переглянути картку контакта" - - %d контакт - %d контакти - %d контактів - %d контакта - - "Картки контактів" - "День народження" - "Нотатки" - "Переслати повідомлення" - "Відповісти" - "+1" - "+%d" - "SMS вимкнено" - "Щоб надсилати SMS, зробіть Повідомлення додатком для SMS за умовчанням" - "Зробіть Повідомлення додатком для SMS за умовчанням" - "Змінити" - "Щоб отримувати повідомлення, зробіть Повідомлення додатком для SMS за умовчанням" - "Не вибрано основної SIM-карти для надсилання SMS-повідомлень" - "Власник пристрою не надав дозволів для цього додатка." - "ОК" - "Забагато учасників у бесіді" - - Недійсні контакти - Недійсні контакти - Недійсні контакти - Недійсні контакти - - "Не вдалося завантажити зображення з камери" - "Ви: " - "%s: " - "Чернетка" - "Коли ви почнете переписку, вона з’явиться тут" - "Тут відображаються заархівовані бесіди" - "Завантаження бесід…" - "Зображення" - "Аудіокліп" - "Відео" - "Картка контакта" - "MMS" - "Скасувати" - "Повторити" - "Щоб створити нове повідомлення, введіть ім’я контакта або номер телефону" - "Заблокувати" - "Блокувати користувача %s" - "Розблокувати користувача %s" - "Заблокувати номер %s?" - "Ви надалі отримуватимете повідомлення з цього номера, але не отримуватимете сповіщення. Цю бесіду буде заархівовано." - "Заблоковані контакти" - "РОЗБЛОКУВАТИ" - "Заблоковані контакти" - "Вибрати зображення з бібліотеки документів" - "Повідомлення надсилається" - "Повідомлення надіслано" - "Мобільне передавання даних вимкнено. Перевірте налаштування." - "Не вдається надіслати повідомлення в режимі польоту" - "Не вдалося надіслати повідомлення" - "Повідомлення завантажено" - "Мобільний Інтернет вимкнено. Перевірте налаштування." - "Неможливо завантажити повідомлення в режимі польоту" - "Не вдалося завантажити повідомлення" - "Нуль" - "Один" - "Два" - "Три" - "Чотири" - "П’ять" - "Шість" - "Сім" - "Вісім" - "Дев’ять" - "Не вдається надіслати повідомлення. Помилка %2$d, оператор %1$s" - "Не вдається надіслати повідомлення. Помилка %1$d, невідомий оператор" - "Пересилання: %s" - "Повідомлення не надіслано: послугу не активовано в мережі" - "Повідомлення не надіслано. Недійсна адреса одержувача" - "Повідомлення не надіслано. Повідомлення недійсне" - "Повідомлення не надіслано. Вміст не підтримується" - "Повідомлення не надіслано. Повідомлення не підтримується" - "Повідомлення не надіслано: текст задовгий" - "Нове повідомлення" - "Переглянути" - "Зображення" - "Не вдалося знайти відповідний додаток" - "Видалити отримувача" - "Нове повідомлення" - "Скасувати" - "Редагування точки доступу" - "Не вказано" - "Назва" - "Назва точки доступу" - "MMSC" - "Проксі MMS" - "Порт MMS" - "MCC" - "MNC" - "Тип назви точки доступу" - "Видалити назву точки доступу" - "Нова назва точки доступу" - "Зберегти" - "Відхилити" - "Поле \"Назва\" не може бути порожнім." - "Поле назви точки доступу не може бути порожнім." - "Поле MCC має містити 3 цифри." - "Поле MNC має містити 2 або 3 цифри." - "Відновлення налаштувань назви точки доступу за умовчанням." - "Відновити налаштування за умовчанням" - "Налаштування назви точки доступу за умовчанням відновлено." - "Без назви" - "Назви точок доступу" - "Назви точок доступу" - "Нова назва точки доступу" - "Цей користувач не може вказувати назву точки доступу" - "Копіювати в буфер обміну?" - "Копіювати" - "на SIM-карту %s" - "Загальні" - "Розширені" - "Загальні налаштування" - "Додаткові налаштування" - "SIM-карта \"%s\"" - "Надсилати окремі SMS усім отримувачам. Відповіді надходитимуть лише вам" - "Надсилати одне MMS усім отримувачам" - "Невідомий номер" - "Нове повідомлення" - "Нове повідомлення." - "Вибір SIM-карти" - "Вибір SIM-карти: вибрано %1$s" - "Змінити тему" - "Вибрати SIM-карту або змінити тему" - "Торкніться й утримуйте, щоб записати аудіо" - "Почати нову бесіду" - "Повідомлення" - "Список у додатку Повідомлення" - "Повідомлення" - "Нове повідомлення" - "Список бесід" - "Завантаження бесід" - "Завантаження повідомлень" - "Переглянути інші бесіди" - "Переглянути більше повідомлень" - "Бесіду видалено" - "Бесіду видалено. Торкніться, щоб переглянути іншу бесіду в додатку Повідомлення" - "Заблоковано" - "Розблоковано" - "Замало пам’яті. Деякі дані можуть не зберегтись." - "Вибрати вкладені файли" - "Підтвердити вибір" - "Вибрано %d" - "Видаліть один або декілька вкладених файлів і повторіть спробу." - "Спробуйте надіслати повідомлення, але адресат може його не отримати, якщо ви не видалите один або декілька вкладених файлів." - "У повідомленні можна надіслати лише одне відео. Видаліть зайві відео та спробуйте знову." - "Не вдалося завантажити вкладенні файли." - "Усе одно надіслати" - "Не вдалося почати бесіду" - "%1$s (%2$s)" - "Вибрано %s" - diff --git a/res/values-ur-rPK/arrays.xml b/res/values-ur-rPK/arrays.xml deleted file mode 100644 index fa4e5da..0000000 --- a/res/values-ur-rPK/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "بلا عنوان" - "بلا عنوان" - - - "ہاں" - "نہیں" - "ٹھیک ہے" - "Hehe" - "شکریہ" - "میں متفق ہوں" - "اچھا" - "راستے میں ہوں" - "ٹھیک ہے، میں بعد میں آپ سے رابطہ کرتا ہوں" - "‎:)‎" - "‎:(‎" - - diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml deleted file mode 100644 index 1ed22e1..0000000 --- a/res/values-ur-rPK/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "پیغام رسانی" - "پیغام رسانی" - "گفتگو منتخب کریں" - "ترتیبات" - "پیغام بھیجیں" - "ایک منسلکہ شامل کریں" - "مدد" - "خوش آمدید" - "نظر انداز کریں" - "اگلا >" - "آگے" - "باہر نکلیں" - "ترتیبات >" - "ترتیبات" - "‏پیغام رسانی کو SMS، فون اور رابطوں کیلئے اجازت درکار ہے۔" - "آپ ترتیبات > ایپس > پیغام رسانی > اجازتوں میں جا کر اجازتیں تبدیل کر سکتے ہیں۔" - "آپ ترتیبات، ایپس، پیغام رسانی، اجازتوں میں جا کر اجازتیں تبدیل کر سکتے ہیں۔" - "اکثر و بیشتر" - "سبھی رابطے" - "%s پر بھیجیں" - "تصاویر یا ویڈیو کیپچر کریں" - "اس آلہ سے تصاویر منتخب کریں" - "آڈیو ریکارڈ کریں" - "تصویر منتخب کریں" - "میڈیا منتخب کردہ ہے۔" - "میڈیا غیر منتخب کردہ ہے۔" - "%d کو منتخب کیا گیا" - "تصویر %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "تصویر" - "آڈیو ریکارڈ کریں" - "اشتراک کریں" - "ابھی ابھی" - "ابھی" - - %d منٹ - %d منٹ - - - %d گھنٹے - %d گھنٹہ - - - %d دن - %d دن - - - %d ہفتے - ایک ہفتہ - - - %d مہینے - ایک ماہ - - - %d سال - ایک سال - - "کلاس 0 پیغام" - "محفوظ کریں" - "آلہ میں جگہ کم ہے۔ پیغام رسانی جگہ خالی کرنے کیلئے خودکار طریقے سے پرانے پیغامات حذف کر دے گی۔" - "اسٹوریج کی جگہ ختم ہو رہی ہے" - "جب تک آپ کے آلہ پر مزید جگہ دستیاب نہ ہو، ممکن ہے اس وقت تک پیغام رسانی نہ پیغامات بھیجے یا نہ موصول کرے۔" - "‏SMS کا کم اسٹوریج۔ آپ کو کچھ پیغامات کو حذف کرنے کی ضرورت پڑ سکتی ہے۔" - "اپنے فون نمبر کی توثیق کریں" - "یہ یک وقتی مرحلہ اس بات کو یقینی بنائے گا کہ پیغام رسانی آپ کے گروپ پیغامات کو مناسب طریقے سے ڈیلیور کرے گی۔" - "فون نمبر" - "میڈیا والے سبھی پیغامات کو حذف کریں" - "%s سے پرانے پیغامات کو حذف کر دیں" - "%s سے پہلے کے پیغامات کو خودکار طور پر حذف کریں" - "نظر انداز کریں" - "میڈیا کے ساتھ سبھی پیغامات کو حذف کریں؟" - "%s سے پہلے کے پیغامات کو حذف کریں؟" - "%s سے پہلے کے پیغامات کو حذف کریں اور خودکار طور پر حذف کرنے کو آن کریں؟" - "%s نے کہا" - "آپ نے کہا" - "پیغام منجانب %s" - "آپ نے ایک پیغام بھیجا" - "بھیجا جا رہا ہے…" - "بھیجا نہیں گیا۔ دوبارہ کوشش کرنے کیلئے ٹچ کریں۔" - "بھیجا نہیں گیا۔ دوبارہ کوشش کی جا رہی ہے…" - "دوبارہ بھیجیں یا حذف کریں" - "براہ کرم ہنگامی سروسز کیلئے ایک صوتی کال کریں۔ آپ کا متنی پیغام اس وقت ڈیلیور نہیں کیا جا سکا۔" - "ناکام" - "‏ڈاؤن لوڈ کرنے کیلئے نیا MMS پیغام" - "‏نیا MMS پیغام" - "ڈاؤن لوڈ نہیں کیا جا سکا" - "دوبارہ کوشش کرنے کیلئے ٹچ کریں" - "ڈاؤن لوڈ کرنے کیلئے ٹچ کریں" - "ڈاؤن لوڈ کریں یا حذف کریں" - "ڈاؤن لوڈ ہو رہا ہے…" - "پیغام کی میعاد ختم ہو گئی یا دستیاب نہیں ہے" - "سائز: %1$s، اختتامی میعاد: %2$s" - "بھیجا نہیں جا سکتا۔ وصول کنندہ درست نہیں ہے۔" - "نیٹ ورک پر سروس فعال نہیں ہے" - "نیٹ ورک کے مسئلہ کی وجہ سے نہیں بھیجا جا سکا" - "پیغام کی میعاد ختم ہو گئی یا دستیاب نہیں ہے" - "(بلا عنوان)" - "نامعلوم ارسال کنندہ" - "ڈیلیور ہو گیا" - "پیغام %1$s کو %2$s سے ڈاؤن لوڈ نہیں کیا جا سکا۔" - "کم میموری کی وجہ سے ڈیٹا بیس کی کارروائی مکمل نہیں ہو سکی" - "پیغام نہیں بھیجا گیا" - "پیغام رسانی میں کچھ پیغامات کو نہیں بھیجا گیا" - - %d گفتگوؤں میں %d پیغامات - ایک گفتگو میں %d پیغامات - - "پیغام ڈاؤن لوڈ نہیں ہوا" - "پیغام رسانی میں کچھ پیغامات ڈاؤن لوڈ نہیں ہوئے" - - %d گفتگوؤں میں %d پیغامات - ایک گفتگو میں %d یغامات - - "%1$s پر پیغام نہیں بھیجا گیا" - "براہ کرم ہنگامی سروسز کیلئے ایک صوتی کال کریں۔ %1$s پر آپ کا متنی پیغام اس وقت ڈیلیور نہیں کیا جا سکا۔" - - %d نئے پیغامات - نیا پیغام - - "شروع کریں" - "کیمرہ دستیاب نہیں ہے" - "کیمرہ دستیاب نہیں ہے" - "ویڈیو کیپچر دستیاب نہیں ہے" - "میڈیا کو محفوظ نہیں کیا جا سکتا ہے" - "تصویر نہیں لی جا سکتی" - "واپس جائیں" - "آرکائیو کردہ" - "حذف کریں" - "آرکائیو کریں" - "آرکائیو سے نکالیں" - "اطلاعات آف کریں" - "اطلاعات آن کریں" - "رابطہ شامل کریں" - "ڈاؤن لوڈ کریں" - "بھیجیں" - "حذف کریں" - "یہ پیغام حذف کریں؟" - "اس عمل کو کالعدم نہیں کیا جاسکتا۔" - "حذف کریں" - - ان گفتگوؤں کو حذف کریں؟ - اس گفتگو کو حذف کریں؟ - - "حذف کریں" - "منسوخ کریں" - "بنام" - "متعدد تصاویر منتخب کریں" - "انتخاب کی توثیق کریں" - "‎+%d" - "آڈیو کو ریکارڈ نہیں کیا جا سکتا ہے۔ دوبارہ کوشش کریں۔" - "آڈیو کو چلایا نہیں جا سکتا ہے۔ دوبارہ کوشش کریں۔" - "آڈیو کو محفوظ نہیں کیا جا سکا۔ دوبارہ کوشش کریں۔" - "ٹچ کریں اور دبائے رکھیں" - "، " - " " - ": " - " " - "تصویر" - "آڈیو کلپ" - "ویڈیو" - "رابطہ کارڈ" - "ڈاؤن لوڈ کریں" - "‏SMS کے ذریعے جواب دیں" - "‏MMS کے ذریعے جواب دیں" - "جواب دیں" - - %d شرکت کنندگان - %d شرکت کنندہ - - "میں" - "رابطہ کو مسدود اور آرکائیو کر دیا گیا" - "رابطہ کو غیر مسدود کر دیا گیا اور آرکائیو سے نکال دیا گیا" - "%d کو آرکائیو کر دیا گیا" - "%d کو آرکائیو سے نکال دیا گیا" - "اطلاعات آف کر دی گئیں" - "اطلاعات آن کر دی گئیں" - "سب ٹھیک ہے۔ بھیجیں کو دوبارہ ٹچ کریں۔" - "‏پیغام رسانی کامیابی سے بطور ڈیفالٹ SMS ایپ سیٹ ہو گئی۔" - - منسلکہ جات کو مسترد کریں - منسلکہ کو مسترد کریں - - "آڈیو منسلکہ" - "آڈیو منسلکہ چلائیں" - "موقوف کریں" - "پیغام منجانب %s: %s۔" - "ناکام پیغام منجانب %s: %s۔ وقت: %s۔" - "پیغام منجانب %s: %s۔ وقت: %s۔" - "غیر ارسال کردہ پیغام بنام %s: %s۔ وقت: %s۔" - "پیغام بھیجا جا رہا ہے بنام %s: %s۔ وقت: %s۔" - "ناکام پیغام بنام %s: %s۔ وقت: %s۔" - "پیغام بنام %s: %s۔ وقت: %s۔" - "ناکام پیغام منجانب %s: %s۔ وقت: %s۔ %s۔" - "پیغام منجانب %s: %s۔ وقت: %s۔ %s۔" - "غیر ارسال کردہ پیغام بنام %s: %s۔ وقت: %s۔" - "پیغام بھیجا جا رہا ہے بنام %s: %s۔ وقت: %s۔" - "ناکام پیغام بنام %s: %s۔ وقت: %s۔" - "پیغام بنام %s: %s۔ وقت: %s۔" - "ناکام پیغام۔ دوبارہ کوشش کرنے کیلئے ٹچ کریں۔" - "%s کے ساتھ گفتگو" - "موضوع حذف کریں" - "ویڈیو کیپچر کریں" - "ایک ساکت تصویر کیپچر کریں" - "تصویر لیں" - "ویڈیو ریکارڈ کرنا شروع کریں" - "پوری اسکرین کے کیمرہ پر سوئچ کریں" - "سامنے اور پیچھے کے کیمرے کے درمیان سوئچ کریں" - "ریکارڈنگ بند کریں اور ویڈیو منسلک کریں" - "ویڈیو ریکارڈ کرنے کو روکیں" - "پیغام رسانی تصاویر" - - %d تصاویر \"%s\" البم میں محفوظ ہو گئیں - %d تصویر \"%s\" البم میں محفوظ ہو گئی - - - %d ویڈیوز \"%s\" البم میں محفوظ ہو گئیں - %d ویڈیو \"%s\" البم میں محفوظ ہو گئی - - - %d منسلکہ جات \"%s\" البم میں محفوظ ہو گئیں - %d منسلکہ \"%s\" البم میں محفوظ ہو گئی - - - %d منسلکہ جات \'ڈاؤن لوڈز\' میں محفوظ ہو گئیں - %d منسلکہ \'ڈاؤن لوڈز\' میں محفوظ ہو گئی - - - %d منسلکہ جات کو محفوظ کر دیا گیا - %d منسلکہ کو محفوظ کر دیا گیا - - - %d منسلکہ جات کو محفوظ نہیں کیا جا سکا - %d منسلکہ کو محفوظ نہیں کیا جا سکا - - "‏MMS منسلکہ کو محفوظ کر دیا گیا" - "ترتیبات" - "آرکائیو کردہ" - "بند کریں" - "MMS" - "جدید ترین" - "ڈیبگ کریں" - "اطلاعات" - "آواز" - "خاموش" - "وائبریٹ" - "مسدود" - "‏SMS ڈیلیوری کی رپورٹس" - "‏اپنے ذریعہ بھیجے جانے والے ہر SMS کی ایک ڈیلیوری رپورٹ کی درخواست کریں" - "خودکار طور پر بازیافت کریں" - "‏خود کار طور پر MMS پیغامات کی بازیابی کریں" - "رومنگ کے وقت خود کار بازیابی" - "‏رومنگ میں رہتے ہوئے MMS کی خود کار طور پر بازيابی کریں" - "گروپ پیغام رسانی" - "‏متعدد وصول کنندگان ہونے کی صورت میں ایک پیغام بھیجنے کیلئے MMS استعمال کریں" - "‏ڈیفالٹ SMS ایپ" - "‏ڈیفالٹ SMS ایپ" - - - "آپ کا فون نمبر" - "نامعلوم" - "آؤٹ گوئنگ پیغامات کی آوازیں" - "‏SMS کو ڈمپ کریں" - "‏وصول کردہ SMS خام ڈیٹا کو خارجی اسٹوریج فائل میں ڈمپ کریں" - "‏MMS کو ڈمپ کریں" - "‏وصول کردہ MMS خام ڈیٹا کو خارجی اسٹوریج فائل میں ڈمپ کریں" - "وائرلیس الرٹس" - "پیغام کے اختیارات" - "متن کاپی کریں" - "تفصیلات دیکھیں" - "حذف کریں" - "فارورڈ کریں" - "پیغام کی تفصیلات" - "قسم: " - "متنی پیغام" - "ملٹی میڈیا پیغام" - "منجانب: " - "بنام: " - "ارسال کردہ: " - "موصول کردہ: " - "موضوع: " - "سائز: " - "ترجیح: " - "SIM:‎ " - "اعلی" - "عام" - "کم" - "SIM %s‎" - "ارسال کنندہ کا پتہ مخفی کر دیا گیا" - "منسلکات کو لوڈ کرتے وقت پیغام نہیں بھیجا جا سکتا۔" - "منسلکہ کو لوڈ نہیں کیا جا سکتا۔ دوبارہ کوشش کریں۔" - "نیٹ ورک تیار نہیں ہے۔ دوبارہ کوشش کریں۔" - "متن کو حذف کریں" - "متن اور اعداد درج کرنے کے بیچ سوئچ کریں" - "مزید شرکاء کو شامل کریں" - "شرکاء کی توثیق کریں" - "نئی گفتگو شروع کریں" - "اس آئٹم کو منتخب کریں" - "ویڈیو چلائیں" - "لوگ اور اختیارات" - "ڈیبگ کریں" - "لوگ اور اختیارات" - "عام" - "اس گفتگو میں موجود لوگ" - "ایک کال کریں" - "پیغام بھیجیں" - "‏پیغام بھیجیں‎<br/><small>‎از %s‎</small>‎" - - تصاویر بھیجیں - تصویر بھیجیں - - - آڈیوز بھیجیں - آڈیو بھیجیں - - - ویڈیوز بھیجیں - ویڈیو بھیجیں - - - رابطہ کارڈز بھیجیں - رابطہ کارڈ بھیجیں - - - منسلکہ جات بھیجیں - منسلکہ بھیجیں - - - %d منسلکہ جات بھیجنے کیلئے تیار - ایک منسلکہ بھیجنے کیلئے تیار - - "تاثرات بھیجیں" - "‏Google Play اسٹور میں دیکھیں" - "ورژن کی معلومات" - "‏ورژن ‎%1$s" - "اوپن سورس لائسنسز" - "اطلاعات" - "منسلکہ کی حد تک پہنچ گئے" - "منسلکہ لوڈ کرنے میں ناکام۔" - "رابطوں میں شامل کریں؟" - "رابطہ شامل کریں" - "موضوع" - "موضوع: " - "‎%s%s‎" - "رابطہ کارڈ لوڈ کر رہا ہے" - "رابطہ کارڈ کو لوڈ نہیں کیا جا سکا" - "رابطہ کارڈ دیکھیں" - - %d رابطے - %d رابطہ - - "رابطہ کارڈز" - "سالگرہ" - "نوٹس" - "پیغام فارورڈ کریں" - "جواب دیں" - "‎+1‎" - "‎+%d‎" - "‏SMS غیر فعال ہے" - "‏بھیجنے کیلئے پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں" - "‏پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں" - "تبدیل کریں" - "‏پیغامات موصول کرنے کیلئے پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں" - "‏SMS پیغامات بھیجنے کیلئے کوئی ترجیحی SIM منتخب نہیں کیا گیا" - "آلہ کے مالک نے اس ایپ کی اجازت نہیں دی ہے۔" - "ٹھیک ہے" - "ایک گفتگو میں بہت زیادہ شرکاء" - - غلط رابطے - غلط رابطہ - - "کیمرے کی تصویر کو لوڈ نہیں کیا جا سکا" - "آپ: " - "‎%s:‎ " - "مسودہ" - "کوئی نئی گفتگو شروع کرنے پر، آپ اسے یہاں مندرج دیکھیں گے" - "آرکائیو کردہ گفتگوئیں یہاں ظاہر ہوتی ہیں" - "گفتگوئیں لوڈ ہو رہی ہیں…" - "تصویر" - "آڈیو کلپ" - "ویڈیو" - "رابطہ کارڈ" - "MMS" - "کالعدم کریں" - "دوبارہ کوشش کریں" - "ایک نیا پیغام شروع کرنے کیلئے رابطہ کا ایک نام یا فون نمبر درج کریں" - "مسدود کریں" - "%s کو مسدود کریں" - "%s کو غیر مسدود کریں" - "%s کو مسدود کریں؟" - "آپ کو اس نمبر سے پیغامات موصول ہوتے رہیں گے لیکن مزید مطلع نہیں کیا جائے گا۔ اس گفتگو کو آرکائیو کر دیا جائے گا۔" - "مسدود رابطے" - "غیر مسدود کریں" - "مسدود رابطے" - "دستاویز لائبریری سے تصویر منتخب کریں" - "پیغام بھیجا جا رہا ہے" - "پیغام بھیج دیا گیا" - "سیلولر ڈیٹا آف ہے۔ اپنی ترتیبات چیک کریں۔" - "پیغامات کو ہوائی جہاز وضع میں نہیں بھیجا جا سکتا ہے" - "پیغام بھیجا نہیں جا سکا" - "پیغام ڈاؤن لوڈ ہو گیا" - "سیلولر ڈیٹا آف ہے۔ اپنی ترتیبات چیک کریں۔" - "ہوائی جہاز وضع میں پیغامات ڈاؤن لوڈ نہیں کیے جا سکتے ہیں" - "پیغام ڈاؤن لوڈ نہیں کیا جا سکا" - "صفر" - "ایک" - "دو" - "تین" - "چار" - "پانچ" - "چھ" - "سات" - "آٹھ" - "نو" - "%1$s کے ساتھ پیغام نہیں بھیجا جا سکتا، خرابی %2$d" - "نامعلوم کیرئیر کے ساتھ پیغام نہیں بھیجا جا سکتا، خرابی %1$d" - "آگے منتقل کریں: %s" - "پیغام نہیں بھیجا گیا: سروس نیٹ ورک پر فعال نہیں ہے" - "پیغام نہیں بھیجا گیا: منزل مقصول کا غلط پتہ" - "پیغام نہیں بھیجا گیا: غلط پیغام" - "پیغام نہیں بھیجا گیا: غیر تعاون یافتہ مواد" - "پیغام کو نہیں بھیجا گیا: غیر تعاون یافتہ پیغام" - "پیغام نہیں بھیجا گیا: کافی بڑا ہے" - "نیا پیغام" - "دیکھیں" - "تصویر" - "ایک مناسب ایپلیکیشن تلاش نہیں کی جا سکی" - "وصول کنندہ کو ہٹائیں" - "نیا پیغام" - "منسوخ کریں" - "رسائی کی جگہ میں ترمیم کریں" - "سیٹ نہیں ہے" - "نام" - "APN" - "MMSC" - "‏MMS پراکسی" - "‏MMS پورٹ" - "MCC" - "MNC" - "‏APN قسم" - "‏APN کو حذف کریں" - "‏نیا APN" - "محفوظ کریں" - "مسترد کریں" - "نام کی فیلڈ خالی نہیں رہ سکتی-" - "‏APN خالی نہيں رہ سکتا۔" - "‏MCC فیلڈ میں 3 ہندسے ہونا ضروری ہے۔" - "‏MNC فیلڈ میں 2 یا 3 ہندسے ہونا ضروری ہے-" - "‏ڈیفالٹ APN ترتیبات بحال ہو رہی ہیں-" - "ڈیفالٹ پر دوبارہ ترتیب دیں" - "‏ڈیفالٹ APN ترتیبات کو دوبارہ ترتیب دینے کا عمل مکمل ہو گیا-" - "بلا عنوان" - "رسائی کی جگہ کے نام" - "APNs" - "‏نیا APN" - "رسائی کی جگہ کے نام کی ترتیبات اس صارف کیلئے دستیاب نہیں ہیں" - "کلپ بورڈ پر کاپی کریں؟" - "کاپی کریں" - "%s پر" - "عام" - "جدید ترین" - "عام ترتیبات" - "جدید ترین ترتیبات" - "‎\"%s\" SIM‎" - "‏سبھی وصول کنندگان کو انفرادی SMS پیغامات بھیجیں۔ صرف آپ کو کوئی جوابات موصول ہوں گے" - "‏سبھی وصول کنندگان کو ایک MMS بھیجیں" - "نامعلوم نمبر" - "نیا پیغام" - "نیا پیغام۔" - "‏SIM کا انتخاب کنندہ" - "‏منتخب شدہ %1$s، SIM کا انتخاب کنندہ" - "موضوع میں ترمیم کریں" - "‏SIM منتخب کریں یا موضوع میں ترمیم کریں" - "آڈیو ریکارڈ کرنے کیلئے ٹچ کریں اور پکڑ کر رکھیں" - "نئی گفتگو شروع کریں" - "پیغام رسانی" - "پیغام رسانی کی فہرست" - "پیغام رسانی" - "نیا پیغام" - "گفتگو کی فہرست" - "گفتگوئیں لوڈ ہو رہی ہیں" - "پیغامات لوڈ ہو رہے ہیں" - "مزید گفتگوئیں دیکھیں" - "مزید پیغامات دیکھیں" - "گفتگو کو حذف کر دیا گیا" - "گفتگو حذف ہوگئی۔ پیغام رسانی کی ایک دوسری گفتگو دکھانے کیلئے ٹچ کریں" - "مسدود" - "غیر مسدود" - "اسٹوریج کی جگہ کم ہے۔ کچھ ڈیٹا ضائع ہو سکتا ہے۔" - "منسلکات منتخب کریں" - "انتخاب کی توثیق کریں" - "%d کو منتخب کیا گیا" - "براہ کرم ایک یا مزید منسلکات ہٹائیں اور دوبارہ کوشش کریں۔" - "آپ اپنا پیغام بھیجنے کی کوشش کر سکتے ہیں، لیکن ہو سکتا ہے وہ تب تک ڈیلیور نہ ہو جب تک کہ آپ ایک یا مزيد منسلکات نہیں ہٹا دیتے۔" - "آپ فی پیغام صرف ایک ویڈیو بھیج سکتے ہیں۔ براہ کرم اضافی ویڈیوز ہٹائیں اور دوبارہ کوشش کریں۔" - "پیغام رسانی منسلکہ کو لوڈ کرنے میں ناکام رہی۔" - "بہر صورت بھیجیں" - "گفتگو شروع نہیں کی جا سکی" - "%1$s (%2$s)" - "%s منتخب کردہ" - diff --git a/res/values-uz-rUZ/arrays.xml b/res/values-uz-rUZ/arrays.xml deleted file mode 100644 index 79b4c47..0000000 --- a/res/values-uz-rUZ/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "mavzu yo‘q" - "mavzu yo‘q" - - - "Ha" - "Yo‘q" - "OK" - "Ha-ha" - "Rahmat" - "Yaxshi" - "Ajoyib" - "Yo‘ldaman" - "OK, keyinroq telefon qilaman" - ":)" - ":(" - - diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml deleted file mode 100644 index 2437398..0000000 --- a/res/values-uz-rUZ/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Xabarlar" - "Xabarlar" - "Suhbatni tanlang" - "Sozlamalar" - "Xabar yuborish" - "Fayl biriktiring" - "Yordam" - "Xush kelibsiz" - "O’tkazib yuborish" - "Keyingi >" - "Keyingi" - "Chiqish" - "Sozlamalar >" - "Sozlamalar" - "SMS/MMS xizmatiga SMS, Telefon va Kontaktlardan foydalanish uchun ruxsat zarur." - "Ruxsatnomalarni bu yerdan o‘zgartiring: Sozlamalar > Ilovalar > SMS/MMS > Ruxsatnomalar" - "Ruxsatnomalarni bu yerdan o‘zgartiring: Sozlamalar > Ilovalar > SMS/MMS > Ruxsatnomalar" - "Tez-tez" - "Barcha kontaktlar" - "Yuborish: %s" - "Rasm yoki video tasvirga olish" - "Ushbu qurilmadan rasmlarni tanlang" - "Audio yozish" - "Rasmni tanlang" - "Media tanlandi." - "Mediani tanlash bekor qilindi." - "%d ta tanlandi" - "tasvir %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "tasvir" - "Audio yozish" - "Ulashish" - "Hozir" - "Hozir" - - %d daqiqa - %d daqiqa - - - %d soat - %d soat - - - %d kun - %d kun - - - %d hafta - bir hafta - - - %d oy - bir oy - - - %d yil - bir yil - - "0-toifa xabari" - "Saqlash" - "Qurilma xotirasidagi bo‘sh joy kam qolmoqda. Joy bo‘shatish uchun eskiroq xabarlar avtomatik o‘chirib tashlanadi." - "Xotirada bo‘sh joy tugamoqda" - "Qurilmangizda bo‘sh joy kam qolsa, SMS/MMS xizmati xabarlarni yubora yoki qabul qila olmay qolishi mumkin." - "SMS uchun xotirada joy kam. Sizdan ba’zi xabarlarni o‘chirish talab qilinishi mumkin." - "Telefon raqamingizni tasdiqlang" - "Bu bir martalik bosqich bajarilsa, SMS/MMS xizmati guruh xabarlarini to‘g‘ri yuboradigan bo‘ladi." - "Telefon raqami" - "Barcha multimediali xabarlarni o‘chirish" - "%sdan eski xabarlar o‘chirilsin" - "%sdan oldingi xabarlar avtomatik o‘chirilsin" - "E’tiborsiz qoldirish" - "Media faylli barcha xabarlar o‘chirib tashlansinmi?" - "%s dan oldingi barcha xabarlar o‘chirib tashlansinmi?" - "%sdan oldingi xabarlar o‘chirilib, avtomatik o‘chirish funksiyasi yoqilsinmi?" - "%s dedi" - "Siz dedingiz" - "%s yuborgan xabar" - "Siz xabar yubordingiz" - "Yuborilmoqda…" - "Yuborilmadi. Qayta urinish uchun bosing." - "Yuborilmadi. Qayta urinilmoqda…" - "Qayta yuboring yoki o‘chirib tashlang" - "Favqulodda xizmatlarga ovozli qo‘ng‘iroq qiling. Favqulodda xizmat raqamlariga yuborilgan SMS xabarlarni hozirda yetkazib bo‘lmaydi." - "Xabar yuborilmadi" - "Yuklab olish uchun yangi MMS xabari" - "Yangi MMS xabari" - "Yuklab olinmadi" - "Qayta urinish uchun bosing" - "Yuklab olish uchun bosing" - "Yuklab oling yoki o‘chirib tashlang" - "Yuklab olinmoqda…" - "Xabar eskirgan yoki mavjud emas" - "hajmi: %1$s, tugash muddati: %2$s" - "Yuborib bo‘lmadi. Qabul qiluvchi noto‘g‘ri ko‘rsatilgan." - "Xizmat tarmoqda faollashtirilmagan" - "Tarmoqdagi muammo tufayli yuborib bo‘lmadi" - "Xabar eskirgan yoki mavjud emas" - "(Mavzu yo‘q)" - "Noma’lum yuboruvchi" - "Yetkazildi" - "“%1$s” to‘g‘risidagi xabarni (yuboruvchi: %2$s) yuklab bo‘lmadi." - "Xotirada kam joy qolganligi sababli ma’lumotlar bazasi amalini bajarib bo‘lmaydi" - "Xabar yuborilmadi" - "SMS/MMS xizmatidagi ayrim xabarlar yuborilmadi" - - Suhbatlar: %d, xabarlar: %d - Suhbatdagi xabarlar soni: %d - - "Xabar yuklab olinmadi" - "SMS/MMS xizmatidagi ayrim xabarlar yuklab olinmadi" - - Suhbatlar: %d, xabarlar: %d - Suhbatdagi xabarlar soni: %d - - "%1$s raqamiga xabar yuborilmadi" - "Favqulodda xizmatlarga ovozli qo‘ng‘iroq qiling. %1$s raqamiga yuborgan SMS xabaringizni hozir yetkazib bo‘lmaydi." - - %d ta yangi xabar - Yangi xabar - - "Boshlash" - "Kamera tayyor emas" - "Kamera tayyor emas" - "Videoga olib bo‘lmaydi" - "Media faylni saqlab bo‘lmadi" - "Rasmga olib bo‘lmadi" - "Orqaga" - "Arxivlangan" - "O‘chirish" - "Arxivlash" - "Arxivdan chiqarish" - "Bildirishnomalarni o‘chirib qo‘yish" - "Bildirishnomalarni yoqish" - "Kontakt qo‘shish" - "Yuklab olish" - "Yuborish" - "O‘chirish" - "Ushbu xabar o‘chirilsinmi?" - "Ushbu amalni bekor qilib bo‘lmaydi." - "O‘chirish" - - Bu suhbatlar o’chirib tashlansinmi? - Bu suhbat o’chirib tashlansinmi? - - "O‘chirish" - "Bekor qilish" - "Kimga:" - "Bir nechta rasm tanlash" - "Tanlovni tasdiqlash" - "+%d ta" - "Audioni yozib bo‘lmadi. Qaytadan urining." - "Audioni ijro qilib bo‘lmadi. Qaytadan urining." - "Audioni saqlab bo‘lmadi. Qaytadan urining." - "Bosib turing" - ", " - " " - ": " - " " - "Rasm" - "Audio-klip" - "Video" - "Kontaktlar kartasi" - "Yuklab olish" - "SMS orqali javob berish" - "MMS orqali javob" - "Javob berish" - - %d ta ishtirokchi - %d ta ishtirokchi - - "Men" - "Kontakt bloklandi va arxivlandi" - "Kontakt blokdan va arxivdan chiqarildi" - "%d ta qayd arxivlandi" - "%d ta qayd arxivdan chiqarildi" - "Bildirishnomalar o‘chirib qo‘yildi" - "Bildirishnomalar yoqildi" - "Hammasi tayyor. Endi “Yuborish” tugmasini bosing." - "SMS/MMS xizmati birlamchi SMS ilovasi sifatida o‘rnatildi." - - Biriktirmalarni olib tashlash - Biriktirmani olib tashlash - - "Ovozli biriktirma" - "Audio biriktirmani ijro qilish" - "To‘xtatib turish" - "%s yuborgan xabar: %s." - "%s yuborgan muvaffaqiyatsiz xabar: %s. Vaqt: %s" - "%s yuborgan xabar: %s. Vaqt: %s" - "%sga yuborilmay qolgan xabar: %s. Vaqt: %s" - "%sga xabar yuborilmoqda: %s. Vaqt: %s" - "%sga yuborilgan muvaffaqiyatsiz xabar: %s. Vaqt: %s" - "%sga xabar: %s. Vaqt: %s" - "%s yuborgan muvaffaqiyatsiz xabar: %s. Vaqt: %s. %s" - "%s yuborgan xabar: %s. Vaqt: %s. %s" - "%s guruhiga yuborilmay qolgan xabar: %s. Vaqt: %s" - "%s guruhiga xabar yuborilmoqda: %s. Vaqt: %s" - "%s guruhiga yuborilgan muvaffaqiyatsiz xabar: %s. Vaqt: %s" - "%s guruhiga xabar: %s. Vaqt: %s" - "Muvaffaqiyatsiz xabar. Qayta urinish uchun bosing." - "%s bilan suhbat" - "Mavzuni o‘chirish" - "Video tasvirga olish" - "Harakatsiz rasmni suratga olish" - "Rasmga olish" - "Video tasvirga olishni boshlash" - "To‘liq ekranli kameraga o‘tish" - "Old va orqa kamera o‘rtasida almashish" - "Tasvirga olishni to‘xtatish va faylni biriktirish" - "Videoga olishni to‘xtatish" - "SMS/MMS rasmlari" - - %d ta rasm “%s” albomiga saqlandi - %d ta rasm “%s” albomiga saqlandi - - - %d ta video “%s” albomiga saqlandi - %d ta video “%s” albomiga saqlandi - - - %d ta biriktirma “%s” albomiga saqlandi - %d ta biriktirma “%s” albomiga saqlandi - - - %d ta biriktirma “Yuklanishlar” jildiga saqlandi - %d ta biriktirma “Yuklanishlar” jildiga saqlandi - - - %d ta biriktirma saqlandi - %d ta biriktirma saqlandi - - - %d ta biriktirmani saqlab bo’lmadi - %d ta biriktirmani saqlab bo’lmadi - - "MMS fayli saqlandi" - "Sozlamalar" - "Arxivlangan" - "Yopish" - "MMS" - "Qo‘shimcha" - "Tuzatish" - "Bildirishnomalar" - "Ovoz" - "Ovozsiz" - "Tebranish" - "Bloklandi" - "SMS xabarlarning yetkazilganlik hisobotlari" - "Yuborilgan har bir SMS uchun yetkazilganlik hisobotini talab qilish" - "Avtomatik yuklanish" - "MMS xabarlarni avtomatik yuklab olish" - "Rouming vaqtida avtomatik yuklab olish" - "Rouming vaqtida MMS xabarlarni avtomatik yuklab olish" - "Guruh xabarlari" - "Bitta xabarni bir nechta qabul qiluvchiga yuborish uchun MMS xabardan foydalanilsin" - "Birlamchi SMS ilovasi" - "Birlamchi SMS ilovasi" - - - "Telefon raqamingiz" - "Noma’lum" - "Chiquvchi xabar ovozlari" - "SMS xabarni o‘tkazish" - "SMS orqali olingan qayta ishlanmagan ma’lumotlarni tashqi xotiraga o‘tkazish" - "MMS xabarni o‘tkazish" - "MMS orqali olingan qayta ishlanmagan ma’lumotlarni tashqi xotiraga o‘tkazish" - "Aholini ogohlantirish" - "Xabar sozlamalari" - "Matndan nusxa olish" - "Tafsilotlarni ko‘rish" - "O‘chirish" - "Qayta yo‘naltirish" - "Xabar tafsilotlari" - "Turi: " - "Matnli xabar" - "Multimediali xabar" - "Kimdan: " - "Kimga: " - "Yuborildi: " - "Qabul qilindi: " - "Mavzu: " - "Hajmi: " - "Ustivorlik: " - "SIM-karta: " - "Yuqori" - "O‘rtacha" - "Past" - "%s-SIM karta" - "Yuboruvchining manzili yashirilgan" - "Biriktirilgan fayllarni yuklab olayotganda xabar yuborib bo‘lmaydi." - "Biriktirma faylini yuklab bo‘lmadi. Qaytadan urining." - "Tarmoq hali tayyor emas. Qaytadan urining." - "Matnni o‘chirish" - "Harf va raqam kiritish o‘rtasida almashish" - "Yana ishtirokchi qo‘shish" - "Ishtirokchilarni tasdiqlang" - "Yangi suhbat boshlash" - "Ushbu variantni tanlash" - "Videoni ijro qilish" - "Odamlar va tanlamalar" - "Tuzatish" - "Odamlar va tanlamalar" - "Umumiy" - "Ushbu suhbat ishtirokchilari" - "Qo‘ng‘iroq qilish" - "Xabar yuborish" - "<br/><small>%s orqali</small> xabar yuboring" - - Rasmlarni yuborish - Rasmni yuborish - - - Audio faylni yuborish - Audio faylni yuborish - - - Videolarni yuborish - Videoni yuborish - - - Kontaktlar kartasini yuborish - Kontaktlar kartasini yuborish - - - Biriktirmani yuborish - Biriktirmani yuborish - - - %d ta biriktirma yuborishga tayyor - Biriktirma yuborishga tayyor - - "Fikr va mulohaza yuborish" - "Play Marketdan qidirish" - "Versiyasi" - "%1$s versiya" - "Ochiq kodli DT litsenziyalari" - "Bildirishnomalar" - "Biriktirma chekloviga yetdi" - "Biriktirmani yuklab bo‘lmadi." - "Kontaktlarga qo‘shilsinmi?" - "Kontakt qo‘shish" - "Mavzu" - "Mavzu: " - "%s%s" - "Kontaktlar kartasi yuklanmoqda" - "Kontaktlar kartasini yuklab bo‘lmadi" - "Kontaktlar kartasini ko‘rish" - - %d ta kontakt - %d ta kontakt - - "Kontakt kartalari" - "Tug‘ilgan kun" - "Qaydlar" - "Xabarni qayta yo‘naltirish" - "Javob berish" - "+1" - "+%d" - "SMS o‘chirib qo‘yilgan" - "Yuborish uchun SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating" - "SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating" - "O‘zgartirish" - "Xabarlarni qabul qilish uchun SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating" - "SMS xabarlarni yuborish uchun ma’qul SIM-karta tanlanmagan" - "Qurilma egasi ushbu ilovadan foydalanishga ruxsat bermagan." - "OK" - "Suhbatdagi ishtirokchilar soni juda ko‘p" - - Kontaktlar topilmadi - Kontakt topilmadi - - "Kameradagi rasmni yuklab bo‘lmadi" - "Siz: " - "%s: " - "Qoralama" - "Yangi suhbat boshlasangiz, u bu yerda ko‘rinadi" - "Arxivlangan suhbatlar bu yerda ko‘rinadi" - "Suhbatlar yuklanmoqda…" - "Rasm" - "Audio-klip" - "Video" - "Kontaktlar kartasi" - "MMS" - "Bekor qilish" - "Qayta urinish" - "Yangi xabar yozish uchun kontaktning ismi yoki telefon raqamini kiriting" - "Bloklash" - "Bloklash:%s" - "Blokdan chiqarish: %s" - "%s bloklansinmi?" - "Sizga ushbu raqamdan xabarlar kelishi mumkin, biroq bu haqda siz xabardor qilinmaysiz. Ushbu suhbat esa arxivlanadi." - "Bloklangan kontaktlar" - "BLOKDAN CHIQARISH" - "Bloklangan kontaktlar" - "Hujjatlar kutubxonasidan rasm tanlang" - "Xabar yuborilmoqda" - "Xabar yuborildi" - "Mobil internet o‘chirilgan. Sozlamalarni tekshiring." - "Parvoz rejimida xabarlarni yuborib bo‘lmaydi" - "Xabar yuborilmadi" - "Xabar yuklab olindi" - "Mobil internet o‘chirilgan. Sozlamalarni tekshiring." - "Parvoz rejimida xabarlarni yuklab olib bo‘lmaydi" - "Xabarni yuklab olib bo‘lmadi" - "Nol" - "Bir" - "Ikki" - "Uch" - "To‘rt" - "Besh" - "Olti" - "Yetti" - "Sakkiz" - "To‘qqiz" - "%1$s orqali xabar yuborib bo‘lmadi, xatolik kodi: %2$d" - "Noma’lum aloqa operatori orqali xabar yuborib bo‘lmadi, xatolik kodi: %1$d" - "Fwd: %s" - "Xabar yuborilmadi: xizmat tarmoqda faollashtirilmagan" - "Xabar yuborilmadi: qabul qiluvchining manzili noto‘g‘ri" - "Xabar yuborilmadi: xabar noto‘g‘ri" - "Xabar yuborilmadi: kontent turi qo‘llab-quvvatlanmaydi" - "Xabar yuborilmadi: xabar turi qo‘llab-quvvatlanmaydi" - "Xabar yuborilmadi: juda katta" - "Yangi xabar" - "Ko‘rish" - "Rasm" - "Mos ilovani topib bo‘lmadi" - "Qabul qiluvchini o‘chirish" - "Yangi xabar" - "Bekor qilish" - "Ulanish nuqtasi sozlamalari" - "Sozlanmagan" - "Nomi" - "APN" - "MMSC" - "MMS proksi-serveri" - "MMS porti" - "MCC" - "MNC" - "APN turi" - "APNni o‘chirish" - "Yangi APN" - "Saqlash" - "Bekor qilish" - "APN nomi maydonchasini to‘ldirish shart." - "APN maydonchasini to‘ldirish shart." - "MCC 3 ta raqamdan iborat bo‘lishi shart." - "MNC 2 yoki 3 ta raqamdan iborat bo‘lishi shart." - "Standart APN sozlamalari qayta tiklanmoqda." - "Standart sozlamalarni tiklash" - "Standart sozlamalar tiklandi." - "Nomsiz" - "Ulanish nuqtalari" - "APN" - "Yangi APN" - "Ushbu foydalanuvchi uchun Ulanish nuqtasi nomi (APN) sozlamalari mavjud emas" - "Vaqtinchalik xotiraga nusxa olinsinmi?" - "Nusxa olish" - "ushbu SIM-kartaga: %s" - "Umumiy" - "Qo‘shimcha" - "Umumiy sozlamalar" - "Qo‘shimcha sozlamalar" - "“%s” SIM kartasi" - "Har bir qabul qiluvchiga alohida SMS xabar yuboring. Faqat siz barcha javoblarni olishingiz mumkin" - "Barcha qabul qiluvchilarga bir xil MMS xabar yuboring" - "Noma’lum raqam" - "Yangi xabar" - "Yangi xabar." - "SIM kartani tanlash" - "%1$s SIM-kartasi tanlangan" - "Mavzuni tahrirlash" - "SIM kartani tanlang yoki mavzuni tahrirlang" - "Audio yozib olish uchun bosib turing" - "Yangi suhbat boshlash" - "SMS/MMS" - "SMS/MMS chatlari" - "SMS/MMS" - "Yangi xabar" - "Suhbatlar ro‘yxati" - "Suhbatlar yuklanmoqda" - "Xabarlar yuklanmoqda" - "Ko‘proq suhbatlarni ko‘rish" - "Boshqa xabarlarni ko‘rish" - "Suhbat o‘chirib tashlandi" - "Suhbat o‘chirib tashlandi. SMS/MMS xizmatidagi boshqa suhbatni ko‘rish uchun bosing." - "Bloklandi" - "Blokdan chiqarildi" - "Xotirada joy kam qoldi. Ba’zi ma’lumotlar saqlanmasligi mumkin." - "Biriktirmalarni tanlash" - "Tanlovni tasdiqlash" - "%d ta tanlandi" - "Bir yoki bir nechta biriktirmani olib tashlab, keyin qayta urinib ko‘ring." - "Xabarni yuborib ko‘rishingiz mumkin, lekin undagi bir yoki bir nechta biriktirma olib tashlanmaguncha qabul qiluvchiga yetib bormasligi mumkin." - "Siz bitta xabarda faqat bitta video jo‘nata olasiz. Ortiqcha videolarni olib tashlang va qaytadan urinib ko‘ring." - "Biriktirmani yuklab bo‘lmadi." - "Baribir yuborilsin" - "Suhbatni boshlab bo‘lmadi" - "%1$s (%2$s)" - "%s tanlandi" - diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml deleted file mode 100644 index 01cec58..0000000 --- a/res/values-v17/styles.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values-v21/colors.xml b/res/values-v21/colors.xml deleted file mode 100644 index d51ad04..0000000 --- a/res/values-v21/colors.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - #DE000000 - #8A000000 - #8A000000 - #DE000000 - diff --git a/res/values-v21/dimens.xml b/res/values-v21/dimens.xml deleted file mode 100644 index 466d2c8..0000000 --- a/res/values-v21/dimens.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - 16dp - 16dp - 56dp - 0dp - diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml deleted file mode 100644 index aef5d1c..0000000 --- a/res/values-v21/styles.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - diff --git a/res/values-vi/arrays.xml b/res/values-vi/arrays.xml deleted file mode 100644 index b16f18b..0000000 --- a/res/values-vi/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "không có chủ đề" - "không có chủ đề" - - - "Có" - "Không" - "OK" - "Hehe" - "Xin cảm ơn" - "Tôi đồng ý" - "Tuyệt vời" - "Đang trên đường" - "OK, tôi sẽ liên hệ lại với bạn sau" - ":)" - ":(" - - diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml deleted file mode 100644 index ac23771..0000000 --- a/res/values-vi/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Nhắn tin" - "Nhắn tin" - "Chọn cuộc hội thoại" - "Cài đặt" - "Gửi tin nhắn" - "Thêm tệp đính kèm" - "Trợ giúp" - "Chào mừng" - "Bỏ qua" - "Tiếp theo >" - "Tiếp theo" - "Thoát" - "Cài đặt >" - "Cài đặt" - "Nhắn tin cần quyền truy cập vào SMS, Điện thoại và Danh bạ." - "Bạn có thể thay đổi các quyền trong Cài đặt > Ứng dụng > Nhắn tin > Quyền." - "Bạn có thể thay đổi các quyền trong Cài đặt, Ứng dụng, Nhắn tin, Quyền." - "Liên hệ thường xuyên" - "Tất cả liên hệ" - "Gửi đến %s" - "Chụp ảnh hoặc quay video" - "Chọn hình ảnh từ thiết bị này" - "Ghi âm" - "Chọn ảnh" - "Phương tiện được chọn." - "Phương tiện không được chọn." - "Đã chọn %d" - "hình ảnh %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "hình ảnh" - "Ghi âm" - "Chia sẻ" - "Vừa đăng" - "Hiện tại" - - %d phút - %d phút - - - %d giờ - %d giờ - - - %d ngày - %d ngày - - - %d tuần - một tuần - - - %d tháng - một tháng - - - %d năm - một năm - - "Thông báo hạng 0" - "Lưu" - "Thiết bị sắp hết bộ nhớ. Nhắn tin sẽ tự động xóa các tin nhắn cũ để giải phóng dung lượng." - "Sắp hết dung lượng lưu trữ" - "Nhắn tin không thể gửi hoặc nhận tin nhắn cho tới khi có thêm bộ nhớ trên thiết bị của bạn." - "Sắp hết dung lượng SMS. Bạn có thể cần xóa tin nhắn." - "Xác nhận số điện thoại của bạn" - "Bước thực hiện một lần này sẽ đảm bảo ứng dụng Nhắn tin gửi tin nhắn nhóm của bạn đúng cách." - "Số điện thoại" - "Xóa tất cả tin nhắn có phương tiện" - "Xóa tin nhắn cũ hơn %s" - "Tự động xóa tin nhắn cũ hơn %s" - "Bỏ qua" - "Xóa tất cả tin nhắn có phương tiện?" - "Xóa tin nhắn cũ hơn %s?" - "Xóa tin nhắn cũ hơn %s và bật tự động xóa?" - "%s đã nói" - "Bạn đã nói" - "Tin nhắn từ %s:" - "Bạn đã gửi tin nhắn" - "Đang gửi…" - "Không gửi được. Hãy chạm để thử lại." - "Không gửi được. Đang thử lại…" - "Gửi lại hoặc xóa" - "Vui lòng thực hiện cuộc gọi thoại tới các dịch vụ khẩn cấp. Chúng tôi hiện không thể gửi tin nhắn văn bản của bạn." - "Không thành công" - "Tin nhắn MMS mới cần tải xuống" - "Tin nhắn MMS mới" - "Không thể tải xuống" - "Chạm để thử lại" - "Chạm để tải xuống" - "Tải xuống hoặc xóa" - "Đang tải xuống..." - "Tin nhắn đã hết hạn hoặc không khả dụng" - "kích thước: %1$s, hết hạn: %2$s" - "Không thể gửi. Người nhận không hợp lệ." - "Dịch vụ không được kích hoạt trên mạng" - "Không thể gửi do sự cố mạng" - "Tin nhắn đã hết hạn hoặc không khả dụng" - "(Không có chủ đề)" - "Người gửi không xác định" - "Đã gửi" - "Không thể tải xuống tin nhắn %1$s từ %2$s." - "Không thể hoàn tất thao tác liên quan đến cơ sở dữ liệu do sắp hết bộ nhớ" - "Không gửi được tin nhắn" - "Không gửi được một số tin nhắn trong Nhắn tin" - - %d tin nhắn trong %d cuộc trò chuyện - %d tin nhắn trong một cuộc trò chuyện - - "Không tải xuống được tin nhắn" - "Không tải xuống được một số tin nhắn trong Nhắn tin" - - %d tin nhắn trong %d cuộc trò chuyện - %d tin nhắn trong một cuộc trò chuyện - - "Tin nhắn tới %1$s chưa được gửi" - "Vui lòng thực hiện cuộc gọi thoại tới các dịch vụ khẩn cấp. Chúng tôi hiện không thể gửi tin nhắn văn bản của bạn tới %1$s." - - %d tin nhắn mới - Tin nhắn mới - - "Bắt đầu" - "Máy ảnh không khả dụng" - "Máy ảnh không khả dụng" - "Quay video không khả dụng" - "Không thể lưu phương tiện" - "Không thể chụp ảnh" - "Quay lại" - "Đã lưu trữ" - "Xóa" - "Lưu trữ" - "Hủy lưu trữ" - "Tắt thông báo" - "Bật thông báo" - "Thêm liên hệ" - "Tải xuống" - "Gửi" - "Xóa" - "Xóa tin nhắn này?" - "Không thể hoàn tác tác vụ này." - "Xóa" - - Xóa những cuộc trò chuyện này? - Xóa cuộc trò chuyện này? - - "Xóa" - "Hủy" - "Đến" - "Chọn nhiều hình ảnh" - "Xác nhận lựa chọn" - "+%d" - "Không thể ghi âm thanh. Hãy thử lại." - "Không thể phát âm thanh. Hãy thử lại." - "Không thể lưu âm thanh. Hãy thử lại." - "Chạm và giữ" - ", " - " " - ": " - " " - "Hình ảnh" - "Clip âm thanh" - "Video" - "Thẻ liên hệ" - "Tải xuống" - "Trả lời qua SMS" - "Trả lời qua MMS" - "Trả lời" - - %d người tham gia - %d người tham gia - - "Tôi" - "Đã chặn và lưu trữ địa chỉ liên hệ" - "Đã bỏ chặn và hủy lưu trữ liên hệ" - "Đã lưu trữ %d" - "Đã hủy lưu trữ %d" - "Đã tắt thông báo" - "Đã bật thông báo" - "Đã hoàn tất. Hãy chạm vào Gửi lần nữa." - "Đã đặt thành công Nhắn tin là ứng dụng SMS mặc định." - - Hủy tệp đính kèm - Hủy tệp đính kèm - - "Tệp âm thanh đính kèm" - "Phát tệp âm thanh đính kèm" - "Tạm dừng" - "Tin nhắn từ %s: %s." - "Không gửi được tin nhắn từ %s: %s. Thời gian: %s." - "Tin nhắn từ %s: %s. Thời gian: %s." - "Chưa gửi được tin nhắn tới %s: %s. Thời gian: %s." - "Đang gửi tin nhắn tới %s: %s. Thời gian: %s." - "Không gửi được tin nhắn tới %s: %s. Thời gian: %s." - "Tin nhắn tới %s: %s. Thời gian: %s." - "Không gửi được tin nhắn từ %s: %s. Thời gian: %s. %s." - "Tin nhắn từ %s: %s. Thời gian: %s. %s." - "Chưa gửi được tin nhắn tới %s: %s. Thời gian: %s." - "Đang gửi tin nhắn tới %s: %s. Thời gian: %s." - "Không gửi được tin nhắn tới %s: %s. Thời gian: %s." - "Tin nhắn tới %s: %s. Thời gian: %s." - "Không gửi được tin nhắn. Hãy chạm để thử lại." - "Cuộc trò chuyện với %s" - "Xóa chủ đề" - "Quay video" - "Chụp ảnh tĩnh" - "Chụp ảnh" - "Bắt đầu quay video" - "Chuyển sang máy ảnh toàn màn hình" - "Chuyển đổi giữa máy ảnh trước và sau" - "Dừng quay và đính kèm video" - "Dừng ghi video" - "Ảnh trong Nhắn tin" - - %d ảnh đã được lưu vào album \"%s\" - %d ảnh đã được lưu vào album \"%s\" - - - %d video đã được lưu vào album \"%s\" - %d video đã được lưu vào album \"%s\" - - - %d tệp đính kèm đã được lưu vào album \"%s\" - %d tệp đính kèm đã được lưu vào album \"%s\" - - - %d tệp đính kèm đã được lưu vào \"Nội dung tải xuống\" - %d tệp đính kèm đã được lưu vào \"Nội dung tải xuống\" - - - Đã lưu %d tệp đính kèm - Đã lưu %d tệp đính kèm - - - Không thể lưu %d tệp đính kèm - Không thể lưu %d tệp đính kèm - - "Đã lưu tệp đính kèm MMS" - "Cài đặt" - "Đã lưu trữ" - "Đóng" - "MMS" - "Nâng cao" - "Gỡ lỗi" - "Thông báo" - "Âm thanh" - "Im lặng" - "Rung" - "Đã chặn" - "Báo cáo gửi SMS" - "Yêu cầu báo cáo gửi cho mỗi SMS bạn gửi" - "Tự động truy xuất" - "Tự động truy xuất tin nhắn MMS" - "Tự động truy xuất khi chuyển vùng" - "Tự động truy xuất MMS khi chuyển vùng" - "Nhắn tin theo nhóm" - "Sử dụng MMS để gửi một tin nhắn duy nhất khi có nhiều người nhận" - "Ứng dụng SMS mặc định" - "Ứng dụng SMS mặc định" - - - "Số điện thoại của bạn" - "Không xác định" - "Âm thanh tin nhắn đi" - "Kết xuất SMS" - "Kết xuất dữ liệu thô SMS đã nhận vào tệp bộ nhớ ngoài" - "Kết xuất MMS" - "Kết xuất dữ liệu thô MMS đã nhận vào tệp bộ nhớ ngoài" - "Cảnh báo không dây" - "Tùy chọn tin nhắn" - "Sao chép văn bản" - "Xem chi tiết" - "Xóa" - "Chuyển tiếp" - "Chi tiết tin nhắn" - "Loại: " - "Tin nhắn văn bản" - "Tin nhắn đa phương tiện" - "Người gửi: " - "Người nhận: " - "Đã gửi: " - "Đã nhận: " - "Chủ đề: " - "Kích cỡ: " - "Mức độ ưu tiên: " - "SIM: " - "Cao" - "Bình thường" - "Thấp" - "SIM %s" - "Địa chỉ người gửi bị ẩn" - "Không thể gửi tin nhắn trong khi tải tệp đính kèm." - "Không thể tải tệp đính kèm. Hãy thử lại." - "Mạng chưa sẵn sàng. Hãy thử lại." - "Xóa văn bản" - "Chuyển đổi giữa nhập văn bản và số" - "Thêm người tham gia khác" - "Xác nhận người tham gia" - "Bắt đầu cuộc trò chuyện mới" - "Chọn mục này" - "Phát video" - "Danh bạ và tùy chọn" - "Gỡ lỗi" - "Danh bạ và tùy chọn" - "Chung" - "Mọi người trong cuộc hội thoại này" - "Gọi điện" - "Gửi tin nhắn" - "Gửi tin nhắn<br/><nhỏ>từ %s</nhỏ>" - - Gửi ảnh - Gửi ảnh - - - Gửi âm thanh - Gửi âm thanh - - - Gửi video - Gửi video - - - Gửi danh thiếp - Gửi danh thiếp - - - Gửi tệp đính kèm - Gửi tệp đính kèm - - - %d tệp đính kèm đã sẵn sàng gửi - Một tệp đính kèm đã sẵn sàng gửi - - "Gửi phản hồi" - "Xem trong Cửa hàng Google Play" - "Thông tin phiên bản" - "Phiên bản %1$s" - "Giấy phép nguồn mở" - "Thông báo" - "Đã đạt đến giới hạn tệp đính kèm" - "Không tải được tệp đính kèm." - "Thêm vào Danh bạ?" - "Thêm liên hệ" - "Chủ đề" - "Chủ đề: " - "%s%s" - "Đang tải thẻ liên hệ" - "Không thể tải danh thiếp" - "Xem thẻ liên hệ" - - %d liên hệ - %d liên hệ - - "Danh thiếp" - "Ngày sinh" - "Ghi chú" - "Chuyển tiếp thư" - "Trả lời" - "+1" - "+%d" - "Đã tắt tính năng nhắn tin SMS" - "Để gửi, hãy đặt Nhắn tin là ứng dụng SMS mặc định" - "Đặt Nhắn tin là ứng dụng SMS mặc định" - "Thay đổi" - "Để nhận tin nhắn, hãy đặt Nhắn tin là ứng dụng SMS mặc định" - "Không có SIM ưu tiên nào được chọn để gửi tin nhắn SMS" - "Ứng dụng này không được chủ sở hữu thiết bị cho phép." - "OK" - "Có quá nhiều người tham gia trong một cuộc trò chuyện" - - Liên hệ không hợp lệ - Liên hệ không hợp lệ - - "Không thể tải hình ảnh trong máy ảnh" - "Bạn: " - "%s: " - "Thư nháp" - "Sau khi bạn bắt đầu một cuộc trò chuyện mới, bạn sẽ thấy cuộc trò chuyện đó được liệt kê ở đây" - "Các cuộc trò chuyện đã lưu trữ xuất hiện ở đây" - "Đang tải cuộc trò chuyện..." - "Hình ảnh" - "Clip âm thanh" - "Video" - "Thẻ liên hệ" - "MMS" - "Hoàn tác" - "Thử lại" - "Nhập tên liên hệ hoặc số điện thoại để bắt đầu tin nhắn mới" - "Chặn" - "Chặn %s" - "Bỏ chặn %s" - "Chặn %s?" - "Bạn sẽ tiếp tục nhận tin nhắn từ số này nhưng bạn sẽ không được thông báo nữa. Cuộc trò chuyện này sẽ được lưu trữ." - "Địa chỉ liên hệ bị chặn" - "BỎ CHẶN" - "Địa chỉ liên hệ bị chặn" - "Chọn hình ảnh từ thư viện tài liệu" - "Đang gửi tin nhắn" - "Đã gửi tin nhắn" - "Dữ liệu di động đã bị tắt. Hãy kiểm tra cài đặt của bạn." - "Không thể gửi tin nhắn ở Chế độ trên máy bay" - "Không thể gửi thư" - "Đã tải tin nhắn xuống" - "Dữ liệu di động đã bị tắt. Hãy kiểm tra cài đặt của bạn." - "Không thể tải xuống tin nhắn ở Chế độ trên máy bay" - "Không thể tải xuống tin nhắn" - "Không" - "Một" - "Hai" - "Ba" - "Bốn" - "Năm" - "Sáu" - "Bảy" - "Tám" - "Chín" - "Không thể gửi tin nhắn bằng %1$s, lỗi %2$d" - "Không thể gửi tin nhắn bằng nhà cung cấp dịch vụ không xác định, lỗi %1$d" - "Chuyển tiếp: %s" - "Không gửi được tin nhắn: dịch vụ chưa được kích hoạt trên mạng" - "Không gửi được tin nhắn: địa chỉ đích không hợp lệ" - "Không gửi được tin nhắn: tin nhắn không hợp lệ" - "Không gửi được tin nhắn: nội dung không được hỗ trợ" - "Không gửi được tin nhắn: tin nhắn không được hỗ trợ" - "Không gửi được tin nhắn: quá lớn" - "Tin nhắn mới" - "Xem" - "Hình ảnh" - "Không thể tìm thấy ứng dụng thích hợp" - "Xóa người nhận" - "Tin nhắn mới" - "Hủy" - "Chỉnh sửa điểm truy cập" - "Chưa đặt" - "Tên" - "APN" - "MMSC" - "Proxy của MMS" - "Cổng MMS" - "MCC" - "MNC" - "Loại APN" - "Xóa APN" - "APN mới" - "Lưu" - "Hủy" - "Không được để trống trường Tên." - "Không được để trống APN." - "Trường MCC phải có 3 chữ số." - "Trường MNC phải có 2 hoặc 3 chữ số." - "Đang khôi phục cài đặt APN mặc định." - "Đặt lại về mặc định" - "Đặt lại cài đặt APN mặc định đã hoàn tất." - "Không có tiêu đề" - "Tên điểm truy cập" - "APN" - "APN mới" - "Cài đặt Tên điểm truy cập không khả dụng cho người dùng này" - "Sao chép vào khay nhớ tạm?" - "Sao chép" - "tới %s" - "Chung" - "Nâng cao" - "Cài đặt chung" - "Cài đặt nâng cao" - "SIM \"%s\"" - "Gửi từng tin nhắn SMS cho tất cả người nhận. Chỉ bạn mới nhận được mọi tin nhắn trả lời" - "Gửi một MMS cho tất cả người nhận" - "Số điện thoại không xác định" - "Tin nhắn mới" - "Tin nhắn mới." - "Bộ chọn SIM" - "Đã chọn %1$s, bộ chọn SIM" - "Chỉnh sửa chủ đề" - "Chọn SIM hoặc chỉnh sửa chủ đề" - "Chạm và giữ để ghi âm" - "Bắt đầu cuộc trò chuyện mới" - "Nhắn tin" - "Danh sách Nhắn tin" - "Nhắn tin" - "Tin nhắn mới" - "Danh sách cuộc hội thoại" - "Đang tải cuộc trò chuyện..." - "Đang tải thư" - "Xem các cuộc hội thoại khác" - "Xem thêm tin nhắn" - "Đã xóa cuộc hội thoại" - "Đã xóa cuộc trò chuyện. Chạm để hiển thị cuộc trò chuyện khác trong Nhắn tin" - "Đã chặn" - "Đã bỏ chặn" - "Dung lượng bộ nhớ thấp. Một số dữ liệu có thể bị mất." - "Chọn tài liệu đính kèm" - "Xác nhận lựa chọn" - "Đã chọn %d" - "Vui lòng xóa một hoặc nhiều tệp đính kèm và thử lại." - "Bạn có thể thử gửi tin nhắn của mình nhưng tin nhắn có thể không gửi được trừ khi bạn xóa một hoặc nhiều tệp đính kèm." - "Bạn chỉ có thể gửi một video cho mỗi tin nhắn. Hãy xóa các video khác rồi thử lại." - "Ứng dụng Nhắn tin không tải được tệp đính kèm." - "Vẫn gửi" - "Không thể bắt đầu cuộc trò chuyện" - "%1$s (%2$s)" - "Đã chọn %s" - diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml deleted file mode 100644 index 9512884..0000000 --- a/res/values-zh-rCN/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "无主题" - "无主题" - - - "是的" - "不" - "好" - "呵呵" - "谢谢" - "同意" - "不错" - "在路上" - "稍后再回复你" - ":)" - ":(" - - diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml deleted file mode 100644 index 1a128fc..0000000 --- a/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "短信" - "短信" - "选择对话" - "设置" - "发送信息" - "添加附件" - "帮助" - "欢迎使用" - "跳过" - "下一步 >" - "下一步" - "退出" - "设置 >" - "设置" - "“短信”应用需要获得“短信”、“电话”和“通讯录”的访问权限。" - "您可以在“设置”>“应用”>“短信”>“权限”中更改权限。" - "您可以依次触摸“设置”、“应用”、“短信”和“权限”来更改权限。" - "常用联系人" - "所有联系人" - "发送到%s" - "拍照或录像" - "选择此设备上的图片" - "录制语音" - "选择照片" - "已选择此媒体。" - "已取消选择此媒体。" - "已选择%d项" - "图片(%1$tB %1$te %1$tY %1$tl %1$tM %1$tp)" - "图片" - "录制语音" - "分享" - "刚刚" - "刚刚" - - %d 分钟 - %d 分钟 - - - %d 小时 - %d 小时 - - - %d - %d - - - %d - 1 周 - - - %d 个月 - 1 个月 - - - %d - 1 年 - - "0级信息" - "保存" - "设备存储空间不足。“短信”将自动删除较早的信息,以便腾出存储空间。" - "存储空间不足" - "除非您的设备上腾出更多可用空间,否则“短信”可能无法收发信息。" - "短信存储空间不足,建议您删除部分信息。" - "确认您的电话号码" - "执行这个一次性步骤可确保“短信”正确地传送您的群发信息。" - "电话号码" - "删除所有含媒体内容的信息" - "删除%s以前的信息" - "自动删除超过 %s以前的信息" - "忽略" - "要删除所有含媒体内容的信息吗?" - "要删除超过 %s以前的信息吗?" - "要删除超过 %s以前的信息并开启自动删除功能吗?" - "%s说" - "您说" - "来自%s的信息" - "您发送了一条信息" - "正在发送…" - "发送失败。触摸即可重试。" - "发送失败。正在重试…" - "重新发送或删除" - "请拨打语音电话寻求紧急服务。目前您的信息无法成功发送。" - "失败" - "待下载的新彩信" - "新彩信" - "无法下载" - "触摸即可重试" - "触摸即可下载" - "下载或删除" - "正在下载…" - "信息已过期或无法查看" - "信息大小:%1$s,过期时间:%2$s" - "无法发送。收件人无效。" - "您尚未在网络上启用该服务。" - "遇到网络问题,发送失败" - "信息已过期或无法查看" - "(无主题)" - "未知发件人" - "已送达" - "无法下载来自%2$s的信息“%1$s”。" - "内存不足,无法完成数据库操作" - "信息发送失败" - "“短信”未发送部分信息" - - %d 个对话中的 %d 条信息 - 1 个对话中的 %d 条信息 - - "信息下载失败" - "“短信”未下载部分信息" - - %d 个对话中的 %d 条信息 - 1 个对话中的 %d 条信息 - - "未能将信息发送至 %1$s" - "请拨打语音电话寻求紧急服务。目前您的信息无法发送至 %1$s。" - - %d 条新信息 - 新信息 - - "发起" - "无法使用相机" - "无法使用相机" - "无法使用视频拍摄功能" - "无法保存媒体" - "无法拍照" - "返回" - "已归档的对话" - "删除" - "归档" - "取消归档" - "关闭通知" - "开启通知" - "添加到通讯录" - "下载" - "发送" - "删除" - "要删除此信息吗?" - "该操作无法撤消。" - "删除" - - 要删除这些对话吗? - 要删除此对话吗? - - "删除" - "取消" - "收件人" - "选择多张图片" - "确认选择" - "+%d" - "无法录制语音,请重试。" - "无法播放语音,请重试。" - "无法保存语音,请重试。" - "触摸并按住" - "、 " - " " - ": " - " " - "图片" - "语音" - "视频" - "名片" - "下载" - "通过短信回复" - "通过彩信回复" - "回复" - - %d 位参与者 - %d 位参与者 - - "我" - "联系人已被屏蔽且已归档" - "已取消屏蔽并取消归档联系人" - "已归档 %d 个" - "已取消归档 %d 个" - "通知已关闭" - "通知已开启" - "设置已完成。请再次触摸“发送”。" - "已成功地将“短信”设为默认短信应用。" - - 舍弃附件 - 舍弃附件 - - "语音附件" - "播放语音附件" - "暂停" - "%s发来了消息:%s。" - "%s未能成功发送的信息,内容为:%s。时间为:%s。" - "%s发来的信息,内容为:%s。时间为:%s。" - "要发送给%s的草稿信息,内容为:%s。时间为:%s。" - "正在向%s发送的信息,内容为:%s。时间为:%s。" - "未能成功发送给%s的信息,内容为:%s。时间为:%s。" - "发送给%s的信息,内容为:%s。时间为:%s。" - "%s未能成功发送的信息,内容为:%s。时间为:%s%s。" - "%s发来的信息,内容为:%s。时间为:%s%s。" - "要发送给%s的草稿信息,内容为:%s。时间为:%s。" - "正在向%s发送的信息,内容为:%s。时间为:%s。" - "未能成功发送给%s的信息,内容为:%s。时间为:%s。" - "发送给%s的信息,内容为:%s。时间为:%s。" - "信息发送失败。触摸即可重试。" - "与%s的对话" - "删除主题" - "拍摄视频" - "拍摄静态照片" - "拍照" - "开始录制视频" - "切换到全屏相机模式" - "在前置和后置相机之间切换" - "停止拍摄并添加视频附件" - "停止录制视频" - "“短信”中的照片" - - %d 张照片已保存至“%s”相册 - %d 张照片已保存至“%s”相册 - - - %d 个视频已保存至“%s”相册 - %d 个视频已保存至“%s”相册 - - - %d 个附件已保存至“%s”相册 - %d 个附件已保存至“%s”相册 - - - %d 个附件已保存至“下载内容” - %d 个附件已保存至“下载内容” - - - 已保存 %d 个附件 - 已保存 %d 个附件 - - - 无法保存 %d 个附件 - 无法保存 %d 个附件 - - "保存的彩信附件" - "设置" - "已归档的对话" - "关闭" - "彩信" - "高级" - "调试" - "通知" - "提示音" - "静音" - "振动" - "已屏蔽" - "短信送达情况报告" - "了解您发的每条短信的送达情况" - "自动检索" - "自动检索彩信" - "漫游时自动检索" - "漫游时自动检索彩信" - "群发彩信" - "有多个收件人时,也使用彩信发送信息" - "默认短信应用" - "默认短信应用" - - - "您的手机号码" - "未知" - "信息发送提示音" - "转储短信" - "将收到的短信原始数据转储到外部存储文件中" - "转储彩信" - "将收到的彩信原始数据转储到外部存储文件中" - "无线警报" - "信息选项" - "复制文字" - "查看详情" - "删除" - "转发" - "信息详情" - "类型: " - "文字信息" - "多媒体信息" - "发件人: " - "收件人: " - "发送时间: " - "接收时间: " - "主题: " - "大小: " - "优先级: " - "SIM 卡: " - "高" - "中" - "低" - "SIM 卡 %s" - "隐藏的发件人地址" - "附件尚未加载完毕,无法发送信息。" - "无法加载附件,请重试。" - "网络尚未就绪,请重试。" - "删除文字" - "在文字和数字输入模式间切换" - "添加更多参与者" - "确认参与者" - "发起新的对话" - "选择此项内容" - "播放视频" - "参与者名单和选项" - "调试" - "参与者名单和选项" - "常规" - "此对话的参与者" - "拨打电话" - "发送信息" - "发送信息<br/><small>(通过%s)</small>" - - 发送照片 - 发送照片 - - - 发送语音 - 发送语音 - - - 发送视频 - 发送视频 - - - 发送名片 - 发送名片 - - - 发送附件 - 发送附件 - - - %d 个附件待发送 - 1 个附件待发送 - - "发送反馈" - "在 Google Play 商店中查看" - "版本信息" - "版本%1$s" - "开放源代码许可" - "通知" - "已达到附件上限" - "加载附件失败。" - "要添加到通讯录吗?" - "添加到通讯录" - "主题" - "主题: " - "%s%s" - "正在加载名片" - "无法加载名片" - "查看名片" - - %d 位联系人 - %d 位联系人 - - "名片" - "生日" - "备注" - "转发信息" - "回复" - "+1" - "+%d" - "短信功能已停用" - "要发送信息,请将“短信”设为默认短信应用" - "将“短信”设为默认短信应用" - "更改" - "要接收信息,请将“短信”设为默认短信应用" - "未选择用于发送短信的首选 SIM 卡" - "此应用未获得设备机主授权。" - "确定" - "对话中的参与者过多" - - 联系人无效 - 联系人无效 - - "无法加载相机图像" - "你: " - "%s: " - "草稿" - "您的短信对话将列在此处" - "已归档的对话会显示在此处" - "正在加载对话…" - "照片" - "语音" - "视频" - "名片" - "彩信" - "撤消" - "重试" - "要发送新信息,请输入联系人姓名或电话号码" - "屏蔽" - "屏蔽%s" - "取消屏蔽%s" - "要屏蔽%s吗?" - "您将继续接收来自此号码的信息,但不会再收到相关通知。此对话将被归档。" - "屏蔽的联系人" - "取消屏蔽" - "屏蔽的联系人" - "从文档库中选择图片" - "正在发送消息" - "消息已发送" - "移动数据网络已关闭。请检查您的设置。" - "在飞行模式下无法发送信息" - "无法发送信息" - "彩信已下载" - "移动数据网络已关闭。请检查您的设置。" - "在飞行模式下无法下载信息" - "无法下载信息" - "0" - "1" - "2" - "3" - "4" - "5" - "6" - "7" - "8" - "9" - "无法发送消息(运营商:%1$s,错误代码:%2$d)" - "无法发送消息(运营商:未知,错误代码:%1$d)" - "转发:%s" - "信息发送失败:服务未在网络上激活" - "信息发送失败:目标地址无效" - "信息发送失败:信息无效" - "信息发送失败:内容不受支持" - "信息发送失败:信息不受支持" - "信息发送失败:信息过长" - "新信息" - "查看" - "图片" - "找不到合适的应用" - "移除收件人" - "新消息" - "取消" - "修改接入点" - "未设置" - "名称" - "APN" - "MMSC" - "彩信代理" - "彩信端口" - "MCC" - "MNC" - "APN 类型" - "删除 APN" - "新建 APN" - "保存" - "舍弃" - "“名称”字段不能为空。" - "APN 不能为空。" - "MCC 字段必须为 3 位数。" - "MNC 字段必须为 2 位数或 3 位数。" - "正在恢复默认 APN 设置。" - "重置为默认值" - "已重置默认 APN 设置。" - "未命名" - "接入点名称" - "APN" - "新建 APN" - "此用户无权修改接入点名称设置" - "要复制到剪贴板吗?" - "复制" - "发至%s" - "常规" - "高级" - "常规设置" - "高级设置" - "SIM 卡 “%s”" - "将单条短信发送给所有收件人。只有您能收到相应回复" - "将单条彩信发送给所有收件人" - "未知号码" - "新信息" - "新信息。" - "SIM 卡选择器" - "已选择%1$s,SIM 卡选择器" - "修改主题" - "选择 SIM 卡或修改主题" - "触摸并按住即可录制语音" - "发起新的对话" - "短信" - "短信列表" - "短信" - "新信息" - "对话列表" - "正在加载对话" - "正在加载信息" - "查看更多对话" - "查看更多信息" - "对话已删除" - "对话已删除。触摸可显示其他“短信”对话" - "已屏蔽" - "已取消屏蔽" - "存储空间不足,部分数据可能会丢失。" - "选择附件" - "确认选择" - "已选择 %d 个附件" - "请移除一个或多个附件,然后重试。" - "您可以尝试发送信息,但您可能需要移除一个或多个附件才能成功发送。" - "您在每条消息中只能发送一个视频。请移除多余的视频,然后重试。" - "“短信”无法加载附件。" - "仍然发送" - "无法发起对话" - "%1$s (%2$s)" - "已选择%s" - diff --git a/res/values-zh-rHK/arrays.xml b/res/values-zh-rHK/arrays.xml deleted file mode 100644 index ab1cad4..0000000 --- a/res/values-zh-rHK/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "沒有主旨" - "無主旨" - - - "是" - "否" - "確定" - "呵呵" - "謝謝" - "我同意" - "很好" - "我正在路上" - "好,我稍後再回覆您" - ":)" - ":(" - - diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml deleted file mode 100644 index a93ba92..0000000 --- a/res/values-zh-rHK/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "訊息" - "訊息" - "選取對話群組" - "設定" - "傳送訊息" - "新增附件" - "說明" - "歡迎" - "略過" - "繼續 >" - "下一步" - "結束" - "設定 >" - "設定" - "「短訊」需要存取短訊、手機和聯絡人的權限。" - "您可以前往 [設定] > [應用程式] > [短訊] > [權限] 變更權限。" - "您可以前往 [設定]、[應用程式]、[短訊]、[權限] 變更權限。" - "常用聯絡人" - "所有聯絡人" - "傳送至 %s" - "拍照或錄影" - "從這部裝置選擇圖片" - "錄音" - "選擇相片" - "已選取媒體。" - "已取消選取媒體。" - "已選取 %d 個項目" - "%1$tB %1$te %1$tY %1$tl %1$tM %1$tp的圖片" - "圖片" - "錄音" - "分享" - "剛剛" - "現在" - - %d分鐘 - %d分鐘 - - - %d小時 - %d小時 - - - %d - %d - - - %d個星期 - 一個星期 - - - %d個月 - 一個月 - - - %d - 一年 - - "等級 0 訊息" - "儲存" - "裝置儲存空間不足。「短訊」會自動刪除較舊的訊息以騰出空間。" - "儲存空間即將用盡" - "「短訊」需要更多裝置空間方可發送或接收訊息。" - "短訊儲存空間即將用完,建議您刪除部分訊息。" - "確認您的電話號碼" - "這個單一步驟能確保「短訊」正確發送群組訊息。" - "電話號碼" - "刪除所有附有媒體的訊息" - "刪除超過 %s的訊息" - "自動刪除超過 %s的訊息" - "略過" - "要刪除所有附有媒體檔案的訊息嗎?" - "要刪除超過 %s的訊息嗎?" - "要刪除超過 %s的訊息並開啟自動刪除嗎?" - "傳送者:%s" - "您說" - "來自%s的訊息" - "您傳送了訊息" - "正在傳送…" - "訊息傳送失敗,輕觸即可再試一次。" - "訊息傳送失敗,正在重試…" - "重新傳送或刪除" - "請致電緊急服務。目前,您的短訊無法送達。" - "失敗" - "下載新的多媒體訊息" - "新的多媒體訊息" - "無法下載" - "輕觸即可再試一次" - "輕觸即可下載" - "下載或刪除" - "正在下載…" - "訊息已過期或無法取得" - "大小:%1$s,期限:%2$s" - "收件者無效,因此無法傳送訊息。" - "您還未在網絡上啟用這項服務" - "網絡發生問題,因此無法傳送" - "訊息已過期或無法取得" - "(沒有主旨)" - "未知的寄件者" - "已傳送" - "無法下載 %2$s 傳送的這則訊息「%1$s」。" - "記憶體不足,因此無法完成資料庫操作" - "無法傳送訊息" - "部分訊息未能在「短訊」中發送" - - %d個對話中的%d則訊息 - 一個對話中的%d則訊息 - - "仍未下載訊息" - "部分訊息未能在「短訊」中下載" - - %d個對話中的%d則訊息 - 一個對話中的%d則訊息 - - "未能傳送訊息至 %1$s" - "請致電緊急服務。目前,您的短訊無法送達至 %1$s。" - - %d 則新訊息 - 新訊息 - - "開始" - "無法使用相機" - "無法使用相機" - "無法錄製影片" - "無法儲存媒體檔案" - "無法拍照" - "返回" - "已封存" - "刪除" - "封存" - "取消封存" - "關閉通知" - "開啟通知" - "新增聯絡人" - "下載" - "傳送" - "刪除" - "要刪除這個訊息嗎?" - "此操作將無法復原。" - "刪除" - - 要刪除這些對話嗎? - 要刪除這個對話嗎? - - "刪除" - "取消" - "收件者︰" - "選取多個圖片" - "確認選取" - "超過 %d 個項目" - "無法錄製語音,請再試一次。" - "無法播放語音,請再試一次。" - "無法儲存語音,請再試一次。" - "輕觸並按住" - "、 " - " " - ": " - " " - "圖片" - "錄音片段" - "影片" - "聯絡人卡" - "下載" - "透過短訊回覆" - "透過 MMS 回覆" - "回覆" - - %d位參加者 - %d位參加者 - - "我" - "已封鎖並封存聯絡人" - "已解除封鎖並取消封存聯絡人" - "已封存 %d 個對話記錄" - "已取消封存 %d 個項目" - "已關閉通知" - "已開啟通知" - "準備就緒,輕觸 [重新傳送]。" - "已成功將「短訊」設定為預設短訊應用程式。" - - 捨棄附件 - 捨棄附件 - - "音效檔案附件" - "播放音效檔案附件" - "暫停" - "來自%s的訊息:%s。" - "%s未能成功傳送的訊息:%s。時間:%s。" - "%s傳送的訊息:%s。時間:%s。" - "尚未傳送給%s的訊息:%s。時間:%s。" - "正在傳送給%s的訊息:%s。時間:%s。" - "未能成功傳送給%s的訊息:%s。時間:%s。" - "傳送給%s的訊息:%s。時間:%s。" - "%s未能成功傳送的訊息:%s。時間:%s%s。" - "%s傳送的訊息:%s。時間:%s%s。" - "尚未傳送給%s的訊息:%s。時間:%s。" - "正在傳送給%s的訊息:%s。時間:%s。" - "未能成功傳送給%s的訊息:%s。時間:%s。" - "傳送給%s的訊息:%s。時間:%s。" - "未能成功傳送的訊息。輕觸即可重試。" - "與%s的對話" - "刪除主旨" - "拍攝影片" - "拍攝靜態圖像" - "拍照" - "開始錄製影片" - "切換至全屏攝像" - "切換前置和後置鏡頭" - "停止錄製並附加影片" - "停止錄製影片" - "「短訊」相片" - - %d 張相片已儲存至「%s」相簿 - %d 張相片已儲存至「%s」相簿 - - - %d 部影片已儲存至「%s」相簿 - %d 部影片已儲存至「%s」相簿 - - - %d 個附件已儲存至「%s」相簿 - %d 個附件已儲存至「%s」相簿 - - - %d 個附件已儲存至「下載」 - %d 個附件已儲存至「下載」 - - - 已儲存%d個附件 - 已儲存%d個附件 - - - 無法儲存%d個附件 - 無法儲存%d個附件 - - "已儲存的 MMS 附件" - "設定" - "已封存" - "關閉" - "MMS" - "進階" - "除錯" - "通知" - "音效" - "靜音" - "震動" - "已封鎖" - "短訊傳送報告" - "每次發送短訊後要求發送報告" - "自動擷取" - "自動擷取 MMS 訊息" - "漫遊時自動擷取" - "漫遊時自動擷取 MMS 訊息" - "群組短訊" - "有多位收件者時只發送一則 MMS 訊息" - "預設短訊應用程式" - "預設短訊應用程式" - - - "您的電話號碼" - "不明" - "傳送訊息音效" - "傾印短訊" - "將收到的短訊原始資料傾印到外部儲存檔案" - "傾印 MMS" - "將收到的 MMS 原始資料傾印到外部儲存檔案" - "無線警示" - "訊息選項" - "複製文字" - "查看詳情" - "刪除" - "轉寄" - "訊息詳情" - "類型: " - "短訊" - "多媒體訊息" - "寄件者: " - "收件者: " - "傳送時間: " - "接收時間: " - "主旨: " - "大小: " - "重要: " - "SIM 卡: " - "高" - "正常" - "低" - "SIM 卡 %s" - "隱藏的寄件者地址" - "載入附件時無法傳送訊息。" - "無法載入附件,請再試一次。" - "網絡尚未準備好,請再試一次。" - "刪除文本" - "切換文字和數字輸入" - "新增更多參與者" - "確認參與者" - "開始新的對話" - "選取這個項目" - "播放影片" - "參與者和選項" - "除錯" - "參與者和選項" - "一般" - "這個對話的參與者" - "進行通話" - "傳送訊息" - "<br/><small>從 %s</small> 發送短訊" - - 傳送相片 - 傳送相片 - - - 傳送音效檔案 - 傳送音效檔案 - - - 傳送影片 - 傳送影片 - - - 傳送聯絡資料 - 傳送聯絡資料 - - - 傳送附件 - 傳送附件 - - - %d個附件正等待傳送 - 一個附件正等待傳送 - - "傳送意見" - "前往 Google Play 商店查看" - "版本資料" - "%1$s 版" - "開放原始碼授權" - "通知" - "已達到附件限制" - "未能成功載入附件。" - "新增至聯絡人?" - "新增聯絡人" - "主旨" - "主旨: " - "%s%s" - "正在載入聯絡人卡片" - "無法載入聯絡人資訊卡" - "查看聯絡人卡片" - - %d 個聯絡人 - %d 個聯絡人 - - "聯絡人資訊卡" - "生日" - "附註" - "轉寄郵件" - "回覆" - "+1" - "+%d" - "短訊功能已停用" - "將「短訊」設定為預設短訊應用程式,即可發送訊息" - "將「短訊」設定為預設短訊應用程式" - "變更" - "將「短訊」設定為預設短訊應用程式,即可接收訊息" - "尚未選取傳送短訊的首選 SIM" - "裝置擁有者不允許使用該應用程式。" - "確定" - "參與對話的人過多" - - 無效的聯絡人 - 無效的聯絡人 - - "無法載入相機圖片" - "您: " - "%s: " - "草稿" - "只要有新對話,便會在此列出" - "封存的對話顯示在這裏" - "正在載入對話…" - "圖片" - "錄音片段" - "影片" - "聯絡人卡" - "MMS" - "復原" - "重試" - "如要傳送新訊息,請輸入聯絡人姓名或電話號碼" - "封鎖" - "封鎖%s" - "解除封鎖%s" - "要封鎖 %s?" - "您將繼續接收這個電話號碼傳來的訊息,但不會再收到通知。這個對話將被封存。" - "已封鎖的聯絡人" - "解除封鎖" - "已封鎖的聯絡人" - "從文件庫選擇圖片" - "正在傳送訊息" - "已傳送訊息" - "流動數據已關閉,請檢查您的設定。" - "無法在飛行模式下傳送訊息" - "無法傳送訊息" - "已下載訊息" - "流動數據已關閉,請檢查您的設定。" - "在飛行模式下無法下載訊息" - "無法下載訊息" - "0" - "1" - "2" - "3" - "4" - "5" - "6" - "7" - "8" - "9" - "無法經%1$s傳送訊息,錯誤:%2$d" - "無法經不明流動網絡供應商傳送訊息,錯誤:%1$d" - "轉寄:%s" - "無法傳送訊息:未在網絡上啟用服務" - "無法傳送訊息:目的地位址無效" - "無法傳送訊息:訊息無效" - "無法傳送訊息:不支援的內容" - "無法傳送訊息:不支援的訊息" - "無法傳送訊息:太大" - "新訊息" - "檢視" - "圖片" - "找不到合適的應用程式" - "移除收件者" - "新訊息" - "取消" - "編輯接入點" - "未設定" - "名稱" - "APN" - "MMSC" - "MMS 代理" - "MMS 通訊埠" - "MCC" - "MNC" - "APN 類型" - "刪除 APN" - "新增 APN" - "儲存" - "捨棄" - "[名稱] 欄位不可留空。" - "APN 不可留空。" - "MCC 欄位必須為 3 位數。" - "MNC 欄位必須要有 2 或 3 位數。" - "正在還原預設 APN 設定。" - "重設回預設值" - "重設預設 APN 設定已完成。" - "未命名" - "接入點名稱" - "APN" - "新增 APN" - "這位使用者無法修改存取點名稱設定" - "要複製到剪貼簿嗎?" - "複製" - "至 %s" - "一般" - "進階" - "一般設定" - "進階設定" - "「%s」SIM 卡" - "向所有收件者逐一傳送短訊。只有您會收到回覆" - "向所有收件者傳送一封 MMS" - "電話號碼不明" - "新訊息" - "新訊息。" - "SIM 選取器" - "已選擇%1$s,SIM 選取器" - "編輯主旨" - "選取 SIM 或編輯主旨" - "輕觸並按住即可錄製音效檔案" - "開始新的對話" - "短訊" - "「短訊」清單" - "短訊" - "新訊息" - "對話群組清單" - "正在載入對話" - "正在載入訊息" - "查看更多對話" - "查看更多訊息" - "對話群組已刪除" - "已刪除對話,輕觸即可查看其他「短訊」對話" - "已封鎖" - "已解除封鎖" - "儲存空間不足。部分資料可能會遺失。" - "選取附件" - "確認選取" - "已選取 %d 個附件" - "請移除一個或多個附件,然後再試一次。" - "您可以嘗試傳送訊息,但可能要移除一個或多個附件才可送達。" - "每封郵件只可傳送一部影片。請移除額外的影片,然後再試一次。" - "「短訊」無法載入附件。" - "直接傳送" - "無法開始對話" - "%1$s (%2$s)" - "已選取 %s" - diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml deleted file mode 100644 index 3226b70..0000000 --- a/res/values-zh-rTW/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "無主旨" - "無主旨" - - - "是" - "否" - "確定" - "呵呵" - "謝謝" - "我同意" - "很好" - "我正在路上" - "好,我稍後再回覆您" - ":)" - ":(" - - diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml deleted file mode 100644 index 957d943..0000000 --- a/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "簡訊" - "簡訊" - "選取對話" - "設定" - "傳送訊息" - "新增附件" - "説明" - "歡迎使用" - "略過" - "繼續 >" - "繼續" - "結束" - "設定 >" - "設定" - "「簡訊」需要取得簡訊、電話和聯絡人應用程式的存取權限。" - "如要變更權限,請依序前往 [設定] > [應用程式] > [簡訊] > [權限]。" - "如要變更權限,請依序前往 [設定]、[應用程式]、[簡訊] 及 [權限]。" - "常用聯絡人" - "所有聯絡人" - "傳送至 %s" - "拍照或錄影" - "選擇這個裝置上的圖片" - "錄音" - "選擇相片" - "已選取媒體。" - "已取消選取媒體。" - "已選取 %d 個項目" - "圖片 (%1$tB %1$te %1$tY %1$tl %1$tM %1$tp)" - "圖片" - "錄音" - "分享" - "剛剛" - "最新" - - %d 分鐘 - %d 分鐘 - - - %d 小時 - %d 小時 - - - %d - %d - - - %d - 1 週 - - - %d 個月 - 1 個月 - - - %d - 1 年 - - "等級 0 訊息" - "儲存" - "裝置儲存空間不足,「簡訊」會自動刪除較舊簡訊以釋出空間。" - "儲存空間不足" - "在裝置可用空間增加前,「簡訊」可能無法收發訊息。" - "簡訊儲存空間即將用完,建議您刪除部分訊息。" - "驗證您的手機號碼" - "這個一次性步驟可確保「簡訊」正確傳送群組訊息。" - "手機號碼" - "刪除所有內含媒體的訊息" - "刪除超過 %s的訊息" - "自動刪除超過 %s的訊息" - "忽略" - "刪除所有內含媒體的訊息?" - "刪除超過 %s的訊息?" - "刪除超過 %s的訊息並開啟自動刪除設定?" - "%s說了" - "您說了" - "來自%s的訊息" - "您傳送了訊息" - "傳送中…" - "傳送失敗,輕觸即可再試一次。" - "傳送失敗,正在嘗試重新傳送…" - "重新傳送或刪除" - "目前無法傳送您的簡訊,請撥打語音電話聯絡緊急服務。" - "失敗" - "可下載新的多媒體訊息" - "新的多媒體訊息" - "無法下載" - "輕觸即可再試一次" - "輕觸即可下載" - "下載或刪除" - "下載中…" - "訊息已過期或無法存取" - "大小:%1$s,期限:%2$s" - "收件者無效,因此無法傳送訊息。" - "尚未透過網路啟用服務" - "網路發生問題,因此無法傳送" - "訊息已過期或無法存取" - "(無主旨)" - "寄件者不明" - "已傳送" - "無法下載來自 %2$s 的這則訊息:%1$s。" - "記憶體不足,因此無法完成資料庫作業" - "訊息未傳送" - "「簡訊」未傳送部分訊息" - - %d 個對話中的 %d 則訊息 - 1 個對話中的 %d 則訊息 - - "訊息未順利下載" - "「簡訊」未下載部分訊息" - - %d 個對話中的 %d 則訊息 - 1 個對話中的 %d 則訊息 - - "無法傳送訊息給 %1$s" - "目前無法將您的簡訊傳送至 %1$s,請撥打語音電話聯絡緊急服務。" - - %d 則新訊息 - 新訊息 - - "發起" - "無法使用相機" - "無法使用相機" - "無法錄影" - "無法儲存媒體" - "無法拍照" - "返回" - "已封存" - "刪除" - "封存" - "取消封存" - "關閉通知" - "開啟通知" - "新增聯絡人" - "下載" - "傳送" - "刪除" - "刪除這則訊息?" - "這項操作無法復原。" - "刪除" - - 要刪除這些對話嗎? - 要刪除這個對話嗎? - - "刪除" - "取消" - "收件者" - "選取多張圖片" - "確認選取" - "超過 %d 個項目" - "無法錄製音訊,請再試一次。" - "無法播放音訊,請再試一次。" - "無法儲存音訊,請再試一次。" - "輕觸並按住" - "、 " - " " - ": " - " " - "圖片" - "音訊剪輯" - "影片" - "聯絡人卡片" - "下載" - "透過簡訊回覆" - "透過多媒體訊息回覆" - "回覆" - - %d 位參與者 - %d 位參與者 - - "我" - "已封鎖並封存聯絡人" - "已解除封鎖及解除封存聯絡人" - "已封存 %d 個" - "已解除封存 %d 個" - "已關閉通知功能" - "已開啟通知功能" - "設定程序已完成,請再次輕觸 [傳送]。" - "已成功將「簡訊」設為預設的簡訊應用程式。" - - 捨棄附件 - 捨棄附件 - - "音訊附件" - "播放音訊附件" - "暫停" - "收到%s傳送的訊息,內容如下:%s。" - "%s嘗試傳送訊息但未成功:%s。時間:%s。" - "%s傳送了訊息:%s。時間:%s。" - "給%s的訊息尚未送出:%s。時間:%s。" - "正在傳送訊息給%s%s。時間:%s。" - "無法傳送訊息給%s%s。時間:%s。" - "已傳送訊息給%s%s。時間:%s。" - "%s嘗試傳送訊息但未成功:%s。時間:%s%s。" - "%s傳送了訊息:%s。時間:%s%s。" - "給%s的訊息尚未送出:%s。時間:%s。" - "正在傳送訊息給%s%s。時間:%s。" - "無法傳送訊息給%s%s。時間:%s。" - "已傳送訊息給%s%s。時間:%s。" - "訊息傳送失敗,輕觸即可重試。" - "與%s的對話" - "刪除主旨" - "錄影" - "拍攝靜態影像" - "拍照" - "開始錄影" - "切換至全螢幕相機" - "切換使用前置與後置鏡頭" - "停止錄影並附加影片" - "停止錄影" - "簡訊相片" - - %d 張相片已儲存到「%s」相簿 - %d 張相片已儲存到「%s」相簿 - - - %d 段影片已儲存到「%s」相簿 - %d 段影片已儲存到「%s」相簿 - - - %d 份附件已儲存到「%s」相簿 - %d 份附件已儲存到「%s」相簿 - - - %d 份附件已儲存到「下載內容」 - %d 份附件已儲存到「下載內容」 - - - 已儲存 %d 個附件 - 已儲存 %d 個附件 - - - 無法儲存 %d 個附件 - 無法儲存 %d 個附件 - - "已儲存的多媒體訊息附件" - "設定" - "已封存" - "關閉" - "多媒體訊息" - "進階" - "偵錯" - "通知" - "音效" - "靜音" - "震動" - "已封鎖" - "簡訊傳送報告" - "傳送簡訊後要求傳送回條" - "自動擷取" - "自動擷取多媒體訊息" - "漫遊時自動擷取" - "漫遊時自動擷取多媒體訊息" - "群組簡訊" - "有多位收件者時,也使用多媒體訊息傳送訊息" - "預設簡訊應用程式" - "預設簡訊應用程式" - - - "您的電話號碼" - "不明" - "訊息傳送音效" - "傾印簡訊" - "將收到的簡訊原始資料傾印到外部儲存空間檔案" - "傾印多媒體訊息" - "將收到的多媒體訊息原始資料傾印到外部儲存空間檔案" - "無線災害示警" - "訊息選項" - "複製文字" - "查看詳細資料" - "刪除" - "轉寄" - "訊息詳細資料" - "類型: " - "簡訊" - "多媒體訊息" - "寄件者: " - "收件者: " - "傳送時間: " - "接收時間: " - "主旨: " - "大小: " - "優先順序: " - "SIM 卡: " - "高" - "一般" - "低" - "SIM 卡 %s" - "隱藏的寄件者地址" - "載入附件時無法傳送訊息。" - "無法載入附件,請再試一次。" - "網路尚未就緒,請再試一次。" - "刪除文字" - "切換輸入的文字和數字" - "新增更多參與者" - "確認參與者" - "發起新對話" - "選取此項目" - "播放影片" - "參與者和選項" - "偵錯" - "參與者和選項" - "一般" - "這個對話的參與者" - "進行通話" - "傳送簡訊" - "<small>透過 %s</small><br> 傳送訊息" - - 傳送相片 - 傳送相片 - - - 傳送音訊 - 傳送音訊 - - - 傳送影片 - 傳送影片 - - - 傳送聯絡人卡片 - 傳送聯絡人卡片 - - - 傳送附件 - 傳送附件 - - - %d 個附件可隨郵件傳送 - 有 1 個附件可隨郵件傳送 - - "提供意見" - "前往 Google Play 商店查看" - "版本資訊" - "版本 %1$s" - "開放原始碼授權" - "通知" - "已達附件數量上限" - "無法載入附件。" - "加到聯絡人?" - "新增聯絡人" - "主旨" - "主旨: " - "%s%s" - "正在載入聯絡人卡片" - "無法載入聯絡人卡片" - "查看聯絡人卡片" - - %d 位聯絡人 - %d 位聯絡人 - - "聯絡人卡片" - "生日" - "附註" - "轉寄郵件" - "回覆" - "還有 1 位參與者" - "還有 %d 位參與者" - "簡訊功能已停用" - "如要傳送訊息,請將「簡訊」設為預設的簡訊應用程式" - "將「簡訊」設為預設的簡訊應用程式" - "變更" - "如要接收訊息,請將「簡訊」設為預設的簡訊應用程式" - "尚未選取傳送簡訊時慣用的 SIM 卡" - "裝置擁有者不允許這個應用程式的要求。" - "確定" - "對話參與者人數過多" - - 聯絡人無效 - 聯絡人無效 - - "無法載入相機圖片" - "您: " - "%s: " - "草稿" - "這裡會列出您發起的新對話" - "封存的對話會顯示在這裡" - "正在載入對話…" - "圖片" - "音訊剪輯" - "影片" - "聯絡人卡片" - "多媒體訊息" - "復原" - "重試" - "輸入聯絡人名稱或電話號碼,即可開始傳送新訊息" - "封鎖" - "封鎖 %s" - "解除封鎖 %s" - "封鎖 %s?" - "您將持續接收來自這個號碼的訊息,但不會再收到通知。此對話將由系統封存。" - "封鎖的聯絡人" - "解除封鎖" - "封鎖的聯絡人" - "選擇文件庫中的圖片" - "正在傳送訊息" - "訊息成功送出" - "行動數據已關閉。請檢查您的設定。" - "飛航模式下無法傳送訊息" - "無法傳送訊息" - "訊息下載完成" - "行動數據已關閉。請檢查您的設定。" - "在飛航模式下無法下載訊息" - "無法下載訊息" - "0" - "1" - "2" - "3" - "4" - "5" - "6" - "7" - "8" - "9" - "無法透過%1$s傳送訊息 (錯誤代碼:%2$d)" - "無法透過不明行動通訊業者傳送訊息 (錯誤代碼:%1$d)" - "轉寄:%s" - "訊息未傳送:未在網路上啟用服務" - "無法傳送訊息:目的地位址無效" - "無法傳送訊息:訊息無效" - "無法傳送訊息:不支援的內容" - "無法傳送訊息:不支援的訊息" - "訊息未傳送:內容過大" - "新訊息" - "查看" - "圖片" - "找不到適用的應用程式" - "移除收件者" - "新訊息" - "取消" - "編輯存取點" - "未設定" - "名稱" - "APN" - "MMSC" - "多媒體訊息 Proxy" - "多媒體訊息通訊埠" - "MCC" - "MNC" - "APN 類型" - "刪除 APN" - "新增 APN" - "儲存" - "捨棄" - "名稱欄位不可留空。" - "APN 不可留空。" - "MCC 欄位的值必須為 3 碼。" - "MNC 欄位的值必須為 2 或 3 碼。" - "正在還原預設 APN 設定。" - "重設為預設值" - "預設 APN 設定的重設程序已完成。" - "未命名" - "存取點名稱" - "APN" - "新增 APN" - "這位使用者無法修改存取點名稱設定" - "複製到剪貼簿?" - "複製" - "傳送至 %s" - "一般" - "進階" - "一般設定" - "進階設定" - "「%s」SIM 卡" - "分別傳送簡訊給所有收件者 (如有任何回覆,只有您會收到)" - "傳送一則多媒體訊息給所有收件者" - "不明號碼" - "新訊息" - "新訊息。" - "SIM 卡選取器" - "已選取「%1$s」,SIM 卡選取器" - "編輯主旨" - "選取 SIM 卡或編輯主旨" - "輕觸並按住即可錄製音訊" - "發起新對話" - "簡訊" - "訊息清單" - "簡訊" - "新訊息" - "會話群組清單" - "正在載入對話" - "正在載入訊息" - "查看更多對話" - "查看更多訊息" - "對話已刪除" - "已刪除對話。輕觸即可顯示其他「簡訊」對話" - "已封鎖" - "已解除封鎖" - "儲存空間即將用盡,部分資料可能會遺失。" - "選取附件" - "確認選取項目" - "選取了 %d 個附件" - "請移除一或多個附件,然後再試一次。" - "您可以嘗試傳送訊息,但可能必須移除一或多個附件才能順利傳送。" - "每則訊息只能傳送一部影片。請移除額外的影片,然後再試一次。" - "「簡訊」無法載入附件。" - "確定傳送" - "無法發起對話" - "%1$s (%2$s)" - "已選取「%s」" - diff --git a/res/values-zu/arrays.xml b/res/values-zu/arrays.xml deleted file mode 100644 index 50eafe1..0000000 --- a/res/values-zu/arrays.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - "asikho isihloko" - "asikho isihloko" - - - "Yebo" - "Cha" - "KULUNGILE" - "Hehe" - "Siyabonga" - "Ngiyavuma" - "I-Nice" - "Ngisendleleni" - "KULUNGILE, mangibuye kuwe emuva kwesikhathi" - ":)" - ":(" - - diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml deleted file mode 100644 index 02e21dd..0000000 --- a/res/values-zu/strings.xml +++ /dev/null @@ -1,519 +0,0 @@ - - - - - "Imilayezo" - "Imilayezo" - "Khetha ingxoxo" - "Izilungiselelo" - "Thumela umlayezo" - "Engeza okunamathiselwe kwi-imeyili" - "Usizo" - "Siyakwamukela" - "Yeqa" - "Okulandelayo >" - "Okulandelayo" - "Phuma" - "Izilungiselelo >" - "Izilungiselelo" - "Ukulayeza kudinga imvume ku-SMS, ifoni noxhumana nabo." - "Ungaguqula izimvume ku-Izilungiselelo > Izinhlelo zokusebenza > Isilayezi > Izimvume." - "Ungaguqula izimvume ku-Izilungiselelo, Izinhlelo zokusebenza, Isilayezi, Izimvume." - "Imvamisa" - "Bonke oxhumana nabo" - "Thumela ku-%s" - "Thwebula izithombe noma ividiyo" - "Khetha izithombe kusuka kule divayisi" - "Rekhoda umsindo" - "Khetha isithombe" - "Imidiya ikhethiwe." - "Imidiya isuswe ukukhetha." - "%d okukhethiwe" - "isithombe %1$tB %1$te %1$tY %1$tl %1$tM %1$tp" - "Isithombe" - "Rekhoda umsindo" - "Yaba" - "Khona manje" - "Manje" - - %d amaminithi - %d amaminithi - - - %d amahora - %d amahora - - - %d izinsuku - %d izinsuku - - - %d amaviki - %d amaviki - - - %dizinyanga - %dizinyanga - - - %d iminyaka - %d iminyaka - - "Umlayezo wesigaba esingu-0" - "Londoloza" - "Idivayisi manje inesikhala esincane. Imilayezo izosusa ngokuzenzakalelayo imilayezo emidala ukukhulula isikhala." - "Isikhala sesitoreji siyaphela" - "Ukulayeza kungenzeka kungathumeli noma kungamukeli imilayezo kuze kube ukuthi isikhala esiningi sitholakala kudivayisi yakho." - "Isitoreji se-SMS esiphansi. Kungahle kudingeke ukuba ususe imilayezo." - "Qinisekisa inombolo yakho yefoni" - "Lesi sinyathelo sesikhathi esisodwa sizoqinisekisa ukuthi Ukulayeza kuzothumela imilayezo yakho yeqembu ngokufanelekile." - "Inombolo yefoni" - "Susa yonke imilayezo ngemidiya" - "Susa imilayezo emidala kuno-%s" - "Susa ngokuzenzakalelayo imilayezo emidala kuno-%s" - "Ziba" - "Susa yonke imilayezo ngemidiya?" - "Sula imilayezo emidala kuno-%s?" - "Susa imilayezo emidala kuno-%s bese uvule ukususa ngokuzenzakalelayo?" - "%s uthe" - "Wena uthe" - "Umlayezo ovela ku-%s" - "Uthumele umlayezo" - "Iyathumela..." - "Akuthunyelwe. Thinta ukuze uzame futhi." - "Akuthunyelwe. Iyazama futhi…" - "Thumela kabusha noma susa" - "Sicela wenze ikholi yezwi kumasevisi wesimo esiphuthumayo. Umlayezo wakho wombhalo awukwazanga ukulethwa ngalesi sikhathi." - "Yehlulekile" - "Umlayezo we-MMS omusha ongalandwa" - "Umlayezo we-MMS omusha" - "Ayikwazanga ukulanda." - "Thinta ukuze uzame futhi" - "Thinta ukuze ulande" - "Landa noma susa" - "Iyalanda..." - "Umlayezo uphelelwe yisikhathi noma awutholakali" - "usayizi: %1$s, ukuphela kwesikhathi: %2$s" - "Ayikwazi ukuthumela. Umamukeli awuvumelekile." - "Isevisi ayisebenzi kunethiwekhi" - "Ayikwazanga ukuthumela ngenxa yenkinga yenethiwekhi" - "Umlayezo uphelelwe yisikhathi noma awutholakali" - "(Asikho isihloko)" - "Umthumeli ongaziwa" - "Kuthunyelwe" - "Ayikwazanga ukulanda umlayezo we-%1$s kusuka ku-%2$s." - "Ayikwazanga ukuqedela umsebenzi wemininingo egciniwe ngenxa yememori ephansi" - "Umlayezo awuthunyelwanga" - "Eminye imilayezo ayithunyelwe Ekulayezeni" - - %d imilayezo kwizingxoxo ezingu-%d - %d imilayezo kwizingxoxo ezingu-%d - - "Umlayezo awuzange ulandwe" - "Eminye imilayezo ayilandiwe Ekulayezeni" - - %d imilayezo kwizingxoxo ezingu-%d - %d imilayezo kwizingxoxo ezingu-%d - - "Umlayezo oya ku-%1$s awuthunyelwanga" - "Sicela uwenze ikholi yezwi kumasevisi wesimo esiphuthumayo. Umlayezo wakho wobhalo ku-%1$s awukwazanga ukulethwa ngalesi sikhathi." - - %d imilayezo emisha - %d imilayezo emisha - - "Qala" - "Ikhamera ayitholakali" - "Ikhamera ayitholakali" - "Ukuthwebula kwevidiyo akutholakali" - "Ayikwazi ukulondoloza imidiya" - "Ayikwazi ukuthatha isithombe" - "Phindela emuva" - "Okufakwe kungobo yomlando" - "Susa" - "Faka kungobo yomlando" - "Ungafaki kungobo yomlando" - "Vala izaziso" - "Vula isaziso" - "Engeza oxhumana naye" - "Landa" - "Thumela" - "Susa" - "Susa lo mlayezo?" - "Lesi senzo asikwazi ukuhlehliswa." - "Susa" - - Susa lezi zingxoxo? - Susa lezi zingxoxo? - - "Susa" - "Khansela" - "Ku-" - "Khetha izithombe eziningi" - "Qinisekisa ukukhetha" - "+%d" - "Ayikwazi ukurekhoda umsindo. Zama futhi." - "Ayikwazi ukudlala umsindo. Zama futhi." - "Ayikwazanga ukulondoloza umsindo. Zama futhi." - "Thinta bese ubambe" - ", " - " " - ": " - " " - "Isithombe" - "Isiqeshana somsindo" - "Ividiyo" - "Ikhadi loxhumana naye" - "Landa" - "Phendula nge-SMS" - "Phendula nge-MMS" - "Phendula" - - %d ababambiqhaza - %d ababambiqhaza - - "Mina" - "Oxhumana naye uvinjiwe futhi wafakwa kungobo yomlando" - "Oxhumana naye uvulelwe futhi wakhishwa kungobo yomlando" - "%d kufakwe kungobo yomlando" - "%d kukhishwe kungobo yomlando" - "Izaziso zivaliwe" - "Izaziso zivuliwe" - "Konke kusethiwe. Thinta u-Thumela futhi." - "Ukulayeza kusethwe ngempumelelo njengohlelo lokusebenza oluzenzakalelayo lwe-SMS." - - Lahla okunamathiselwe - Lahla okunamathiselwe - - "Ukunamathiselwa komsindo" - "Dala okunamathiselwe komsindo" - "Misa isikhashana" - "Umlayezo kusukela ku-%s: %s." - "Umlayezo ohlulekile ophuma ku-%s: %s. Isikhathi: %s." - "Umlayezo ophuma ku-%s: %s. Isikhathi: %s." - "Umlayezo ongathunyelwe ku-%s: %s. Isikhathi: %s." - "Ithumela umlayezo ku-%s: %s. Isikhathi: %s." - "Umlayezo ohlulekile oya ku-%s: %s. Isikhathi: %s." - "Umlayezo oya ku-%s: %s. Isikhathi: %s." - "Umlayezo ohlulekile kusukela ku-%s: %s. Isikhathi: %s. %s." - "Umlayezo ophuma ku-%s: %s. Isikhathi: %s. %s." - "Umlayezo ongathunyelwe ku-%s: %s. Isikhathi: %s." - "Ithumela umlayezo ku-%s: %s. Isikhathi: %s." - "Umlayezo ohlulekile oya ku-%s: %s. Isikhathi: %s." - "Umlayezo oya ku-%s: %s. Isikhathi: %s." - "Umlayezo ohlulekile. Thinta ukuze uphinde uzame." - "Ingxoxo no-%s" - "Sula isihloko" - "Thwebula ividiyo" - "Thwebula isithombe esimile" - "Thatha isithombe" - "Qala ukuqopha ividiyo" - "Shintshela kukhamela yesikrini esigcwele" - "Shintsha phakathi kwekhamera yaphambilini neyasemuva" - "Misa ukurekhoda uphinde ushumeke ividiyo" - "Misa ukurekhoda amavidiyo" - "Ilayeza izithombe" - - %d izithombe zilondolozwe ku-albhamu \"%s\" - %d izithombe zilondolozwe ku-albhamu \"%s\" - - - %d amavidiyo alondolozwe ku-albhamu \"%s\" - %d amavidiyo alondolozwe ku-albhamu \"%s\" - - - %d okunamathiselwe kulondolozwe ku-albhamu \"%s\" - %d okunamathiselwe kulondolozwe ku-albhamu \"%s\" - - - %d Okunamathiselwe kulondolozwe kokuthi \"Okulandiwe\" - %d Okunamathiselwe kulondolozwe kokuthi \"Okulandiwe\" - - - %d okunamathiselwe kulondoloziwe - %d okunamathiselwe kulondoloziwe - - - Ayikwazanga ukulondoloza okunamathiselwe okungu-%d - Ayikwazanga ukulondoloza okunamathiselwe okungu-%d - - "Ilondoloze okunamathiselwa kwe-MMS" - "Izilungiselelo" - "Okufakwe kungobo yomlando" - "Vala" - "I-MMS" - "Okuthuthukisiwe" - "Lungisa iphutha" - "Izaziso" - "Umsindo" - "Thulile" - "Dlidlizela" - "Ivinjelwe" - "Imibiko yokulethwa kwe-SMS" - "Cela umbiko wokuthumela we-SMS ngayinye oyithumelayo" - "Thola ngokuzenzakalekayo" - "Thola ngokuzenzakalela imilayezo ye-MMS" - "Ukuthola okuzenzakalelayo kokuzula" - "Thola ngokuzenzakalela i-MMS uma uzula" - "Imilayezo yeqembu" - "Sebenzisa i-MMS ukuthumela umlayezo owodwa uma kunabamukeli abaningi" - "Uhlelo lokusebenza oluzenzakalelayo le-SMS" - "Uhlelo lokusebenza oluzenzakalelayo le-SMS" - - - "Inombolo yakho yefoni" - "Akwaziwa" - "Imisindo yemilayezo ephumayo" - "Lahla i-SMS" - "Lahla idatha engahluziwe ye-SMS etholiwe kufayela lesitoreji sangaphandle" - "Lahla i-MMS" - "Lahla idatha engahluziwe ye-MMS etholiwe kufayela lesitoreji sangaphandle" - "Izexwayiso ezingenantambo" - "Izinketho zomlayezo" - "Kopisha umbhalo" - "Buka imininingwane" - "Susa" - "Dlulisela phambili" - "Imininingwane yomlayezo" - "Uhlobo: " - "Umlayezo wombhalo" - "Umlayezo wokuxhumana okuxubile" - "Kusuka ku: " - "Ku: " - "Okuthunyelwe: " - "Okutholiwe: " - "Isihloko: " - "Usayizi: " - "Okubalulekile: " - "SIM: " - "Okuphezulu" - "Okuvamile" - "Okuphansi" - "SIM %s" - "Ikheli lomthumeli lifihliwe" - "Ayikwazi ukuthumela umlayezo ngenkathi ilayisha izinamathiseli." - "Ayikwazi ukulayisha okunamathiselwe. Zama futhi." - "Inethiwekhi ayilungile. Zama futhi." - "Susa umbhalo" - "Shintsha phakathi kokufaka umbhalo nezinombolo" - "Engeza ababambiqhaza abaningi" - "Qinisekisa ababambiqhazi" - "Qala ingxoxo entsha" - "Khetha into" - "Dlala ividiyo" - "Abantu nezinketho" - "Lungisa iphutha" - "Abantu nezinketho" - "Okuvamile" - "Abantu kule ngxoxo" - "Yenza ikholi" - "Thumela umlayezo" - "Thumela umlayezo<br/><small>kusukela ku-%s</small>" - - Thumela izithombe - Thumela izithombe - - - Thumela imisindo - Thumela imisindo - - - Thumela amavidiyo - Thumela amavidiyo - - - Thumela amakhadi oxhumana nabo - Thumela amakhadi oxhumana nabo - - - Thumela okunamathiselwe - Thumela okunamathiselwe - - - %d okunamathiselwe kukulungele ukuthunyelwa - %d okunamathiselwe kukulungele ukuthunyelwa - - "Thumela impendulo" - "Buka ku-Google Play Isitolo" - "Ulwazi lwenguqulo" - "Inguqulo %1$s" - "Amalayisense womthombo ovulekile" - "Izaziso" - "Kufinyelelwe kumkhawulo wokunamathiselwe kwi-imeyili" - "Ihlulekile ukulayisha okunamathiselwe kwi-imeyili." - "Engeza oxhumana nabo" - "Engeza oxhumana naye" - "Isihloko" - "Isihloko: " - "%s%s" - "Ilayisha ikhadi loxhumana naye" - "Ayikwazi ukulayisha ikhadi loxhumana naye" - "Buka ikhadi lokuxhumana" - - %d oxhumana nabo - %d oxhumana nabo - - "Amakhadi wokuxhumana" - "Usuku lokuzalwa" - "Amanothi" - "Dlulisa umlayezo" - "Phendula" - "+1" - "+%d" - "I-SMS ikhutshaziwe" - "Ukuze uthumele, setha Ukulayeza njengohlelo lwakho lokusebenza oluzenzakalelayo lwe-SMS" - "Setha Ukulayeza njengohlelo lokusebenza oluzenzakalelayo lwe-SMS" - "Guqula" - "Ukuze uthole imilayezo, setha Ukulayeza njengohlelo lokusebenza oluzenzakalelayo lwe-SMS" - "Ayikho i-SIM ethandwayo ekhethiwe yokuthumela imilayezo ye-SMS" - "Lolu hlelo lokusebenza aluvumelekile umnikazi wedivayisi." - "KULUNGILE" - "Ababambiqhaza abaningi kakhulu engxoxweni" - - Oxhumana nabo abangavumelekile - Oxhumana nabo abangavumelekile - - "Ayikwazanga ukulayisha isithombe sekhamera" - "Wena: " - "%s: " - "Okusalungiswa" - "Uma uqala ingxoxo entsha, uzoyibona kuhlu lapha" - "Izingxoxo ezifakwe kungobo yomlando zivela lapha" - "Ilayisha izingxoxo..." - "Isithombe" - "Isiqeshana somsindo" - "Ividiyo" - "Ikhadi loxhumana naye" - "i-MMS" - "Hlehlisa" - "Zama futhi" - "Faka igama loxhumana naye noma inombolo eyefoni ukuze uqale umlayezo omusha" - "Vimba" - "Vimbela u-%s" - "Susa ukuvimbela u-%s" - "Vumba e-%s?" - "Uzoqhubeka nokuthola imilayezo kusuka kule nombolo kodwa ngeke usaziswa. Le ngxoxo izofakwa kungobo yomlando." - "Oxhumana nabo abavinjiwe" - "VULELA" - "Oxhumana nabo abavinjiwe" - "Khetha isithombe kusuka kulabhulali yedokhumenti" - "Ithumela umlayezo" - "Umyalezo uthunyelwe" - "Idatha yeselula ivaliwe. Hlola izilungiselelo zakho." - "Ayikwazi ukuthumela imilayezo kumodi yendiza" - "Umlayezo awukwazanga ukuthunyelwa" - "Umlayezo ulandiwe" - "Idatha yeselula ivaliwe. Hlola izilungiselelo zakho." - "Ayikwazi ukulanda imilayezo kumodi ye-Ndiza" - "Umlayezo awukwazanga ukulandwa" - "Iqanda" - "Kunye" - "Kubili" - "Kuthathu" - "Kune" - "Kuhlanu" - "Isithupha" - "Yisikhombisa" - "Isishiyagalombili" - "Isishiyagalolunye" - "Ayikwazi ukuthumela umlayezo nge-%1$s, iphutha %2$d" - "Ayikwazi ukuthumela umlayezo ngenkampani yenethiwekhi engaziwa, iphutha %1$d" - "Dlulisa: %s" - "Umlayezo awuthunyelwe: isevisi ayisebenzi kunethiwekhi" - "Umlayezo awuthunyelwe: ikheli lendawo okuya kulo alivumelekile" - "Umlayezo awuthunyelwe: umlayezo ongavumelekile" - "Umlayezo awuthunyelwe: okuqukethwe okungasekelwe" - "Umlayezo awuthunyelwe: umlayezo ongasekelwe" - "Umlayezo awuthunyelwe: mkhulu kakhulu" - "Umlayezo omusha" - "Buka" - "Isithombe" - "Ayikwazanga ukuthola uhlelo lokusebenza olufanelekile" - "Susa umamukeli" - "Umlayezo omusha" - "Khansela" - "Hlela iphoyinti lokufinyelela" - "Ayisethiwe" - "Igama" - "I-APN" - "I-MMSC" - "Ummeleli we-MMS" - "Imbobo ye-MMS" - "I-MCC" - "I-MNC" - "Uhlobo lwe-APN" - "Susa i-APN" - "I-APN Entsha" - "Londoloza" - "Lahla" - "Igama lenkambu akumele lingabi nalutho." - "I-APN akumele ingabi nalutho." - "Inkundla ye-MCC kumele ibe namadijithi angu-3." - "Inkambu ye-MNC kumelwe ibe amadijithi angu-2 noma angu-3." - "Ibuyisela izilungiselelo ze-APN zokuzenzakalelayo." - "Setha kabusha kube ngokuzenzakalelayo" - "Izilungiselelo ze-APN zokusethwa kabusha okuzenzakalelayo ziqediwe." - "Akunasihloko" - "Amagama wephoyinti lokufinyelela" - "Ama-APN" - "I-APN Entsha" - "Izilungiselelo zegama le-Phoyinti Lokufinyelela azitholakali kulo msebenzisi" - "Kopisha kubhodi lokunamathisela?" - "Kopisha" - "ukuya ku-%s" - "Okuvamile" - "Okuthuthukisiwe" - "Izilungiselelo ezijwayelekile" - "Izilungiselelo ezithuthukisiwe" - "\"%s\" SIM" - "Thumela imilayezo ye-SIM ngamunye kubo bonke abamukeli. Uwena kuphela ozothla izimpendulo" - "Thumela i-SMS eyodwa kubo bonke abamukeli" - "Inombolo engaziwa" - "Umlayezo omusha." - "Umlayezo omusha." - "Iskhethi se-SIM" - "%1$s okukhethiwe, isikhethi se-SIM" - "Hlela isihloko" - "Khetha i-SIM noma hlela isihloko" - "Thinta futhi ubambe ukuze urekhode umsindo" - "Qala ingxoxo entsha" - "Imilayezo" - "Uhlu lokulayeza" - "Imilayezo" - "Umlayezo omusha" - "Uhlu lwengxoxo" - "Ilayisha izingxoxo" - "Ilayisha" - "Buka izingxoxo ezengeziwe" - "Buka eminye imilayezo" - "Ingxoxo isusiwe" - "Ingxoxo isusiwe. Thinta ukuze ubonise ingxoxo ehlukile yokulayeza" - "Uvinjelwe" - "Uvulelwe" - "Isikhala sesitoreji siphansi. Enye idatha ingalahleka." - "Khetha okunamathiselwe" - "Qinisekisa ukukhetha" - "%d okukhethiwe" - "Sicela ususe okunamathiselwe kwi-imeyili okukodwa noma okuningi uphinde uzame futhi." - "Ungazama ukuthumela umlayezo wakho, kodwa kungenzeka ungathunyelwa ngaphandle kokuthi ususe okunamathiselwe kwi-imeyili okukodwa noma okuningi." - "Ungathumela ividiyo eyodwa kuphela ngomlayezo. Sicela ususe amavidiyo angeziwe uphinde uzame futhi." - "Ukulayeza kuhlulekile ukulayisha okunamathiselwe kwi-imeyili." - "Thumela noma kunjalo" - "Ayikwazanga ukuqala ingxoxo" - "%1$s (%2$s)" - "I-%s ikhethiwe" - diff --git a/res/values/arrays.xml b/res/values/arrays.xml deleted file mode 100644 index 59e3206..0000000 --- a/res/values/arrays.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - no subject - nosubject - - - - - Yes - No - OK - Hehe - Thanks - I agree - Nice - On my way - OK, let me get back to you later - :) - :( - - diff --git a/res/values/attrs.xml b/res/values/attrs.xml deleted file mode 100644 index 233cf9c..0000000 --- a/res/values/attrs.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values/colors.xml b/res/values/colors.xml deleted file mode 100644 index f33e105..0000000 --- a/res/values/colors.xml +++ /dev/null @@ -1,174 +0,0 @@ - - - - #689F38 - @color/primary_color - - - #ffffff - @color/primary_color - #537F2D - #ffffff - #9D9D9D - #838383 - - - #636363 - #323232 - #F05A24 - #DE000000 - #8A000000 - - #44000000 - - #000000 - #99323232 - #99ffffff - #99ff0000 - #000000 - - #ff323232 - #000000 - #99ffffff - #6a6a6a - #007fce - #99ff0000 - #ffffffff - #99ffffff - #ffffffff - - @android:color/transparent - #670099cc - #cc0099cc - #6733b5e5 - - #ffffffff - #ffffffff - #6a6a6a - #ff323232 - #eeeeee - #9d9d9d - @color/primary_color - #999999 - #ffffffff - #e2e2e2 - #ffffffff - #8BC34A - #80689F38 - #ff808080 - - - #ff0000 - - - #7f040000 - - #d2d2d2 - #40000000 - #4d4d4d - #999999 - - @android:color/white - #a0ffffff - #44000000 - #ddffffff - @android:color/white - @color/primary_color - #ffffff - #4285f4 - #ededed - #33000000 - @android:color/white - - #80000000 - - #80ffffff - - #eeeeee - #6733b5e5 - #44000000 - - #ffffff - - #29000000 - #30000000 - #40000000 - #323232 - #323232 - @color/primary_color - - #9A9A9A - #FFFFFF - #FFFFFF - #a2a2a2 - @color/primary_color - #ff0000 - #99aaaaaa - - #4d4d4d - #6d6d6d - #6d6d6d - #cccccc - - @color/primary_color - #3ea4dc - #40ffffff - - #555555 - #8BC34A - #6d6d6d - #ff000000 - @color/primary_color - #40000000 - @color/primary_color - - #4d4d4d - #999999 - #cceeeeee - #ffeeeeee - #ffffff - #323232 - - #80689F38 - #b0dddddd - - #03a9f4 - #4d4d4d - - #4d4d4d - #6d6d6d - #333333 - #000000 - - @color/primary_color - - #424242 - @android:color/white - #424242 - #ffffff - #40000000 - #FFFFFF - - #eeeeee - #eeeeee - - #1e000000 - #42000000 - #ffffff - - #F1F1F1 - diff --git a/res/values/config.xml b/res/values/config.xml deleted file mode 100644 index 954f5ed..0000000 --- a/res/values/config.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - 0 - diff --git a/res/values/constants.xml b/res/values/constants.xml deleted file mode 100644 index 8985fd1..0000000 --- a/res/values/constants.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - notifications_category - mms_messaging_category - advanced_category - notifications_enabled - true - notification_sound - notification_vibration - true - sms_disabled - sms_enabled - send_sound - true - advanced_prefs - - - buglesub_delivery_reports - false - buglesub_auto_retrieve_mms - true - buglesub_auto_retrieve_mms_when_roaming - false - buglesub_group_messaging - true - buglesub_mms_phone_number - buglesub_wireless_alerts_key - buglesub_apn_list - - - debug_category - dump_sms - false - dump_mms - false - - - - - sms_apns_key - use_local_apn_pref_key - false - - 600 - 300 - 300 - 200 - 70% - - 300 - - - notifications_group_children - - - "'IMG'_yyyyMMdd_HHmmss" - - "yyyyMMdd_HHmmss" - - - 300 - 4000 - - - 300 - - - 300 - - - 300 - diff --git a/res/values/dimens.xml b/res/values/dimens.xml deleted file mode 100644 index 270ea9e..0000000 --- a/res/values/dimens.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - 56dp - 8dp - 20sp - 16sp - 12sp - 12sp - 12sp - 16sp - 42dp - 56dp - 12dp - 16dp - 8dp - 3dp - 56dp - 56dp - 42dp - 96dp - 4dp - 30dp - 16dp - - 9dp - 12dp - 10dp - 14dp - 18dp - 2dp - 4dp - 12dp - - 215dp - 4dp - 42dp - 36dp - 3dp - 16dp - 16sp - 20dp - 10dp - 133dp - 52dp - 64dp - 4dp - 6dp - -12dp - 12dp - 2dp - 12dp - 8dp - - - 240dp - 240dp - - - 16sp - 14sp - 42dp - 20sp - 14sp - 2dp - 26dp - - - 150dp - - 1000dp - 110dp - 50dp - 150dp - 130dp - 260dp - 1dp - 22sp - - 67% - 28% - 75% - - 16sp - 14sp - 6dp - - 320dp - 3dp - 2dp - 12dp - 18sp - 14sp - 16sp - 240dp - 240dp - - 60dp - 4dp - 2dp - 6dp - 12dp - 14dp - 12sp - - 40dp - 12sp - 10dp - 14sp - 20dp - 60dp - 16sp - - 24dp - 18dp - - 24dp - 20dp - 14dp - 20dp - - 6dp - - 14sp - 14sp - 42dp - - 25dp - 10dp - 5dp - - 16sp - 14sp - 60dp - 14sp - - - 40dp - 30dp - 20dp - 8dp - 12dp - 3dp - 2dp - - 20dp - - - 8dp - 0dp - 2dp - 48dp - 48dp - 18sp - 12dp - 16dp - - 110dp - 15dp - - - 120dp - - - 8dp - 8dp - 48dp - 88dp - 88dp - 44dp - 24dp - 8dp - 8dp - 24sp - - diff --git a/res/values/strings.xml b/res/values/strings.xml deleted file mode 100644 index ec675ef..0000000 --- a/res/values/strings.xml +++ /dev/null @@ -1,976 +0,0 @@ - - - - - Messaging - - - Messaging - - Select conversation - - Settings - Send Message - Add an attachment - Help - - Welcome - Skip - Next > - Next - Exit - Settings > - Settings - - Messaging needs permission to SMS, Phone and Contacts. - You can change permissions in Settings > Apps > Messaging > Permissions. - You can change permissions in Settings, Apps, Messaging, Permissions. - - - Frequents - - - All contacts - - - Send to %s - - Capture pictures or video - Choose images from this device - Record audio - Choose photo - The media is selected. - The media is unselected. - %d selected - - image %1$tB %1$te %1$tY %1$tl %1$tM %1$tp - image - Record audio - - Share - - "Just now" - "Now" - - - - %d min - %d mins - - - - - %d hour - %d hours - - - - - %d day - %d days - - - - - - a week - - %d weeks - - - - a month - - %d months - - - - a year - - %d years - - - - Class 0 message - Save - - - Device is low on space. Messaging will automatically delete older messages to free up space. - - Storage space running out - - Messaging might not send or receive messages until more space is available on your device. - - Low SMS storage. You may need to delete messages. - - - Confirm your phone number - - This one-time step will ensure Messaging will deliver your group messages properly. - - Phone number - - - Delete all messages with media - Delete messages older than %s - Auto-delete messages older than %s - Ignore - - - Delete all messages with media? - Delete messages older than %s? - Delete messages older than %s and turn on auto-delete? - - - - - %s said - You said - - - Message from %s - You sent a message - - - Sending… - - Not sent. Touch to try again. - - Not sent. Trying again… - - Resend or delete - - - Please make a voice call to emergency services. Your text message could not be delivered at this time. - - - Failed - - - New MMS message to download - - New MMS message - - Couldn\'t download - - Touch to try again - - Touch to download - - Download or delete - - Downloading… - - Message expired or not available - - size: %1$s, expiration: %2$s - - Can\'t send. Recipient not valid. - - Service not activated on network - - Couldn\'t send due to network problem - - Message expired or not available - - - (No subject) - - Unknown sender - Delivered - - Couldn\'t download message %1$s from %2$s. - - Couldn\'t complete database operation due to low memory - - Message not sent - - Some messages not sent in Messaging - - %d messages in one conversation - %d messages in %d conversations - - - Message not downloaded - - Some messages not downloaded in Messaging - - %d messages in one conversation - %d messages in %d conversations - - - Message to %1$s not sent - - - Please make a voice call to emergency services. Your text message to - %1$s could not be delivered at this time. - - - - New message - %d new messages - - - - Start - - Camera not available - Camera not available - Video capture not available - - Can\'t save media - Can\'t take picture - - - Back - - Archived - - Delete - - Archive - - Unarchive - - Turn off notifications - - Turn on notifications - - Add contact - - Download - - Send - - Delete - - Delete this message? - This action cannot be undone. - Delete - - - - Delete this conversation? - Delete these conversations? - - - - Delete - - Cancel - - - To - - Select multiple images - - Confirm selection - - +%d - - Can\'t record audio. Try again. - - Can\'t play audio. Try again. - - Couldn\'t save audio. Try again. - - Touch & hold - - - ,\u0020 - - \u0020\u0020 - - :\u0020 - - \u0020\u0020 - - Picture - - Audio clip - - Video - - Contact card - - Download - - Reply via SMS - - Reply via MMS - - Reply - ... - - - %d participant - %d participants - - - Me - - - Contact blocked & archived - - Contact unblocked & unarchived - - %d archived - - %d unarchived - - Notifications turned off - - Notifications turned on - - - All set. Touch Send again. - - Messaging successfully set as the default SMS app. - - - - Discard attachment - Discard attachments - - Audio attachment - Play audio attachment - Pause - - - Message from %s: %s. - - Failed message from %s: %s. Time: %s. - Message from %s: %s. Time: %s. - Unsent message to %s: %s. Time: %s. - Sending message to %s: %s. Time: %s. - Failed message to %s: %s. Time: %s. - Message to %s: %s. Time: %s. - - Failed message from %s: %s. Time: %s. %s. - Message from %s: %s. Time: %s. %s. - Unsent message to %s: %s. Time: %s. - Sending message to %s: %s. Time: %s. - Failed message to %s: %s. Time: %s. - Message to %s: %s. Time: %s. - - Failed message. Touch to retry. - - - Conversation with %s - - - Delete subject - - Capture video - Capture a still image - Take picture - Start recording video - Switch to full screen camera - Switch between front and back camera - Stop recording and attach video - Stop recording video - - Messaging photos - - %d photo saved to \"%s\" album - %d photos saved to \"%s\" album - - - %d video saved to \"%s\" album - %d videos saved to \"%s\" album - - - %d attachment saved to \"%s\" album - %d attachments saved to \"%s\" album - - - %d attachment saved to \"Downloads\" - %d attachments saved to \"Downloads\" - - - %d attachment saved - %d attachments saved - - - Couldn\'t save %d attachment - Couldn\'t save %d attachments - - - Saved MMS attachment - - - Settings - - Archived - - Close - - MMS - - Advanced - - Debug - - Notifications - - Sound - - Silent - - Vibrate - Blocked - - SMS delivery reports - Request a delivery report for each SMS you send - Auto-retrieve - Automatically retrieve MMS messages - - Roaming auto-retrieve - Automatically retrieve MMS when roaming - Group messaging - Use MMS to send a single message when there are multiple recipients - - Default SMS app - - Default SMS app - - %s - - Your phone number - - Unknown - - Outgoing message sounds - - Dump SMS - Dump received SMS raw data into external storage file - Dump MMS - Dump received MMS raw data into external storage file - - SMS dump file - Email SMS dump - Load dump file - Email dump file - Wireless alerts - - - Message options - - Copy text - - View details - - Delete - - Forward - - Message details - - Type:\u0020 - - Text message - - Multimedia message - - From:\u0020 - - To:\u0020 - - Sent:\u0020 - - Received:\u0020 - - Subject:\u0020 - - Size:\u0020 - - Priority:\u0020 - - SIM:\u0020 - - High - - Normal - - Low - - SIM %s - - Hidden sender address - - Can\'t send message while loading attachments. - - Can\'t load attachment. Try again. - - Network is not ready. Try again. - - Delete text - Switch between entering text and numbers - Add more participants - Confirm participants - Start new conversation - - Select this item - Play video - - People & options - - Debug - - People & options - - General - - People in this conversation - - Make a call - - Send message - - Send message<br/><small>from %s</small> - - - Send photo - Send photos - - - - Send audio - Send audios - - - - Send video - Send videos - - - - Send contact card - Send contact cards - - - - Send attachment - Send attachments - - - - No attachments selected - One attachment ready to send - %d attachments ready to send - - - Send feedback - - View in Google Play Store - - Version info - - Version %1$s - - - Open source licenses - - Notifications - - Attachment limit reached - - Failed to load attachment. - - Add to Contacts? - - Add Contact - - Subject - - Subject:\u0020 - - %s%s - - - Loading contact card - - Couldn\'t load contact card - - View contact card - - - - %d contact - %d contacts - - - Contact cards - - Birthday - - Notes - - Forward message - - Reply - - - +1 - - - +%d - - - SMS disabled - - - To send, set Messaging as the default SMS app - - - Set Messaging as the default SMS app - - - Change - - - To receive messages, set Messaging as the default SMS app - - - No preferred SIM selected for sending SMS messages - - - This app isn\'t allowed by the device owner. - - OK - - - Too many participants in a conversation - - - Invalid contact - Invalid contacts - - - Couldn\'t load camera image - - - You:\u0020 - - - %s:\u0020 - - - Draft - - - Once you start a new conversation, you’ll see it listed here - - Archived conversations appear here - - Loading conversations… - - - Picture - - Audio clip - - Video - - Contact card - - - MMS - - - Undo - Retry - - - Enter a contact name or phone number to start a new message - - - Block - - - Block %s - - - Unblock %s - - - Block %s? - - - You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived. - - - Blocked contacts - - - UNBLOCK - - - Blocked contacts - - - Choose image from document library - - Sending message - - Message sent - - Cellular data is turned off. Check your settings. - - Can\'t send messages in Airplane mode - - Message could not be sent - - - Message downloaded - - Cellular data is turned off. Check your settings. - - Can\'t download messages in Airplane mode - - Message could not be downloaded - - - Zero - One - Two - Three - Four - Five - Six - Seven - Eight - Nine - - - Can\'t send message with %1$s, error %2$d - Can\'t send message with unknown carrier, error %1$d - - - Fwd: %s - - - Message not sent: service not activated on network - Message not sent: invalid destination address - Message not sent: invalid message - Message not sent: unsupported content - Message not sent: unsupported message - Message not sent: too large - - - New message - View - - - Image - - - Could not find an appropriate application - - SUB Id - - - Remove recipient - - - New message - - - Cancel - - - Edit access point - - Not set - - Name - - APN - - MMSC - - MMS proxy - - MMS port - - MCC - - MNC - - APN type - - Delete APN - - New APN - - Save - - Discard - - The Name field can\'t be empty. - - The APN can\'t be empty. - - MCC field must be 3 digits. - - MNC field must be 2 or 3 digits. - - Restoring default APN settings. - - Reset to default - - Reset default APN settings completed. - - Untitled - - Access Point Names - - APNs - - New APN - - Access Point Name settings are not available for this user - - Copy to clipboard? - - Copy - - to %s - - - General - - - Advanced - - - General settings - - - Advanced settings - - - \"%s\" SIM - - - Send individual SMS messages to all recipients. Only you will get any replies - Send a single MMS to all recipients - - - Unknown number - - - New message - - New message. - - - SIM selector - - %1$s selected, SIM selector - - - Edit subject - - - Select SIM or edit subject - - - Touch and hold to record audio - - - Start new conversation - - Messaging - - Messaging List - - Messaging - - New message - - Conversation list - - Loading conversations - - Loading messages - - View more conversations - - View more messages - - Conversation deleted - - Conversation deleted. Touch to show a different Messaging conversation - - - Blocked - - Unblocked - Storage space is low. Some data may be lost. - - - Select attachments - - - Confirm selection - - - %d selected - - - Please remove one or more attachments and try again. - - - You can try sending your message, but it might not be delivered unless you remove one or more attachments. - - - You can only send one video per message. Please remove additional videos and try again. - - - Messaging failed to load attachment. - - - Send anyway - - - Could not start conversation - - - %1$s (%2$s) - - - %s selected - diff --git a/res/values/styles.xml b/res/values/styles.xml deleted file mode 100644 index d45f2e1..0000000 --- a/res/values/styles.xml +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values/versions.xml b/res/values/versions.xml deleted file mode 100644 index e82b79e..0000000 --- a/res/values/versions.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - 1 - - - 1 - diff --git a/res/xml-mcc204-mnc04/mms_config.xml b/res/xml-mcc204-mnc04/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc204-mnc04/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc208-mnc01/mms_config.xml b/res/xml-mcc208-mnc01/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc208-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc208-mnc10/mms_config.xml b/res/xml-mcc208-mnc10/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc208-mnc10/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc208-mnc15/mms_config.xml b/res/xml-mcc208-mnc15/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc208-mnc15/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc208-mnc20/mms_config.xml b/res/xml-mcc208-mnc20/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc208-mnc20/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc208-mnc26/mms_config.xml b/res/xml-mcc208-mnc26/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc208-mnc26/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc214-mnc01/mms_config.xml b/res/xml-mcc214-mnc01/mms_config.xml deleted file mode 100644 index d6fad05..0000000 --- a/res/xml-mcc214-mnc01/mms_config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - false - diff --git a/res/xml-mcc214-mnc03/mms_config.xml b/res/xml-mcc214-mnc03/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc214-mnc03/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc214-mnc07/mms_config.xml b/res/xml-mcc214-mnc07/mms_config.xml deleted file mode 100644 index a90a26d..0000000 --- a/res/xml-mcc214-mnc07/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 512000 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc218-mnc05/mms_config.xml b/res/xml-mcc218-mnc05/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc218-mnc05/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc222-mnc01/mms_config.xml b/res/xml-mcc222-mnc01/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc222-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc222-mnc08/mms_config.xml b/res/xml-mcc222-mnc08/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc222-mnc08/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc234-mnc10/mms_config.xml b/res/xml-mcc234-mnc10/mms_config.xml deleted file mode 100755 index 9f89f96..0000000 --- a/res/xml-mcc234-mnc10/mms_config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - 10 - diff --git a/res/xml-mcc234-mnc11 b/res/xml-mcc234-mnc11 deleted file mode 120000 index e29c9d3..0000000 --- a/res/xml-mcc234-mnc11 +++ /dev/null @@ -1 +0,0 @@ -xml-mcc234-mnc10 \ No newline at end of file diff --git a/res/xml-mcc234-mnc15/mms_config.xml b/res/xml-mcc234-mnc15/mms_config.xml deleted file mode 100755 index 9f89f96..0000000 --- a/res/xml-mcc234-mnc15/mms_config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - 10 - diff --git a/res/xml-mcc238-mnc02/mms_config.xml b/res/xml-mcc238-mnc02/mms_config.xml deleted file mode 100644 index 6a27809..0000000 --- a/res/xml-mcc238-mnc02/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 2097152 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc240-mnc01/mms_config.xml b/res/xml-mcc240-mnc01/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc240-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc240-mnc04/mms_config.xml b/res/xml-mcc240-mnc04/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc240-mnc04/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc240-mnc08/mms_config.xml b/res/xml-mcc240-mnc08/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc240-mnc08/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc240-mnc24/mms_config.xml b/res/xml-mcc240-mnc24/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc240-mnc24/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc242-mnc01/mms_config.xml b/res/xml-mcc242-mnc01/mms_config.xml deleted file mode 100644 index 6a27809..0000000 --- a/res/xml-mcc242-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 2097152 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc242-mnc02/mms_config.xml b/res/xml-mcc242-mnc02/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc242-mnc02/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc262-mnc07/mms_config.xml b/res/xml-mcc262-mnc07/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc262-mnc07/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc274-mnc02/mms_config.xml b/res/xml-mcc274-mnc02/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc274-mnc02/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc274-mnc03/mms_config.xml b/res/xml-mcc274-mnc03/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc274-mnc03/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc286-mnc01/mms_config.xml b/res/xml-mcc286-mnc01/mms_config.xml deleted file mode 100644 index a90a26d..0000000 --- a/res/xml-mcc286-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 512000 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc286-mnc03/mms_config.xml b/res/xml-mcc286-mnc03/mms_config.xml deleted file mode 100644 index a90a26d..0000000 --- a/res/xml-mcc286-mnc03/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 512000 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc294-mnc01/mms_config.xml b/res/xml-mcc294-mnc01/mms_config.xml deleted file mode 100644 index 03ff16c..0000000 --- a/res/xml-mcc294-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 102400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc294-mnc02/mms_config.xml b/res/xml-mcc294-mnc02/mms_config.xml deleted file mode 100644 index 51af512..0000000 --- a/res/xml-mcc294-mnc02/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 131072 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc294-mnc03/mms_config.xml b/res/xml-mcc294-mnc03/mms_config.xml deleted file mode 100644 index 208a5a2..0000000 --- a/res/xml-mcc294-mnc03/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 256000 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc302-mnc220/mms_config.xml b/res/xml-mcc302-mnc220/mms_config.xml deleted file mode 100644 index 4267322..0000000 --- a/res/xml-mcc302-mnc220/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - - - 5 - - - 80 - diff --git a/res/xml-mcc302-mnc221/mms_config.xml b/res/xml-mcc302-mnc221/mms_config.xml deleted file mode 100644 index 4267322..0000000 --- a/res/xml-mcc302-mnc221/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - - - 5 - - - 80 - diff --git a/res/xml-mcc302-mnc270/mms_config.xml b/res/xml-mcc302-mnc270/mms_config.xml deleted file mode 100644 index 7f92daa..0000000 --- a/res/xml-mcc302-mnc270/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc290/mms_config.xml b/res/xml-mcc302-mnc290/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc302-mnc290/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc302-mnc320/mms_config.xml b/res/xml-mcc302-mnc320/mms_config.xml deleted file mode 100644 index 0dc7956..0000000 --- a/res/xml-mcc302-mnc320/mms_config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - 614400 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc370/mms_config.xml b/res/xml-mcc302-mnc370/mms_config.xml deleted file mode 100644 index 7f92daa..0000000 --- a/res/xml-mcc302-mnc370/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc490/mms_config.xml b/res/xml-mcc302-mnc490/mms_config.xml deleted file mode 100644 index 7f92daa..0000000 --- a/res/xml-mcc302-mnc490/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc500/mms_config.xml b/res/xml-mcc302-mnc500/mms_config.xml deleted file mode 100644 index 0216774..0000000 --- a/res/xml-mcc302-mnc500/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 3072000 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc510/mms_config.xml b/res/xml-mcc302-mnc510/mms_config.xml deleted file mode 100644 index 0216774..0000000 --- a/res/xml-mcc302-mnc510/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 3072000 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc520/mms_config.xml b/res/xml-mcc302-mnc520/mms_config.xml deleted file mode 100644 index 21b15bb..0000000 --- a/res/xml-mcc302-mnc520/mms_config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - 3072000 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc610/mms_config.xml b/res/xml-mcc302-mnc610/mms_config.xml deleted file mode 100644 index 576b5c1..0000000 --- a/res/xml-mcc302-mnc610/mms_config.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - - - 20 - - - 8 - - - 80 - - - 6245 - - - false - - - false - diff --git a/res/xml-mcc302-mnc660/mms_config.xml b/res/xml-mcc302-mnc660/mms_config.xml deleted file mode 100644 index 7f92daa..0000000 --- a/res/xml-mcc302-mnc660/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc720/mms_config.xml b/res/xml-mcc302-mnc720/mms_config.xml deleted file mode 100644 index 7f92daa..0000000 --- a/res/xml-mcc302-mnc720/mms_config.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 20 - - - 6 - - - 80 - diff --git a/res/xml-mcc302-mnc780/mms_config.xml b/res/xml-mcc302-mnc780/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc302-mnc780/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc004/mms_config.xml b/res/xml-mcc310-mnc004/mms_config.xml deleted file mode 100644 index 346218b..0000000 --- a/res/xml-mcc310-mnc004/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - true - - - true - - - 1258291 - - - Profile - - - 20 - - - x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - - 6245 - - - true - - - 2 - - - 48 - - - false - - - false - - - 80 - - - true - - - 7 - - diff --git a/res/xml-mcc310-mnc005/mms_config.xml b/res/xml-mcc310-mnc005/mms_config.xml deleted file mode 100644 index 346218b..0000000 --- a/res/xml-mcc310-mnc005/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - true - - - true - - - 1258291 - - - Profile - - - 20 - - - x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - - 6245 - - - true - - - 2 - - - 48 - - - false - - - false - - - 80 - - - true - - - 7 - - diff --git a/res/xml-mcc310-mnc010/mms_config.xml b/res/xml-mcc310-mnc010/mms_config.xml deleted file mode 100644 index 571bc07..0000000 --- a/res/xml-mcc310-mnc010/mms_config.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - 1048576 - - - 1944 - - - 2592 - - - false - - - true - - - false - - - -1 - - false - - false - - false - - - false - - diff --git a/res/xml-mcc310-mnc026/mms_config.xml b/res/xml-mcc310-mnc026/mms_config.xml deleted file mode 100644 index 91481f9..0000000 --- a/res/xml-mcc310-mnc026/mms_config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc040/mms_config.xml b/res/xml-mcc310-mnc040/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc310-mnc040/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc070/mms_config.xml b/res/xml-mcc310-mnc070/mms_config.xml deleted file mode 100755 index e9404e8..0000000 --- a/res/xml-mcc310-mnc070/mms_config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc090/mms_config.xml b/res/xml-mcc310-mnc090/mms_config.xml deleted file mode 100755 index 0c8f8de..0000000 --- a/res/xml-mcc310-mnc090/mms_config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc120/mms_config.xml b/res/xml-mcc310-mnc120/mms_config.xml deleted file mode 100644 index 8c1ae11..0000000 --- a/res/xml-mcc310-mnc120/mms_config.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - true - - - 1048576 - - - 1944 - - - 2592 - - - 40 - - - true - - - 6 - - - false - - - -1 - - - 80 - - - 6245 - - false - - false - - false - - - X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI## - - - :pcs - - - false - diff --git a/res/xml-mcc310-mnc130/mms_config.xml b/res/xml-mcc310-mnc130/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc310-mnc130/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc150/mms_config.xml b/res/xml-mcc310-mnc150/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc310-mnc150/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc170/mms_config.xml b/res/xml-mcc310-mnc170/mms_config.xml deleted file mode 100644 index 6705cbc..0000000 --- a/res/xml-mcc310-mnc170/mms_config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc260/mms_config.xml b/res/xml-mcc310-mnc260/mms_config.xml deleted file mode 100644 index 9bf9d70..0000000 --- a/res/xml-mcc310-mnc260/mms_config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc360/mms_config.xml b/res/xml-mcc310-mnc360/mms_config.xml deleted file mode 100644 index eb8da80..0000000 --- a/res/xml-mcc310-mnc360/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1228800 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc380/mms_config.xml b/res/xml-mcc310-mnc380/mms_config.xml deleted file mode 100755 index 0ea83d1..0000000 --- a/res/xml-mcc310-mnc380/mms_config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc410/mms_config.xml b/res/xml-mcc310-mnc410/mms_config.xml deleted file mode 100755 index 4e42f3a..0000000 --- a/res/xml-mcc310-mnc410/mms_config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc420/mms_config.xml b/res/xml-mcc310-mnc420/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc310-mnc420/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc450/mms_config.xml b/res/xml-mcc310-mnc450/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc310-mnc450/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc490/mms_config.xml b/res/xml-mcc310-mnc490/mms_config.xml deleted file mode 100644 index 06da531..0000000 --- a/res/xml-mcc310-mnc490/mms_config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc560/mms_config.xml b/res/xml-mcc310-mnc560/mms_config.xml deleted file mode 100755 index a12d640..0000000 --- a/res/xml-mcc310-mnc560/mms_config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc580/mms_config.xml b/res/xml-mcc310-mnc580/mms_config.xml deleted file mode 100644 index eb8da80..0000000 --- a/res/xml-mcc310-mnc580/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1228800 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc600/mms_config.xml b/res/xml-mcc310-mnc600/mms_config.xml deleted file mode 100644 index f76c945..0000000 --- a/res/xml-mcc310-mnc600/mms_config.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - true - - - 8388608 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - false - - - false - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc680/mms_config.xml b/res/xml-mcc310-mnc680/mms_config.xml deleted file mode 100755 index b42fa02..0000000 --- a/res/xml-mcc310-mnc680/mms_config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc310-mnc750/mms_config.xml b/res/xml-mcc310-mnc750/mms_config.xml deleted file mode 100644 index eb8da80..0000000 --- a/res/xml-mcc310-mnc750/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1228800 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc770/mms_config.xml b/res/xml-mcc310-mnc770/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc310-mnc770/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc310-mnc920/mms_config.xml b/res/xml-mcc310-mnc920/mms_config.xml deleted file mode 100644 index 4319cb0..0000000 --- a/res/xml-mcc310-mnc920/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1258291 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc310-mnc980/mms_config.xml b/res/xml-mcc310-mnc980/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc310-mnc980/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc311-mnc012/mms_config.xml b/res/xml-mcc311-mnc012/mms_config.xml deleted file mode 100644 index 346218b..0000000 --- a/res/xml-mcc311-mnc012/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - true - - - true - - - 1258291 - - - Profile - - - 20 - - - x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - - 6245 - - - true - - - 2 - - - 48 - - - false - - - false - - - 80 - - - true - - - 7 - - diff --git a/res/xml-mcc311-mnc070/mms_config.xml b/res/xml-mcc311-mnc070/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc311-mnc070/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc100/mms_config.xml b/res/xml-mcc311-mnc100/mms_config.xml deleted file mode 100755 index dd671f6..0000000 --- a/res/xml-mcc311-mnc100/mms_config.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 10 - - - false - - - -1 - - - false - - - -1 - - - 80 - - - 6245 - - - false - - - false - - - false - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc180/mms_config.xml b/res/xml-mcc311-mnc180/mms_config.xml deleted file mode 100644 index 891618b..0000000 --- a/res/xml-mcc311-mnc180/mms_config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 10 - diff --git a/res/xml-mcc311-mnc220/mms_config.xml b/res/xml-mcc311-mnc220/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc220/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc221/mms_config.xml b/res/xml-mcc311-mnc221/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc221/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc222/mms_config.xml b/res/xml-mcc311-mnc222/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc222/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc223/mms_config.xml b/res/xml-mcc311-mnc223/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc223/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc224/mms_config.xml b/res/xml-mcc311-mnc224/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc224/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc225/mms_config.xml b/res/xml-mcc311-mnc225/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc225/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc226/mms_config.xml b/res/xml-mcc311-mnc226/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc226/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc227/mms_config.xml b/res/xml-mcc311-mnc227/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc227/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc228/mms_config.xml b/res/xml-mcc311-mnc228/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc228/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc229/mms_config.xml b/res/xml-mcc311-mnc229/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc229/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc230/mms_config.xml b/res/xml-mcc311-mnc230/mms_config.xml deleted file mode 100755 index b82f1d6..0000000 --- a/res/xml-mcc311-mnc230/mms_config.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - false - - - -1 - - - false - - - -1 - - - 80 - - - 6245 - - - false - - - false - - - false - - - X-CS3G-MDN: 1##LINE1NOCOUNTRYCODE## - - diff --git a/res/xml-mcc311-mnc340/mms_config.xml b/res/xml-mcc311-mnc340/mms_config.xml deleted file mode 100644 index eb8da80..0000000 --- a/res/xml-mcc311-mnc340/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1228800 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc370/mms_config.xml b/res/xml-mcc311-mnc370/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc311-mnc370/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc311-mnc410/mms_config.xml b/res/xml-mcc311-mnc410/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc311-mnc410/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc430/mms_config.xml b/res/xml-mcc311-mnc430/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc311-mnc430/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc440/mms_config.xml b/res/xml-mcc311-mnc440/mms_config.xml deleted file mode 100644 index 0f26fab..0000000 --- a/res/xml-mcc311-mnc440/mms_config.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - true - - - 1228800 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - false - - - false - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc480/mms_config.xml b/res/xml-mcc311-mnc480/mms_config.xml deleted file mode 100644 index 1929b6a..0000000 --- a/res/xml-mcc311-mnc480/mms_config.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - true - - - true - - - 1258291 - - - Profile - - - 20 - - - x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - - 6245 - - - true - - - 2 - - - 48 - - - false - - - false - - - 80 - - - true - - - 7 - - diff --git a/res/xml-mcc311-mnc490/mms_config.xml b/res/xml-mcc311-mnc490/mms_config.xml deleted file mode 100644 index 8c1ae11..0000000 --- a/res/xml-mcc311-mnc490/mms_config.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - true - - - 1048576 - - - 1944 - - - 2592 - - - 40 - - - true - - - 6 - - - false - - - -1 - - - 80 - - - 6245 - - false - - false - - false - - - X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI## - - - :pcs - - - false - diff --git a/res/xml-mcc311-mnc580/mms_config.xml b/res/xml-mcc311-mnc580/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc580/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc581/mms_config.xml b/res/xml-mcc311-mnc581/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc581/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc582/mms_config.xml b/res/xml-mcc311-mnc582/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc582/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc583/mms_config.xml b/res/xml-mcc311-mnc583/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc583/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc584/mms_config.xml b/res/xml-mcc311-mnc584/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc584/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc585/mms_config.xml b/res/xml-mcc311-mnc585/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc585/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc586/mms_config.xml b/res/xml-mcc311-mnc586/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc586/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc587/mms_config.xml b/res/xml-mcc311-mnc587/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc587/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc588/mms_config.xml b/res/xml-mcc311-mnc588/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc588/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc589/mms_config.xml b/res/xml-mcc311-mnc589/mms_config.xml deleted file mode 100644 index 196d4f7..0000000 --- a/res/xml-mcc311-mnc589/mms_config.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - true - - - true - - - 1048576 - - - x-wap-profile - - - 20 - - - x-vzw-mdn: 1##LINE1NOCOUNTRYCODE## - - - 1944 - - - 2592 - - - false - - - - 6245 - - true - - - false - - - 80 - - - diff --git a/res/xml-mcc311-mnc590/mms_config.xml b/res/xml-mcc311-mnc590/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc311-mnc590/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc311-mnc870/mms_config.xml b/res/xml-mcc311-mnc870/mms_config.xml deleted file mode 100644 index 8c1ae11..0000000 --- a/res/xml-mcc311-mnc870/mms_config.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - true - - - 1048576 - - - 1944 - - - 2592 - - - 40 - - - true - - - 6 - - - false - - - -1 - - - 80 - - - 6245 - - false - - false - - false - - - X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI## - - - :pcs - - - false - diff --git a/res/xml-mcc312-mnc160/mms_config.xml b/res/xml-mcc312-mnc160/mms_config.xml deleted file mode 100644 index 9622857..0000000 --- a/res/xml-mcc312-mnc160/mms_config.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - true - - - 1048576 - - - 2432 - - - 4320 - - - 500 - - - 50 - - - 10 - - - 5000 - - - 20 - - - true - - - 1 - - - true - - - -1 - - - 80 - - - X-MDN: ##LINE1## - diff --git a/res/xml-mcc312-mnc530/mms_config.xml b/res/xml-mcc312-mnc530/mms_config.xml deleted file mode 100755 index 7b1b093..0000000 --- a/res/xml-mcc312-mnc530/mms_config.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - true - - - 1048576 - - - 1944 - - - 2592 - - - 40 - - - true - - - 6 - - - false - - - -1 - - - 80 - - - 6245 - - false - - false - - false - - - X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI## - - - :pcs - - - false - diff --git a/res/xml-mcc334-mnc020/mms_config.xml b/res/xml-mcc334-mnc020/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc334-mnc020/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc418-mnc05/mms_config.xml b/res/xml-mcc418-mnc05/mms_config.xml deleted file mode 100644 index 03ff16c..0000000 --- a/res/xml-mcc418-mnc05/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 102400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc418-mnc20/mms_config.xml b/res/xml-mcc418-mnc20/mms_config.xml deleted file mode 100644 index 9ef0dd8..0000000 --- a/res/xml-mcc418-mnc20/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 153600 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc418-mnc30/mms_config.xml b/res/xml-mcc418-mnc30/mms_config.xml deleted file mode 100644 index 9ef0dd8..0000000 --- a/res/xml-mcc418-mnc30/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 153600 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc420-mnc04/mms_config.xml b/res/xml-mcc420-mnc04/mms_config.xml deleted file mode 100644 index 614ba8e..0000000 --- a/res/xml-mcc420-mnc04/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 409600 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc440-mnc10/mms_config.xml b/res/xml-mcc440-mnc10/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc440-mnc10/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc440-mnc20/mms_config.xml b/res/xml-mcc440-mnc20/mms_config.xml deleted file mode 100644 index 7f383c4..0000000 --- a/res/xml-mcc440-mnc20/mms_config.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - true - - - 307200 - - - 1944 - - - 2592 - - - 10000 - - - 1000 - - - 10 - - - 5000 - - - -1 - - - true - - - -1 - - - false - diff --git a/res/xml-mcc450-mnc00/mms_config.xml b/res/xml-mcc450-mnc00/mms_config.xml deleted file mode 100644 index 96e5b9d..0000000 --- a/res/xml-mcc450-mnc00/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc450-mnc02/mms_config.xml b/res/xml-mcc450-mnc02/mms_config.xml deleted file mode 100644 index 2a7231c..0000000 --- a/res/xml-mcc450-mnc02/mms_config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - false - - false - - false - diff --git a/res/xml-mcc450-mnc05/mms_config.xml b/res/xml-mcc450-mnc05/mms_config.xml deleted file mode 100644 index 2a7231c..0000000 --- a/res/xml-mcc450-mnc05/mms_config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - false - - false - - false - diff --git a/res/xml-mcc450-mnc06/mms_config.xml b/res/xml-mcc450-mnc06/mms_config.xml deleted file mode 100644 index 5123984..0000000 --- a/res/xml-mcc450-mnc06/mms_config.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - - 80 - - false - - false - - false - diff --git a/res/xml-mcc450-mnc08/mms_config.xml b/res/xml-mcc450-mnc08/mms_config.xml deleted file mode 100644 index 2a7231c..0000000 --- a/res/xml-mcc450-mnc08/mms_config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - 1048576 - - - 1944 - - - 2592 - - false - - false - - false - diff --git a/res/xml-mcc505-mnc01/mms_config.xml b/res/xml-mcc505-mnc01/mms_config.xml deleted file mode 100644 index 6a27809..0000000 --- a/res/xml-mcc505-mnc01/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 2097152 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc530-mnc05/mms_config.xml b/res/xml-mcc530-mnc05/mms_config.xml deleted file mode 100644 index fabcf3c..0000000 --- a/res/xml-mcc530-mnc05/mms_config.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - - - false - diff --git a/res/xml-mcc604-mnc00/mms_config.xml b/res/xml-mcc604-mnc00/mms_config.xml deleted file mode 100644 index 03ff16c..0000000 --- a/res/xml-mcc604-mnc00/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 102400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc604-mnc02/mms_config.xml b/res/xml-mcc604-mnc02/mms_config.xml deleted file mode 100644 index 03ff16c..0000000 --- a/res/xml-mcc604-mnc02/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 102400 - - - 1944 - - - 2592 - diff --git a/res/xml-mcc647-mnc10/mms_config.xml b/res/xml-mcc647-mnc10/mms_config.xml deleted file mode 100644 index 486c468..0000000 --- a/res/xml-mcc647-mnc10/mms_config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - 614400 - - - 1944 - - - 2592 - diff --git a/res/xml-v21/preferences_application.xml b/res/xml-v21/preferences_application.xml deleted file mode 100644 index 5d8ee4c..0000000 --- a/res/xml-v21/preferences_application.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml-v21/preferences_per_subscription.xml b/res/xml-v21/preferences_per_subscription.xml deleted file mode 100644 index d0ae3e6..0000000 --- a/res/xml-v21/preferences_per_subscription.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml-v23/preferences_application.xml b/res/xml-v23/preferences_application.xml deleted file mode 100644 index 8fbadc4..0000000 --- a/res/xml-v23/preferences_application.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/apn_editor.xml b/res/xml/apn_editor.xml deleted file mode 100644 index 5afaeb5..0000000 --- a/res/xml/apn_editor.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - diff --git a/res/xml/apn_settings.xml b/res/xml/apn_settings.xml deleted file mode 100644 index 61a8b5b..0000000 --- a/res/xml/apn_settings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/res/xml/apns.xml b/res/xml/apns.xml deleted file mode 100644 index 57e1163..0000000 --- a/res/xml/apns.xml +++ /dev/null @@ -1,3284 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/mms_config.xml b/res/xml/mms_config.xml deleted file mode 100644 index e5a9338..0000000 --- a/res/xml/mms_config.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - true - - - 307200 - - - 480 - - - 640 - - - -1 - - - true - - - -1 - - - -1 - - - - - - - - - - - diff --git a/res/xml/preferences_application.xml b/res/xml/preferences_application.xml deleted file mode 100644 index 7a18d09..0000000 --- a/res/xml/preferences_application.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/preferences_per_subscription.xml b/res/xml/preferences_per_subscription.xml deleted file mode 100644 index 5e00086..0000000 --- a/res/xml/preferences_per_subscription.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/widget_conversation.xml b/res/xml/widget_conversation.xml deleted file mode 100644 index d1a573b..0000000 --- a/res/xml/widget_conversation.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/res/xml/widget_conversation_list.xml b/res/xml/widget_conversation_list.xml deleted file mode 100644 index b294acb..0000000 --- a/res/xml/widget_conversation_list.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/src/android/support/v7/mms/ApnException.java b/src/android/support/v7/mms/ApnException.java deleted file mode 100644 index 075d92d..0000000 --- a/src/android/support/v7/mms/ApnException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -/** - * APN exception - */ -public class ApnException extends Exception { - - public ApnException() { - super(); - } - - public ApnException(String message) { - super(message); - } - - public ApnException(Throwable cause) { - super(cause); - } - - public ApnException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/android/support/v7/mms/ApnSettingsLoader.java b/src/android/support/v7/mms/ApnSettingsLoader.java deleted file mode 100644 index ff8ed12..0000000 --- a/src/android/support/v7/mms/ApnSettingsLoader.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import java.util.List; - -/** - * Interface for loading APNs for default SMS SIM - */ -public interface ApnSettingsLoader { - /** - * Interface to represent the minimal information MMS lib needs from an APN - */ - interface Apn { - /** - * Get the MMSC URL string - * - * @return MMSC URL - */ - String getMmsc(); - - /** - * Get the MMS proxy host address - * - * @return MMS proxy - */ - String getMmsProxy(); - - /** - * Get the MMS proxy host port - * - * @return the port of MMS proxy - */ - int getMmsProxyPort(); - - /** - * Flag the APN as a successful APN to use - */ - void setSuccess(); - } - - /** - * Get a list possible APN matching the subId and APN name - * - * @param apnName the APN name - * @return a list of possible APNs - */ - List get(String apnName); -} diff --git a/src/android/support/v7/mms/ApnsXmlParser.java b/src/android/support/v7/mms/ApnsXmlParser.java deleted file mode 100644 index eeafcf6..0000000 --- a/src/android/support/v7/mms/ApnsXmlParser.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.ContentValues; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -/** - * Parser for built-in XML resource file for APN list - */ -class ApnsXmlParser extends MmsXmlResourceParser { - interface ApnProcessor { - void process(ContentValues apnValues); - } - - private static final String TAG_APNS = "apns"; - private static final String TAG_APN = "apn"; - - private final ApnProcessor mApnProcessor; - - private final ContentValues mValues = new ContentValues(); - - ApnsXmlParser(final XmlPullParser parser, final ApnProcessor apnProcessor) { - super(parser); - mApnProcessor = apnProcessor; - } - - // Parse one APN - @Override - protected void parseRecord() throws IOException, XmlPullParserException { - if (TAG_APN.equals(mInputParser.getName())) { - mValues.clear(); - // Collect all the attributes - for (int i = 0; i < mInputParser.getAttributeCount(); i++) { - final String key = mInputParser.getAttributeName(i); - if (key != null) { - mValues.put(key, mInputParser.getAttributeValue(i)); - } - } - // We are done parsing one APN, call the handler - if (mApnProcessor != null) { - mApnProcessor.process(mValues); - } - } - // We are at the end tag - if (mInputParser.next() != XmlPullParser.END_TAG) { - throw new XmlPullParserException("Expecting end tag @" + xmlParserDebugContext()); - } - } - - @Override - protected String getRootTag() { - return TAG_APNS; - } -} diff --git a/src/android/support/v7/mms/CarrierConfigValuesLoader.java b/src/android/support/v7/mms/CarrierConfigValuesLoader.java deleted file mode 100644 index ad94fb2..0000000 --- a/src/android/support/v7/mms/CarrierConfigValuesLoader.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.os.Bundle; - -/** - * Loader for carrier dependent configuration values - */ -public interface CarrierConfigValuesLoader { - /** - * Get the carrier config values in a bundle - * - * @param subId the associated subscription ID for the carrier configuration - * @return a bundle of all the values - */ - Bundle get(int subId); - - // Configuration keys and default values - - /** Boolean value: if MMS is enabled */ - public static final String CONFIG_ENABLED_MMS = "enabledMMS"; - public static final boolean CONFIG_ENABLED_MMS_DEFAULT = true; - /** - * Boolean value: if transaction ID should be appended to - * the download URL of a single segment WAP push message - */ - public static final String CONFIG_ENABLED_TRANS_ID = "enabledTransID"; - public static final boolean CONFIG_ENABLED_TRANS_ID_DEFAULT = false; - /** - * Boolean value: if acknowledge or notify response to a download - * should be sent to the WAP push message's download URL - */ - public static final String CONFIG_ENABLED_NOTIFY_WAP_MMSC = "enabledNotifyWapMMSC"; - public static final boolean CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT = false; - /** - * Boolean value: if phone number alias can be used - */ - public static final String CONFIG_ALIAS_ENABLED = "aliasEnabled"; - public static final boolean CONFIG_ALIAS_ENABLED_DEFAULT = false; - /** - * Boolean value: if audio is allowed in attachment - */ - public static final String CONFIG_ALLOW_ATTACH_AUDIO = "allowAttachAudio"; - public static final boolean CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT = true; - /** - * Boolean value: if true, long sms messages are always sent as multi-part sms - * messages, with no checked limit on the number of segments. If false, then - * as soon as the user types a message longer than a single segment (i.e. 140 chars), - * the message will turn into and be sent as an mms message or separate, - * independent SMS messages (dependent on CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES flag). - * This feature exists for carriers that don't support multi-part sms. - */ - public static final String CONFIG_ENABLE_MULTIPART_SMS = "enableMultipartSMS"; - public static final boolean CONFIG_ENABLE_MULTIPART_SMS_DEFAULT = true; - /** - * Boolean value: if SMS delivery report is supported - */ - public static final String CONFIG_ENABLE_SMS_DELIVERY_REPORTS = "enableSMSDeliveryReports"; - public static final boolean CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT = true; - /** - * Boolean value: if group MMS is supported - */ - public static final String CONFIG_ENABLE_GROUP_MMS = "enableGroupMms"; - public static final boolean CONFIG_ENABLE_GROUP_MMS_DEFAULT = true; - /** - * Boolean value: if the content_disposition field of an MMS part should be parsed - * Check wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21. Most carriers support it except some. - */ - public static final String CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION = - "supportMmsContentDisposition"; - public static final boolean CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT = true; - /** - * Boolean value: if the sms app should support a link to the system settings - * where amber alerts are configured. - */ - public static final String CONFIG_CELL_BROADCAST_APP_LINKS = "config_cellBroadcastAppLinks"; - public static final boolean CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT = true; - /** - * Boolean value: if multipart SMS should be sent as separate SMS messages - */ - public static final String CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES = - "sendMultipartSmsAsSeparateMessages"; - public static final boolean CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT = false; - /** - * Boolean value: if MMS read report is supported - */ - public static final String CONFIG_ENABLE_MMS_READ_REPORTS = "enableMMSReadReports"; - public static final boolean CONFIG_ENABLE_MMS_READ_REPORTS_DEFAULT = false; - /** - * Boolean value: if MMS delivery report is supported - */ - public static final String CONFIG_ENABLE_MMS_DELIVERY_REPORTS = "enableMMSDeliveryReports"; - public static final boolean CONFIG_ENABLE_MMS_DELIVERY_REPORTS_DEFAULT = false; - /** - * Boolean value: if "charset" value is supported in the "Content-Type" HTTP header - */ - public static final String CONFIG_SUPPORT_HTTP_CHARSET_HEADER = "supportHttpCharsetHeader"; - public static final boolean CONFIG_SUPPORT_HTTP_CHARSET_HEADER_DEFAULT = false; - /** - * Integer value: maximal MMS message size in bytes - */ - public static final String CONFIG_MAX_MESSAGE_SIZE = "maxMessageSize"; - public static final int CONFIG_MAX_MESSAGE_SIZE_DEFAULT = 300 * 1024; - /** - * Integer value: maximal MMS image height in pixels - */ - public static final String CONFIG_MAX_IMAGE_HEIGHT = "maxImageHeight"; - public static final int CONFIG_MAX_IMAGE_HEIGHT_DEFAULT = 480; - /** - * Integer value: maximal MMS image width in pixels - */ - public static final String CONFIG_MAX_IMAGE_WIDTH = "maxImageWidth"; - public static final int CONFIG_MAX_IMAGE_WIDTH_DEFAULT = 640; - /** - * Integer value: limit on recipient list of an MMS message - */ - public static final String CONFIG_RECIPIENT_LIMIT = "recipientLimit"; - public static final int CONFIG_RECIPIENT_LIMIT_DEFAULT = Integer.MAX_VALUE; - /** - * Integer value: HTTP socket timeout in milliseconds for MMS - */ - public static final String CONFIG_HTTP_SOCKET_TIMEOUT = "httpSocketTimeout"; - public static final int CONFIG_HTTP_SOCKET_TIMEOUT_DEFAULT = 60 * 1000; - /** - * Integer value: minimal number of characters of an alias - */ - public static final String CONFIG_ALIAS_MIN_CHARS = "aliasMinChars"; - public static final int CONFIG_ALIAS_MIN_CHARS_DEFAULT = 2; - /** - * Integer value: maximal number of characters of an alias - */ - public static final String CONFIG_ALIAS_MAX_CHARS = "aliasMaxChars"; - public static final int CONFIG_ALIAS_MAX_CHARS_DEFAULT = 48; - /** - * Integer value: the threshold of number of SMS parts when an multipart SMS will be - * converted into an MMS, e.g. if this is "4", when an multipart SMS message has 5 - * parts, then it will be sent as MMS message instead. "-1" indicates no such conversion - * can happen. - */ - public static final String CONFIG_SMS_TO_MMS_TEXT_THRESHOLD = "smsToMmsTextThreshold"; - public static final int CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT = -1; - /** - * Integer value: the threshold of SMS length when it will be converted into an MMS. - * "-1" indicates no such conversion can happen. - */ - public static final String CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD = - "smsToMmsTextLengthThreshold"; - public static final int CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT = -1; - /** - * Integer value: maximal length in bytes of SMS message - */ - public static final String CONFIG_MAX_MESSAGE_TEXT_SIZE = "maxMessageTextSize"; - public static final int CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT = -1; - /** - * Integer value: maximum number of characters allowed for mms subject - */ - public static final String CONFIG_MAX_SUBJECT_LENGTH = "maxSubjectLength"; - public static final int CONFIG_MAX_SUBJECT_LENGTH_DEFAULT = 40; - /** - * String value: name for the user agent profile HTTP header - */ - public static final String CONFIG_UA_PROF_TAG_NAME = "mUaProfTagName"; - public static final String CONFIG_UA_PROF_TAG_NAME_DEFAULT = "x-wap-profile"; - /** - * String value: additional HTTP headers for MMS HTTP requests. - * The format is - * header_1:header_value_1|header_2:header_value_2|... - * Each value can contain macros. - */ - public static final String CONFIG_HTTP_PARAMS = "httpParams"; - public static final String CONFIG_HTTP_PARAMS_DEFAULT = null; - /** - * String value: number of email gateway - */ - public static final String CONFIG_EMAIL_GATEWAY_NUMBER = "emailGatewayNumber"; - public static final String CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT = null; - /** - * String value: suffix for the NAI HTTP header value, e.g. ":pcs" - * (NAI is used as authentication in HTTP headers for some carriers) - */ - public static final String CONFIG_NAI_SUFFIX = "naiSuffix"; - public static final String CONFIG_NAI_SUFFIX_DEFAULT = null; -} diff --git a/src/android/support/v7/mms/CarrierConfigXmlParser.java b/src/android/support/v7/mms/CarrierConfigXmlParser.java deleted file mode 100644 index 42e7c21..0000000 --- a/src/android/support/v7/mms/CarrierConfigXmlParser.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -/** - * XML parser for carrier config (i.e. mms_config) - */ -class CarrierConfigXmlParser extends MmsXmlResourceParser { - interface KeyValueProcessor { - void process(String type, String key, String value); - } - - private static final String TAG_MMS_CONFIG = "mms_config"; - - private final KeyValueProcessor mKeyValueProcessor; - - CarrierConfigXmlParser(final XmlPullParser parser, final KeyValueProcessor keyValueProcessor) { - super(parser); - mKeyValueProcessor = keyValueProcessor; - } - - // Parse one key/value - @Override - protected void parseRecord() throws IOException, XmlPullParserException { - final String key = mInputParser.getAttributeValue(null, "name"); - // We are at the start tag, the name of the tag is the type - // e.g. value - final String type = mInputParser.getName(); - int nextEvent = mInputParser.next(); - String value = null; - if (nextEvent == XmlPullParser.TEXT) { - value = mInputParser.getText(); - nextEvent = mInputParser.next(); - } - if (nextEvent != XmlPullParser.END_TAG) { - throw new XmlPullParserException("Expecting end tag @" + xmlParserDebugContext()); - } - // We are done parsing one mms_config key/value, call the handler - if (mKeyValueProcessor != null) { - mKeyValueProcessor.process(type, key, value); - } - } - - @Override - protected String getRootTag() { - return TAG_MMS_CONFIG; - } - -} diff --git a/src/android/support/v7/mms/DefaultApnSettingsLoader.java b/src/android/support/v7/mms/DefaultApnSettingsLoader.java deleted file mode 100644 index 83f2fd3..0000000 --- a/src/android/support/v7/mms/DefaultApnSettingsLoader.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.net.Uri; -import android.provider.Telephony; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; - -import com.android.messaging.R; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; - -/** - * Default implementation of APN settings loader - */ -class DefaultApnSettingsLoader implements ApnSettingsLoader { - /** - * The base implementation of an APN - */ - private static class BaseApn implements Apn { - /** - * Create a base APN from parameters - * - * @param typesIn the APN type field - * @param mmscIn the APN mmsc field - * @param proxyIn the APN mmsproxy field - * @param portIn the APN mmsport field - * @return an instance of base APN, or null if any of the parameter is invalid - */ - public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn, - final String portIn) { - if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) { - return null; - } - String mmsc = trimWithNullCheck(mmscIn); - if (TextUtils.isEmpty(mmsc)) { - return null; - } - mmsc = trimV4AddrZeros(mmsc); - try { - new URI(mmsc); - } catch (final URISyntaxException e) { - return null; - } - String mmsProxy = trimWithNullCheck(proxyIn); - int mmsProxyPort = 80; - if (!TextUtils.isEmpty(mmsProxy)) { - mmsProxy = trimV4AddrZeros(mmsProxy); - final String portString = trimWithNullCheck(portIn); - if (portString != null) { - try { - mmsProxyPort = Integer.parseInt(portString); - } catch (final NumberFormatException e) { - // Ignore, just use 80 to try - } - } - } - return new BaseApn(mmsc, mmsProxy, mmsProxyPort); - } - - private final String mMmsc; - private final String mMmsProxy; - private final int mMmsProxyPort; - - public BaseApn(final String mmsc, final String proxy, final int port) { - mMmsc = mmsc; - mMmsProxy = proxy; - mMmsProxyPort = port; - } - - @Override - public String getMmsc() { - return mMmsc; - } - - @Override - public String getMmsProxy() { - return mMmsProxy; - } - - @Override - public int getMmsProxyPort() { - return mMmsProxyPort; - } - - @Override - public void setSuccess() { - // Do nothing - } - - public boolean equals(final BaseApn other) { - return TextUtils.equals(mMmsc, other.getMmsc()) && - TextUtils.equals(mMmsProxy, other.getMmsProxy()) && - mMmsProxyPort == other.getMmsProxyPort(); - } - } - - /** - * An in-memory implementation of an APN. These APNs are organized into an in-memory list. - * The order of the list can be changed by the setSuccess method. - */ - private static class MemoryApn implements Apn { - /** - * Create an in-memory APN loaded from resources - * - * @param apns the in-memory APN list - * @param typesIn the APN type field - * @param mmscIn the APN mmsc field - * @param proxyIn the APN mmsproxy field - * @param portIn the APN mmsport field - * @return an in-memory APN instance, null if there is invalid parameter - */ - public static MemoryApn from(final List apns, final String typesIn, - final String mmscIn, final String proxyIn, final String portIn) { - if (apns == null) { - return null; - } - final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn); - if (base == null) { - return null; - } - for (final Apn apn : apns) { - if (apn instanceof MemoryApn && ((MemoryApn) apn).equals(base)) { - return null; - } - } - return new MemoryApn(apns, base); - } - - private final List mApns; - private final BaseApn mBase; - - public MemoryApn(final List apns, final BaseApn base) { - mApns = apns; - mBase = base; - } - - @Override - public String getMmsc() { - return mBase.getMmsc(); - } - - @Override - public String getMmsProxy() { - return mBase.getMmsProxy(); - } - - @Override - public int getMmsProxyPort() { - return mBase.getMmsProxyPort(); - } - - @Override - public void setSuccess() { - // If this is being marked as a successful APN, move it to the top of the list so - // next time it will be tried first - boolean moved = false; - synchronized (mApns) { - if (mApns.get(0) != this) { - mApns.remove(this); - mApns.add(0, this); - moved = true; - } - } - if (moved) { - Log.d(MmsService.TAG, "Set APN [" - + "MMSC=" + getMmsc() + ", " - + "PROXY=" + getMmsProxy() + ", " - + "PORT=" + getMmsProxyPort() + "] to be first"); - } - } - - public boolean equals(final BaseApn other) { - if (other == null) { - return false; - } - return mBase.equals(other); - } - } - - /** - * APN_TYPE_ALL is a special type to indicate that this APN entry can - * service all data connections. - */ - public static final String APN_TYPE_ALL = "*"; - /** APN type for MMS traffic */ - public static final String APN_TYPE_MMS = "mms"; - - private static final String[] APN_PROJECTION = { - Telephony.Carriers.TYPE, - Telephony.Carriers.MMSC, - Telephony.Carriers.MMSPROXY, - Telephony.Carriers.MMSPORT, - }; - private static final int COLUMN_TYPE = 0; - private static final int COLUMN_MMSC = 1; - private static final int COLUMN_MMSPROXY = 2; - private static final int COLUMN_MMSPORT = 3; - - private static final String APN_MCC = "mcc"; - private static final String APN_MNC = "mnc"; - private static final String APN_APN = "apn"; - private static final String APN_TYPE = "type"; - private static final String APN_MMSC = "mmsc"; - private static final String APN_MMSPROXY = "mmsproxy"; - private static final String APN_MMSPORT = "mmsport"; - - private final Context mContext; - - // Cached APNs for subIds - private final SparseArray> mApnsCache; - - DefaultApnSettingsLoader(final Context context) { - mContext = context; - mApnsCache = new SparseArray<>(); - } - - @Override - public List get(final String apnName) { - final int subId = Utils.getEffectiveSubscriptionId(MmsManager.DEFAULT_SUB_ID); - List apns; - boolean didLoad = false; - synchronized (this) { - apns = mApnsCache.get(subId); - if (apns == null) { - apns = new ArrayList<>(); - mApnsCache.put(subId, apns); - loadLocked(subId, apnName, apns); - didLoad = true; - } - } - if (didLoad) { - Log.i(MmsService.TAG, "Loaded " + apns.size() + " APNs"); - } - return apns; - } - - private void loadLocked(final int subId, final String apnName, final List apns) { - // Try system APN table first - loadFromSystem(subId, apnName, apns); - if (apns.size() > 0) { - return; - } - // Try loading from apns.xml in resources - loadFromResources(subId, apnName, apns); - if (apns.size() > 0) { - return; - } - // Try resources but without APN name - loadFromResources(subId, null/*apnName*/, apns); - } - - /** - * Load matching APNs from telephony provider. - * We try different combinations of the query to work around some platform quirks. - * - * @param subId the SIM subId - * @param apnName the APN name to match - * @param apns the list used to return results - */ - private void loadFromSystem(final int subId, final String apnName, final List apns) { - Uri uri; - if (Utils.supportMSim() && subId != MmsManager.DEFAULT_SUB_ID) { - uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId); - } else { - uri = Telephony.Carriers.CONTENT_URI; - } - Cursor cursor = null; - try { - for (; ; ) { - // Try different combinations of queries. Some would work on some platforms. - // So we query each combination until we find one returns non-empty result. - cursor = querySystem(uri, true/*checkCurrent*/, apnName); - if (cursor != null) { - break; - } - cursor = querySystem(uri, false/*checkCurrent*/, apnName); - if (cursor != null) { - break; - } - cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/); - if (cursor != null) { - break; - } - cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/); - break; - } - } catch (final SecurityException e) { - // Can't access platform APN table, return directly - return; - } - if (cursor == null) { - return; - } - try { - if (cursor.moveToFirst()) { - final Apn apn = BaseApn.from( - cursor.getString(COLUMN_TYPE), - cursor.getString(COLUMN_MMSC), - cursor.getString(COLUMN_MMSPROXY), - cursor.getString(COLUMN_MMSPORT)); - if (apn != null) { - apns.add(apn); - } - } - } finally { - cursor.close(); - } - } - - /** - * Query system APN table - * - * @param uri The APN query URL to use - * @param checkCurrent If add "CURRENT IS NOT NULL" condition - * @param apnName The optional APN name for query condition - * @return A cursor of the query result. If a cursor is returned as not null, it is - * guaranteed to contain at least one row. - */ - private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) { - Log.i(MmsService.TAG, "Loading APNs from system, " - + "checkCurrent=" + checkCurrent + " apnName=" + apnName); - final StringBuilder selectionBuilder = new StringBuilder(); - String[] selectionArgs = null; - if (checkCurrent) { - selectionBuilder.append(Telephony.Carriers.CURRENT).append(" IS NOT NULL"); - } - apnName = trimWithNullCheck(apnName); - if (!TextUtils.isEmpty(apnName)) { - if (selectionBuilder.length() > 0) { - selectionBuilder.append(" AND "); - } - selectionBuilder.append(Telephony.Carriers.APN).append("=?"); - selectionArgs = new String[] { apnName }; - } - try { - final Cursor cursor = mContext.getContentResolver().query( - uri, - APN_PROJECTION, - selectionBuilder.toString(), - selectionArgs, - null/*sortOrder*/); - if (cursor == null || cursor.getCount() < 1) { - if (cursor != null) { - cursor.close(); - } - Log.w(MmsService.TAG, "Query " + uri + " with apn " + apnName + " and " - + (checkCurrent ? "checking CURRENT" : "not checking CURRENT") - + " returned empty"); - return null; - } - return cursor; - } catch (final SQLiteException e) { - Log.w(MmsService.TAG, "APN table query exception: " + e); - } catch (final SecurityException e) { - Log.w(MmsService.TAG, "Platform restricts APN table access: " + e); - throw e; - } - return null; - } - - /** - * Find matching APNs using builtin APN list resource - * - * @param subId the SIM subId - * @param apnName the APN name to match - * @param apns the list for returning results - */ - private void loadFromResources(final int subId, final String apnName, final List apns) { - Log.i(MmsService.TAG, "Loading APNs from resources, apnName=" + apnName); - final int[] mccMnc = Utils.getMccMnc(mContext, subId); - if (mccMnc[0] == 0 && mccMnc[0] == 0) { - Log.w(MmsService.TAG, "Can not get valid mcc/mnc from system"); - return; - } - // MCC/MNC is good, loading/querying APNs from XML - XmlResourceParser xml = null; - try { - xml = mContext.getResources().getXml(R.xml.apns); - new ApnsXmlParser(xml, new ApnsXmlParser.ApnProcessor() { - @Override - public void process(ContentValues apnValues) { - final String mcc = trimWithNullCheck(apnValues.getAsString(APN_MCC)); - final String mnc = trimWithNullCheck(apnValues.getAsString(APN_MNC)); - final String apn = trimWithNullCheck(apnValues.getAsString(APN_APN)); - try { - if (mccMnc[0] == Integer.parseInt(mcc) && - mccMnc[1] == Integer.parseInt(mnc) && - (TextUtils.isEmpty(apnName) || apnName.equalsIgnoreCase(apn))) { - final String type = apnValues.getAsString(APN_TYPE); - final String mmsc = apnValues.getAsString(APN_MMSC); - final String mmsproxy = apnValues.getAsString(APN_MMSPROXY); - final String mmsport = apnValues.getAsString(APN_MMSPORT); - final Apn newApn = MemoryApn.from(apns, type, mmsc, mmsproxy, mmsport); - if (newApn != null) { - apns.add(newApn); - } - } - } catch (final NumberFormatException e) { - // Ignore - } - } - }).parse(); - } catch (final Resources.NotFoundException e) { - Log.w(MmsService.TAG, "Can not get apns.xml " + e); - } finally { - if (xml != null) { - xml.close(); - } - } - } - - private static String trimWithNullCheck(final String value) { - return value != null ? value.trim() : null; - } - - /** - * Trim leading zeros from IPv4 address strings - * Our base libraries will interpret that as octel.. - * Must leave non v4 addresses and host names alone. - * For example, 192.168.000.010 -> 192.168.0.10 - * - * @param addr a string representing an ip addr - * @return a string propertly trimmed - */ - private static String trimV4AddrZeros(final String addr) { - if (addr == null) { - return null; - } - final String[] octets = addr.split("\\."); - if (octets.length != 4) { - return addr; - } - final StringBuilder builder = new StringBuilder(16); - String result = null; - for (int i = 0; i < 4; i++) { - try { - if (octets[i].length() > 3) { - return addr; - } - builder.append(Integer.parseInt(octets[i])); - } catch (final NumberFormatException e) { - return addr; - } - if (i < 3) { - builder.append('.'); - } - } - result = builder.toString(); - return result; - } - - /** - * Check if the APN contains the APN type we want - * - * @param types The string encodes a list of supported types - * @param requestType The type we want - * @return true if the input types string contains the requestType - */ - public static boolean isValidApnType(final String types, final String requestType) { - // If APN type is unspecified, assume APN_TYPE_ALL. - if (TextUtils.isEmpty(types)) { - return true; - } - for (final String t : types.split(",")) { - if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) { - return true; - } - } - return false; - } -} diff --git a/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java b/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java deleted file mode 100644 index 1d45f8f..0000000 --- a/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.os.Bundle; -import android.util.Log; -import android.util.SparseArray; - -import com.android.messaging.R; - -/** - * The default implementation of loader for carrier config values - */ -class DefaultCarrierConfigValuesLoader implements CarrierConfigValuesLoader { - /* - * Key types - */ - public static final String KEY_TYPE_INT = "int"; - public static final String KEY_TYPE_BOOL = "bool"; - public static final String KEY_TYPE_STRING = "string"; - - private final Context mContext; - - // Cached values for subIds - private final SparseArray mValuesCache; - - DefaultCarrierConfigValuesLoader(final Context context) { - mContext = context; - mValuesCache = new SparseArray<>(); - } - - @Override - public Bundle get(int subId) { - subId = Utils.getEffectiveSubscriptionId(subId); - Bundle values; - boolean didLoad = false; - synchronized (this) { - values = mValuesCache.get(subId); - if (values == null) { - values = new Bundle(); - mValuesCache.put(subId, values); - loadLocked(subId, values); - didLoad = true; - } - } - if (didLoad) { - Log.i(MmsService.TAG, "Carrier configs loaded: " + values); - } - return values; - } - - private void loadLocked(final int subId, final Bundle values) { - // For K and earlier, load from resources - loadFromResources(subId, values); - if (Utils.hasMmsApi()) { - // For L and later, also load from system MMS service - loadFromSystem(subId, values); - } - } - - /** - * Load from system, using MMS API - * - * @param subId which SIM to load for - * @param values the result to add to - */ - private static void loadFromSystem(final int subId, final Bundle values) { - try { - final Bundle systemValues = Utils.getSmsManager(subId).getCarrierConfigValues(); - if (systemValues != null) { - values.putAll(systemValues); - } - } catch (final Exception e) { - Log.w(MmsService.TAG, "Calling system getCarrierConfigValues exception", e); - } - } - - private void loadFromResources(final int subId, final Bundle values) { - // Get a subscription-dependent context for loading the mms_config.xml - final Context subContext = Utils.getSubDepContext(mContext, subId); - XmlResourceParser xml = null; - try { - xml = subContext.getResources().getXml(R.xml.mms_config); - new CarrierConfigXmlParser(xml, new CarrierConfigXmlParser.KeyValueProcessor() { - @Override - public void process(String type, String key, String value) { - try { - if (KEY_TYPE_INT.equals(type)) { - values.putInt(key, Integer.parseInt(value)); - } else if (KEY_TYPE_BOOL.equals(type)) { - values.putBoolean(key, Boolean.parseBoolean(value)); - } else if (KEY_TYPE_STRING.equals(type)) { - values.putString(key, value); - } - } catch (final NumberFormatException e) { - Log.w(MmsService.TAG, "Load carrier value from resources: " - + "invalid " + key + "," + value + "," + type); - } - } - }).parse(); - } catch (final Resources.NotFoundException e) { - Log.w(MmsService.TAG, "Can not get mms_config.xml"); - } finally { - if (xml != null) { - xml.close(); - } - } - } -} diff --git a/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java b/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java deleted file mode 100644 index e59a234..0000000 --- a/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.Context; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -/** - * The default implementation of loader of UA and UAProfUrl - */ -class DefaultUserAgentInfoLoader implements UserAgentInfoLoader { - // Default values to be used as user agent info - private static final String DEFAULT_USER_AGENT = "Android MmsLib/1.0"; - private static final String DEFAULT_UA_PROF_URL = - "http://www.gstatic.com/android/sms/mms_ua_profile.xml"; - - private Context mContext; - private boolean mLoaded; - - private String mUserAgent; - private String mUAProfUrl; - - DefaultUserAgentInfoLoader(final Context context) { - mContext = context; - } - - @Override - public String getUserAgent() { - load(); - return mUserAgent; - } - - @Override - public String getUAProfUrl() { - load(); - return mUAProfUrl; - } - - private void load() { - if (mLoaded) { - return; - } - boolean didLoad = false; - synchronized (this) { - if (!mLoaded) { - loadLocked(); - mLoaded = true; - didLoad = true; - } - } - if (didLoad) { - Log.i(MmsService.TAG, "Loaded user agent info: " - + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl); - } - } - - private void loadLocked() { - if (Utils.hasUserAgentApi()) { - // load the MMS User agent and UaProfUrl from TelephonyManager APIs - final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE); - mUserAgent = telephonyManager.getMmsUserAgent(); - mUAProfUrl = telephonyManager.getMmsUAProfUrl(); - } - if (TextUtils.isEmpty(mUserAgent)) { - mUserAgent = DEFAULT_USER_AGENT; - } - if (TextUtils.isEmpty(mUAProfUrl)) { - mUAProfUrl = DEFAULT_UA_PROF_URL; - } - } -} diff --git a/src/android/support/v7/mms/DownloadRequest.java b/src/android/support/v7/mms/DownloadRequest.java deleted file mode 100644 index 254c8ca..0000000 --- a/src/android/support/v7/mms/DownloadRequest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.ParcelFileDescriptor; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - * Request to download an MMS - */ -class DownloadRequest extends MmsRequest { - - DownloadRequest(final String locationUrl, final Uri pduUri, - final PendingIntent sentIntent) { - super(locationUrl, pduUri, sentIntent); - } - - @Override - protected boolean loadRequest(final Context context, final Bundle mmsConfig) { - // No need to load PDU from app. Always true. - return true; - } - - @Override - protected boolean transferResponse(Context context, Intent fillIn, byte[] response) { - return writePduToContentUri(context, mPduUri, response); - } - - @Override - protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettingsLoader.Apn apn, - Bundle mmsConfig, String userAgent, String uaProfUrl) throws MmsHttpException { - final MmsHttpClient httpClient = netMgr.getHttpClient(); - return httpClient.execute(getHttpRequestUrl(apn), null/*pdu*/, MmsHttpClient.METHOD_GET, - !TextUtils.isEmpty(apn.getMmsProxy()), apn.getMmsProxy(), apn.getMmsProxyPort(), - mmsConfig, userAgent, uaProfUrl); - - } - - @Override - protected String getHttpRequestUrl(final ApnSettingsLoader.Apn apn) { - return mLocationUrl; - } - - /** - * Write pdu bytes to content provider uri - * - * @param contentUri content provider uri to which bytes should be written - * @param pdu Bytes to write - * @return true if all bytes successfully written else false - */ - public boolean writePduToContentUri(final Context context, final Uri contentUri, - final byte[] pdu) { - if (contentUri == null || pdu == null) { - return false; - } - final Callable copyDownloadedPduToOutput = new Callable() { - public Boolean call() { - ParcelFileDescriptor.AutoCloseOutputStream outStream = null; - try { - final ContentResolver cr = context.getContentResolver(); - final ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "w"); - outStream = new ParcelFileDescriptor.AutoCloseOutputStream(pduFd); - outStream.write(pdu); - return true; - } catch (IOException e) { - Log.e(MmsService.TAG, "Writing PDU to downloader: IO exception", e); - return false; - } finally { - if (outStream != null) { - try { - outStream.close(); - } catch (IOException ex) { - // Ignore - } - } - } - } - }; - final Future pendingResult = - mPduTransferExecutor.submit(copyDownloadedPduToOutput); - try { - return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - // Typically a timeout occurred - cancel task - pendingResult.cancel(true); - } - return false; - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public DownloadRequest createFromParcel(Parcel in) { - return new DownloadRequest(in); - } - - public DownloadRequest[] newArray(int size) { - return new DownloadRequest[size]; - } - }; - - private DownloadRequest(Parcel in) { - super(in); - } -} diff --git a/src/android/support/v7/mms/MmsHttpClient.java b/src/android/support/v7/mms/MmsHttpClient.java deleted file mode 100644 index 8ca34da..0000000 --- a/src/android/support/v7/mms/MmsHttpClient.java +++ /dev/null @@ -1,523 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.Context; -import android.os.Bundle; -import android.telephony.SmsManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Method; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.ProtocolException; -import java.net.Proxy; -import java.net.URL; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * MMS HTTP client for sending and downloading MMS messages - */ -public class MmsHttpClient { - static final String METHOD_POST = "POST"; - static final String METHOD_GET = "GET"; - - private static final String HEADER_CONTENT_TYPE = "Content-Type"; - private static final String HEADER_ACCEPT = "Accept"; - private static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language"; - private static final String HEADER_USER_AGENT = "User-Agent"; - - // The "Accept" header value - private static final String HEADER_VALUE_ACCEPT = - "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"; - // The "Content-Type" header value - private static final String HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET = - "application/vnd.wap.mms-message; charset=utf-8"; - private static final String HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET = - "application/vnd.wap.mms-message"; - - /* - * Macro names - */ - // The raw phone number - private static final String MACRO_LINE1 = "LINE1"; - // The phone number without country code - private static final String MACRO_LINE1NOCOUNTRYCODE = "LINE1NOCOUNTRYCODE"; - // NAI (Network Access Identifier) - private static final String MACRO_NAI = "NAI"; - - // The possible NAI system property name - private static final String NAI_PROPERTY = "persist.radio.cdma.nai"; - - private final Context mContext; - private final TelephonyManager mTelephonyManager; - - /** - * Constructor - * - * @param context The Context object - */ - MmsHttpClient(Context context) { - mContext = context; - mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - } - - /** - * Execute an MMS HTTP request, either a POST (sending) or a GET (downloading) - * - * @param urlString The request URL, for sending it is usually the MMSC, and for downloading - * it is the message URL - * @param pdu For POST (sending) only, the PDU to send - * @param method HTTP method, POST for sending and GET for downloading - * @param isProxySet Is there a proxy for the MMSC - * @param proxyHost The proxy host - * @param proxyPort The proxy port - * @param mmsConfig The MMS config to use - * @param userAgent The user agent header value - * @param uaProfUrl The UA Prof URL header value - * @return The HTTP response body - * @throws MmsHttpException For any failures - */ - public byte[] execute(String urlString, byte[] pdu, String method, boolean isProxySet, - String proxyHost, int proxyPort, Bundle mmsConfig, String userAgent, String uaProfUrl) - throws MmsHttpException { - Log.d(MmsService.TAG, "HTTP: " + method + " " + Utils.redactUrlForNonVerbose(urlString) - + (isProxySet ? (", proxy=" + proxyHost + ":" + proxyPort) : "") - + ", PDU size=" + (pdu != null ? pdu.length : 0)); - checkMethod(method); - HttpURLConnection connection = null; - try { - Proxy proxy = Proxy.NO_PROXY; - if (isProxySet) { - proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); - } - final URL url = new URL(urlString); - // Now get the connection - connection = (HttpURLConnection) url.openConnection(proxy); - connection.setDoInput(true); - connection.setConnectTimeout( - mmsConfig.getInt(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT, - CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT_DEFAULT)); - // ------- COMMON HEADERS --------- - // Header: Accept - connection.setRequestProperty(HEADER_ACCEPT, HEADER_VALUE_ACCEPT); - // Header: Accept-Language - connection.setRequestProperty( - HEADER_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault())); - // Header: User-Agent - Log.i(MmsService.TAG, "HTTP: User-Agent=" + userAgent); - connection.setRequestProperty(HEADER_USER_AGENT, userAgent); - // Header: x-wap-profile - final String uaProfUrlTagName = mmsConfig.getString( - CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME, - CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME_DEFAULT); - if (uaProfUrl != null) { - Log.i(MmsService.TAG, "HTTP: UaProfUrl=" + uaProfUrl); - connection.setRequestProperty(uaProfUrlTagName, uaProfUrl); - } - // Add extra headers specified by mms_config.xml's httpparams - addExtraHeaders(connection, mmsConfig); - // Different stuff for GET and POST - if (METHOD_POST.equals(method)) { - if (pdu == null || pdu.length < 1) { - Log.e(MmsService.TAG, "HTTP: empty pdu"); - throw new MmsHttpException(0/*statusCode*/, "Sending empty PDU"); - } - connection.setDoOutput(true); - connection.setRequestMethod(METHOD_POST); - if (mmsConfig.getBoolean( - CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER, - CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER_DEFAULT)) { - connection.setRequestProperty(HEADER_CONTENT_TYPE, - HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET); - } else { - connection.setRequestProperty(HEADER_CONTENT_TYPE, - HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET); - } - if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getRequestProperties()); - } - connection.setFixedLengthStreamingMode(pdu.length); - // Sending request body - final OutputStream out = - new BufferedOutputStream(connection.getOutputStream()); - out.write(pdu); - out.flush(); - out.close(); - } else if (METHOD_GET.equals(method)) { - if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getRequestProperties()); - } - connection.setRequestMethod(METHOD_GET); - } - // Get response - final int responseCode = connection.getResponseCode(); - final String responseMessage = connection.getResponseMessage(); - Log.d(MmsService.TAG, "HTTP: " + responseCode + " " + responseMessage); - if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getHeaderFields()); - } - if (responseCode / 100 != 2) { - throw new MmsHttpException(responseCode, responseMessage); - } - final InputStream in = new BufferedInputStream(connection.getInputStream()); - final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - final byte[] buf = new byte[4096]; - int count = 0; - while ((count = in.read(buf)) > 0) { - byteOut.write(buf, 0, count); - } - in.close(); - final byte[] responseBody = byteOut.toByteArray(); - Log.d(MmsService.TAG, "HTTP: response size=" - + (responseBody != null ? responseBody.length : 0)); - return responseBody; - } catch (MalformedURLException e) { - final String redactedUrl = Utils.redactUrlForNonVerbose(urlString); - Log.e(MmsService.TAG, "HTTP: invalid URL " + redactedUrl, e); - throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + redactedUrl, e); - } catch (ProtocolException e) { - final String redactedUrl = Utils.redactUrlForNonVerbose(urlString); - Log.e(MmsService.TAG, "HTTP: invalid URL protocol " + redactedUrl, e); - throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + redactedUrl, e); - } catch (IOException e) { - Log.e(MmsService.TAG, "HTTP: IO failure", e); - throw new MmsHttpException(0/*statusCode*/, e); - } finally { - if (connection != null) { - connection.disconnect(); - } - } - } - - private static void logHttpHeaders(Map> headers) { - final StringBuilder sb = new StringBuilder(); - if (headers != null) { - for (Map.Entry> entry : headers.entrySet()) { - final String key = entry.getKey(); - final List values = entry.getValue(); - if (values != null) { - for (String value : values) { - sb.append(key).append('=').append(value).append('\n'); - } - } - } - Log.v(MmsService.TAG, "HTTP: headers\n" + sb.toString()); - } - } - - private static void checkMethod(String method) throws MmsHttpException { - if (!METHOD_GET.equals(method) && !METHOD_POST.equals(method)) { - throw new MmsHttpException(0/*statusCode*/, "Invalid method " + method); - } - } - - private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US"; - - /** - * Return the Accept-Language header. Use the current locale plus - * US if we are in a different locale than US. - * This code copied from the browser's WebSettings.java - * - * @return Current AcceptLanguage String. - */ - public static String getCurrentAcceptLanguage(Locale locale) { - final StringBuilder buffer = new StringBuilder(); - addLocaleToHttpAcceptLanguage(buffer, locale); - - if (!Locale.US.equals(locale)) { - if (buffer.length() > 0) { - buffer.append(", "); - } - buffer.append(ACCEPT_LANG_FOR_US_LOCALE); - } - - return buffer.toString(); - } - - /** - * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish, - * to new standard. - */ - private static String convertObsoleteLanguageCodeToNew(String langCode) { - if (langCode == null) { - return null; - } - if ("iw".equals(langCode)) { - // Hebrew - return "he"; - } else if ("in".equals(langCode)) { - // Indonesian - return "id"; - } else if ("ji".equals(langCode)) { - // Yiddish - return "yi"; - } - return langCode; - } - - private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale) { - final String language = convertObsoleteLanguageCodeToNew(locale.getLanguage()); - if (language != null) { - builder.append(language); - final String country = locale.getCountry(); - if (country != null) { - builder.append("-"); - builder.append(country); - } - } - } - - private static final Pattern MACRO_P = Pattern.compile("##(\\S+)##"); - /** - * Resolve the macro in HTTP param value text - * For example, "something##LINE1##something" is resolved to "something9139531419something" - * - * @param value The HTTP param value possibly containing macros - * @return The HTTP param with macro resolved to real value - */ - private String resolveMacro(String value, Bundle mmsConfig) { - if (TextUtils.isEmpty(value)) { - return value; - } - final Matcher matcher = MACRO_P.matcher(value); - int nextStart = 0; - StringBuilder replaced = null; - while (matcher.find()) { - if (replaced == null) { - replaced = new StringBuilder(); - } - final int matchedStart = matcher.start(); - if (matchedStart > nextStart) { - replaced.append(value.substring(nextStart, matchedStart)); - } - final String macro = matcher.group(1); - final String macroValue = getHttpParamMacro(macro, mmsConfig); - if (macroValue != null) { - replaced.append(macroValue); - } - nextStart = matcher.end(); - } - if (replaced != null && nextStart < value.length()) { - replaced.append(value.substring(nextStart)); - } - return replaced == null ? value : replaced.toString(); - } - - /** - * Add extra HTTP headers from mms_config.xml's httpParams, which is a list of key/value - * pairs separated by "|". Each key/value pair is separated by ":". Value may contain - * macros like "##LINE1##" or "##NAI##" which is resolved with methods in this class - * - * @param connection The HttpURLConnection that we add headers to - * @param mmsConfig The MmsConfig object - */ - private void addExtraHeaders(HttpURLConnection connection, Bundle mmsConfig) { - final String extraHttpParams = mmsConfig.getString( - CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS); - if (!TextUtils.isEmpty(extraHttpParams)) { - // Parse the parameter list - String paramList[] = extraHttpParams.split("\\|"); - for (String paramPair : paramList) { - String splitPair[] = paramPair.split(":", 2); - if (splitPair.length == 2) { - final String name = splitPair[0].trim(); - final String value = resolveMacro(splitPair[1].trim(), mmsConfig); - if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) { - // Add the header if the param is valid - connection.setRequestProperty(name, value); - } - } - } - } - } - - /** - * Return the HTTP param macro value. - * Example: LINE1 returns the phone number, etc. - * - * @param macro The macro name - * @param mmsConfig The carrier configuration values - * @return The value of the defined macro - */ - private String getHttpParamMacro(final String macro, final Bundle mmsConfig) { - if (MACRO_LINE1.equals(macro)) { - return getSelfNumber(); - } else if (MACRO_LINE1NOCOUNTRYCODE.equals(macro)) { - return PhoneNumberHelper.getNumberNoCountryCode( - getSelfNumber(), getSimOrLocaleCountry()); - } else if (MACRO_NAI.equals(macro)) { - return getEncodedNai(mmsConfig.getString( - CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX, - CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX_DEFAULT)); - } - return null; - } - - /** - * Get the device phone number - * - * @return the phone number text - */ - private String getSelfNumber() { - if (Utils.supportMSim()) { - final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); - final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo( - SmsManager.getDefaultSmsSubscriptionId()); - if (info != null) { - return info.getNumber(); - } else { - return null; - } - } else { - return mTelephonyManager.getLine1Number(); - } - } - - /** - * Get the country ISO code from SIM or system locale - * - * @return the country ISO code - */ - private String getSimOrLocaleCountry() { - String country = null; - if (Utils.supportMSim()) { - final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); - final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo( - SmsManager.getDefaultSmsSubscriptionId()); - if (info != null) { - country = info.getCountryIso(); - } - } else { - country = mTelephonyManager.getSimCountryIso(); - } - if (!TextUtils.isEmpty(country)) { - return country.toUpperCase(); - } else { - return Locale.getDefault().getCountry(); - } - } - - /** - * Get encoded NAI string to use as the HTTP header for some carriers. - * On L-MR1+, we call the hidden system API to get this - * On L-MR1-, we try to find it via system property. - * - * @param naiSuffix the suffix to append to NAI before encoding - * @return the Base64 encoded NAI string to use as HTTP header - */ - private String getEncodedNai(final String naiSuffix) { - String nai; - if (Utils.supportMSim()) { - nai = getNaiBySystemApi( - getSlotId(Utils.getEffectiveSubscriptionId(MmsManager.DEFAULT_SUB_ID))); - } else { - nai = getNaiBySystemProperty(); - } - if (!TextUtils.isEmpty(nai)) { - Log.i(MmsService.TAG, "NAI is not empty"); - if (!TextUtils.isEmpty(naiSuffix)) { - nai = nai + naiSuffix; - } - byte[] encoded = null; - try { - encoded = Base64.encode(nai.getBytes("UTF-8"), Base64.NO_WRAP); - } catch (UnsupportedEncodingException e) { - encoded = Base64.encode(nai.getBytes(), Base64.NO_WRAP); - } - try { - return new String(encoded, "UTF-8"); - } catch (UnsupportedEncodingException e) { - return new String(encoded); - } - } - return null; - } - - /** - * Invoke hidden SubscriptionManager.getSlotId(int) - * - * @param subId the subId - * @return the SIM slot ID - */ - private static int getSlotId(final int subId) { - try { - final Method method = SubscriptionManager.class.getMethod("getSlotId", Integer.TYPE); - if (method != null) { - return (Integer) method.invoke(null, subId); - } - } catch (Exception e) { - Log.w(MmsService.TAG, "SubscriptionManager.getSlotId failed " + e); - } - return -1; - } - - /** - * Get NAI using hidden TelephonyManager.getNai(int) - * - * @param slotId the SIM slot ID - * @return the NAI string - */ - private String getNaiBySystemApi(final int slotId) { - try { - final Method method = mTelephonyManager.getClass().getMethod("getNai", Integer.TYPE); - if (method != null) { - return (String) method.invoke(mTelephonyManager, slotId); - } - } catch (Exception e) { - Log.w(MmsService.TAG, "TelephonyManager.getNai failed " + e); - } - return null; - } - - /** - * Get NAI using hidden SystemProperties.get(String) - * - * @return the NAI string as system property - */ - private static String getNaiBySystemProperty() { - try { - final Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); - if (systemPropertiesClass != null) { - final Method method = systemPropertiesClass.getMethod("get", String.class); - if (method != null) { - return (String) method.invoke(null, NAI_PROPERTY); - } - } - } catch (Exception e) { - Log.w(MmsService.TAG, "SystemProperties.get failed " + e); - } - return null; - } -} diff --git a/src/android/support/v7/mms/MmsHttpException.java b/src/android/support/v7/mms/MmsHttpException.java deleted file mode 100644 index 913ba55..0000000 --- a/src/android/support/v7/mms/MmsHttpException.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -/** - * HTTP exception - */ -public class MmsHttpException extends Exception { - // Optional HTTP status code. 0 means ignore. Otherwise this - // should be a valid HTTP status code. - private final int mStatusCode; - - public MmsHttpException(int statusCode) { - super(); - mStatusCode = statusCode; - } - - public MmsHttpException(int statusCode, String message) { - super(message); - mStatusCode = statusCode; - } - - public MmsHttpException(int statusCode, Throwable cause) { - super(cause); - mStatusCode = statusCode; - } - - public MmsHttpException(int statusCode, String message, Throwable cause) { - super(message, cause); - mStatusCode = statusCode; - } - - public int getStatusCode() { - return mStatusCode; - } -} diff --git a/src/android/support/v7/mms/MmsManager.java b/src/android/support/v7/mms/MmsManager.java deleted file mode 100644 index f9c0a91..0000000 --- a/src/android/support/v7/mms/MmsManager.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.app.PendingIntent; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.telephony.SmsManager; -import android.util.SparseArray; - -/** - * The public interface of MMS library - */ -public class MmsManager { - /** - * Default subscription ID - */ - public static final int DEFAULT_SUB_ID = -1; - - // Whether to force legacy MMS sending - private static volatile boolean sForceLegacyMms = false; - - // Cached computed overrides for carrier configuration values - private static SparseArray sConfigOverridesMap = new SparseArray<>(); - - /** - * Set the flag about whether to force to use legacy system APIs instead of system MMS API - * - * @param forceLegacyMms value to set - */ - public static void setForceLegacyMms(boolean forceLegacyMms) { - sForceLegacyMms = forceLegacyMms; - } - - /** - * Set the size of thread pool for request execution. - * - * Default is 4 - * - * Note: if system MMS API is used, this has no effect - * - * @param size thread pool size - */ - public static void setThreadPoolSize(int size) { - MmsService.setThreadPoolSize(size); - } - - /** - * Set whether to use wake lock while sending or downloading MMS. - * - * Default value is true - * - * Note: if system MMS API is used, this has no effect - * - * @param useWakeLock true to use wake lock, false otherwise - */ - public static void setUseWakeLock(final boolean useWakeLock) { - MmsService.setUseWakeLock(useWakeLock); - } - - /** - * Set the optional carrier config values loader - * - * Note: if system MMS API is used, this is used to compute the overrides - * of carrier configuration values - * - * @param loader the carrier config values loader - */ - public static void setCarrierConfigValuesLoader(CarrierConfigValuesLoader loader) { - if (loader == null) { - throw new IllegalArgumentException("Carrier configuration loader can not be empty"); - } - synchronized (sConfigOverridesMap) { - MmsService.setCarrierConfigValuesLoader(loader); - sConfigOverridesMap.clear(); - } - } - - /** - * Set the optional APN settings loader - * - * Note: if system MMS API is used, this has no effect - * - * @param loader the APN settings loader - */ - public static void setApnSettingsLoader(ApnSettingsLoader loader) { - if (loader == null) { - throw new IllegalArgumentException("APN settings loader can not be empty"); - } - MmsService.setApnSettingsLoader(loader); - } - - /** - * Set user agent info loader - * - * Note: if system MMS API is used, this is used to compute the overrides - * of carrier configuration values - - * @param loader the user agent info loader - */ - public static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) { - if (loader == null) { - throw new IllegalArgumentException("User agent info loader can not be empty"); - } - synchronized (sConfigOverridesMap) { - MmsService.setUserAgentInfoLoader(loader); - sConfigOverridesMap.clear(); - } - } - - /** - * Send MMS via platform MMS API (if platform supports and not forced to - * use legacy APIs) or legacy APIs - * - * @param subId the subscription ID of the SIM to use - * @param context the Context to use - * @param contentUri the content URI of the PDU to be sent - * @param locationUrl the optional location URL to use for sending - * @param sentIntent the pending intent for returning results - */ - public static void sendMultimediaMessage(int subId, Context context, Uri contentUri, - String locationUrl, PendingIntent sentIntent) { - if (Utils.hasMmsApi() && !sForceLegacyMms) { - subId = Utils.getEffectiveSubscriptionId(subId); - final SmsManager smsManager = Utils.getSmsManager(subId); - smsManager.sendMultimediaMessage(context, contentUri, locationUrl, - getConfigOverrides(subId), sentIntent); - } else { - MmsService.startRequest(context, new SendRequest(locationUrl, contentUri, sentIntent)); - } - } - - /** - * Download MMS via platform MMS API (if platform supports and not forced to - * use legacy APIs) or legacy APIs - * - * @param subId the subscription ID of the SIM to use - * @param context the Context to use - * @param contentUri the content URI of the PDU to be sent - * @param locationUrl the optional location URL to use for sending - * @param downloadedIntent the pending intent for returning results - */ - public static void downloadMultimediaMessage(int subId, Context context, String locationUrl, - Uri contentUri, PendingIntent downloadedIntent) { - if (Utils.hasMmsApi() && !sForceLegacyMms) { - subId = Utils.getEffectiveSubscriptionId(subId); - final SmsManager smsManager = Utils.getSmsManager(subId); - smsManager.downloadMultimediaMessage(context, locationUrl, contentUri, - getConfigOverrides(subId), downloadedIntent); - } else { - MmsService.startRequest(context, - new DownloadRequest(locationUrl, contentUri, downloadedIntent)); - } - } - - /** - * Get carrier configuration values overrides when platform MMS API is called. - * We only need to compute this if customized carrier config values loader or - * user agent info loader are set - * - * @param subId the ID of the SIM to use - * @return a Bundle containing the overrides - */ - private static Bundle getConfigOverrides(final int subId) { - if (!Utils.hasMmsApi()) { - // If MMS API is not present, it is not necessary to compute overrides - return null; - } - Bundle overrides = null; - synchronized (sConfigOverridesMap) { - overrides = sConfigOverridesMap.get(subId); - if (overrides == null) { - overrides = new Bundle(); - sConfigOverridesMap.put(subId, overrides); - computeOverridesLocked(subId, overrides); - } - } - return overrides; - } - - /** - * Compute the overrides, incorporating the user agent info - * - * @param subId the subId of the SIM to use - * @param overrides the computed values overrides - */ - private static void computeOverridesLocked(final int subId, final Bundle overrides) { - // Overrides not computed yet - final CarrierConfigValuesLoader carrierConfigValuesLoader = - MmsService.getCarrierConfigValuesLoader(); - if (carrierConfigValuesLoader != null && - !(carrierConfigValuesLoader instanceof DefaultCarrierConfigValuesLoader)) { - // Compute the overrides for carrier config values first if the config loader - // is not the default one. - final Bundle systemValues = Utils.getSmsManager(subId).getCarrierConfigValues(); - final Bundle callerValues = - MmsService.getCarrierConfigValuesLoader().get(subId); - if (systemValues != null && callerValues != null) { - computeConfigDelta(systemValues, callerValues, overrides); - } else if (systemValues == null && callerValues != null) { - overrides.putAll(callerValues); - } - } - final UserAgentInfoLoader userAgentInfoLoader = MmsService.getUserAgentInfoLoader(); - if (userAgentInfoLoader != null && - !(userAgentInfoLoader instanceof DefaultUserAgentInfoLoader)) { - // Also set the user agent and ua prof url via the overrides - // if the user agent loader is not the default one. - overrides.putString(UserAgentInfoLoader.CONFIG_USER_AGENT, - userAgentInfoLoader.getUserAgent()); - overrides.putString(UserAgentInfoLoader.CONFIG_UA_PROF_URL, - userAgentInfoLoader.getUAProfUrl()); - } - } - - /** - * Compute the delta between two sets of carrier configuration values: system and caller - * - * @param systemValues the system config values - * @param callerValues the caller's config values - * @param delta the delta of values (caller - system), using caller value to override system's - */ - private static void computeConfigDelta(final Bundle systemValues, final Bundle callerValues, - final Bundle delta) { - for (final String key : callerValues.keySet()) { - final Object callerValue = callerValues.get(key); - final Object systemValue = systemValues.get(key); - if ((callerValue != null && systemValue != null && !callerValue.equals(systemValue)) || - (callerValue != null && systemValue == null) || - (callerValue == null && systemValue != null)) { - if (callerValue == null || callerValue instanceof String) { - delta.putString(key, (String) callerValue); - } else if (callerValue instanceof Integer) { - delta.putInt(key, (Integer) callerValue); - } else if (callerValue instanceof Boolean) { - delta.putBoolean(key, (Boolean) callerValue); - } - } - } - } -} diff --git a/src/android/support/v7/mms/MmsNetworkException.java b/src/android/support/v7/mms/MmsNetworkException.java deleted file mode 100644 index 776d8ee..0000000 --- a/src/android/support/v7/mms/MmsNetworkException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -/** - * MMS network exception - */ -class MmsNetworkException extends Exception { - - public MmsNetworkException() { - super(); - } - - public MmsNetworkException(String message) { - super(message); - } - - public MmsNetworkException(Throwable cause) { - super(cause); - } - - public MmsNetworkException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/android/support/v7/mms/MmsNetworkManager.java b/src/android/support/v7/mms/MmsNetworkManager.java deleted file mode 100644 index 0630293..0000000 --- a/src/android/support/v7/mms/MmsNetworkManager.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; -import android.os.SystemClock; -import android.util.Log; - -import java.lang.reflect.Method; -import java.util.Timer; -import java.util.TimerTask; - -/** - * Class manages MMS network connectivity using legacy platform APIs - * (deprecated since Android L) on pre-L devices (or when forced to - * be used on L and later) - */ -class MmsNetworkManager { - // Hidden platform constants - private static final String FEATURE_ENABLE_MMS = "enableMMS"; - private static final String REASON_VOICE_CALL_ENDED = "2GVoiceCallEnded"; - private static final int APN_ALREADY_ACTIVE = 0; - private static final int APN_REQUEST_STARTED = 1; - private static final int APN_TYPE_NOT_AVAILABLE = 2; - private static final int APN_REQUEST_FAILED = 3; - private static final int APN_ALREADY_INACTIVE = 4; - // A map from platform APN constant to text string - private static final String[] APN_RESULT_STRING = new String[]{ - "already active", - "request started", - "type not available", - "request failed", - "already inactive", - "unknown", - }; - - private static final long NETWORK_ACQUIRE_WAIT_INTERVAL_MS = 15000; - private static final long DEFAULT_NETWORK_ACQUIRE_TIMEOUT_MS = 180000; - private static final String MMS_NETWORK_EXTENSION_TIMER = "mms_network_extension_timer"; - private static final long MMS_NETWORK_EXTENSION_TIMER_WAIT_MS = 30000; - - private static volatile long sNetworkAcquireTimeoutMs = DEFAULT_NETWORK_ACQUIRE_TIMEOUT_MS; - - /** - * Set the network acquire timeout - * - * @param timeoutMs timeout in millisecond - */ - static void setNetworkAcquireTimeout(final long timeoutMs) { - sNetworkAcquireTimeoutMs = timeoutMs; - } - - private final Context mContext; - private final ConnectivityManager mConnectivityManager; - - // If the connectivity intent receiver is registered - private boolean mReceiverRegistered; - // Count of requests that are using the MMS network - private int mUseCount; - // Count of requests that are waiting for connectivity (i.e. in acquireNetwork wait loop) - private int mWaitCount; - // Timer to extend the network connectivity - private Timer mExtensionTimer; - - private final MmsHttpClient mHttpClient; - - private final IntentFilter mConnectivityIntentFilter; - private final BroadcastReceiver mConnectivityChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { - return; - } - final int networkType = getConnectivityChangeNetworkType(intent); - if (networkType != ConnectivityManager.TYPE_MOBILE_MMS) { - return; - } - onMmsConnectivityChange(context, intent); - } - }; - - MmsNetworkManager(final Context context) { - mContext = context; - mConnectivityManager = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - mHttpClient = new MmsHttpClient(mContext); - mConnectivityIntentFilter = new IntentFilter(); - mConnectivityIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - mUseCount = 0; - mWaitCount = 0; - } - - ConnectivityManager getConnectivityManager() { - return mConnectivityManager; - } - - MmsHttpClient getHttpClient() { - return mHttpClient; - } - - /** - * Synchronously acquire MMS network connectivity - * - * @throws MmsNetworkException If failed permanently or timed out - */ - void acquireNetwork() throws MmsNetworkException { - Log.i(MmsService.TAG, "Acquire MMS network"); - synchronized (this) { - try { - mUseCount++; - mWaitCount++; - if (mWaitCount == 1) { - // Register the receiver for the first waiting request - registerConnectivityChangeReceiverLocked(); - } - long waitMs = sNetworkAcquireTimeoutMs; - final long beginMs = SystemClock.elapsedRealtime(); - do { - if (!isMobileDataEnabled()) { - // Fast fail if mobile data is not enabled - throw new MmsNetworkException("Mobile data is disabled"); - } - // Always try to extend and check the MMS network connectivity - // before we start waiting to make sure we don't miss the change - // of MMS connectivity. As one example, some devices fail to send - // connectivity change intent. So this would make sure we catch - // the state change. - if (extendMmsConnectivityLocked()) { - // Connected - return; - } - try { - wait(Math.min(waitMs, NETWORK_ACQUIRE_WAIT_INTERVAL_MS)); - } catch (final InterruptedException e) { - Log.w(MmsService.TAG, "Unexpected exception", e); - } - // Calculate the remaining time to wait - waitMs = sNetworkAcquireTimeoutMs - (SystemClock.elapsedRealtime() - beginMs); - } while (waitMs > 0); - // Last check - if (extendMmsConnectivityLocked()) { - return; - } else { - // Reaching here means timed out. - throw new MmsNetworkException("Acquiring MMS network timed out"); - } - } finally { - mWaitCount--; - if (mWaitCount == 0) { - // Receiver is used to listen to connectivity change and unblock - // the waiting requests. If nobody's waiting on change, there is - // no need for the receiver. The auto extension timer will try - // to maintain the connectivity periodically. - unregisterConnectivityChangeReceiverLocked(); - } - } - } - } - - /** - * Release MMS network connectivity. This is ref counted. So it only disconnect - * when the ref count is 0. - */ - void releaseNetwork() { - Log.i(MmsService.TAG, "release MMS network"); - synchronized (this) { - mUseCount--; - if (mUseCount == 0) { - stopNetworkExtensionTimerLocked(); - endMmsConnectivity(); - } - } - } - - String getApnName() { - String apnName = null; - final NetworkInfo mmsNetworkInfo = mConnectivityManager.getNetworkInfo( - ConnectivityManager.TYPE_MOBILE_MMS); - if (mmsNetworkInfo != null) { - apnName = mmsNetworkInfo.getExtraInfo(); - } - return apnName; - } - - // Process mobile MMS connectivity change, waking up the waiting request thread - // in certain conditions: - // - Successfully connected - // - Failed permanently - // - Required another kickoff - // We don't initiate connection here but just notifyAll so the waiting request - // would wake up and retry connection before next wait. - private void onMmsConnectivityChange(final Context context, final Intent intent) { - if (mUseCount < 1) { - return; - } - final NetworkInfo mmsNetworkInfo = - mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); - // Check availability of the mobile network. - if (mmsNetworkInfo != null) { - if (REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) { - // This is a very specific fix to handle the case where the phone receives an - // incoming call during the time we're trying to setup the mms connection. - // When the call ends, restart the process of mms connectivity. - // Once the waiting request is unblocked, before the next wait, we would start - // MMS network again. - unblockWait(); - } else { - final NetworkInfo.State state = mmsNetworkInfo.getState(); - if (state == NetworkInfo.State.CONNECTED || - (state == NetworkInfo.State.DISCONNECTED && !isMobileDataEnabled())) { - // Unblock the waiting request when we either connected - // OR - // disconnected due to mobile data disabled therefore needs to fast fail - // (on some devices if mobile data disabled and starting MMS would cause - // an immediate state change to disconnected, so causing a tight loop of - // trying and failing) - // Once the waiting request is unblocked, before the next wait, we would - // check mobile data and start MMS network again. So we should catch - // both the success and the fast failure. - unblockWait(); - } - } - } - } - - private void unblockWait() { - synchronized (this) { - notifyAll(); - } - } - - private void startNetworkExtensionTimerLocked() { - if (mExtensionTimer == null) { - mExtensionTimer = new Timer(MMS_NETWORK_EXTENSION_TIMER, true/*daemon*/); - mExtensionTimer.schedule( - new TimerTask() { - @Override - public void run() { - synchronized (this) { - if (mUseCount > 0) { - try { - // Try extending the connectivity - extendMmsConnectivityLocked(); - } catch (final MmsNetworkException e) { - // Ignore the exception - } - } - } - } - }, - MMS_NETWORK_EXTENSION_TIMER_WAIT_MS); - } - } - - private void stopNetworkExtensionTimerLocked() { - if (mExtensionTimer != null) { - mExtensionTimer.cancel(); - mExtensionTimer = null; - } - } - - private boolean extendMmsConnectivityLocked() throws MmsNetworkException { - final int result = startMmsConnectivity(); - if (result == APN_ALREADY_ACTIVE) { - // Already active - startNetworkExtensionTimerLocked(); - return true; - } else if (result != APN_REQUEST_STARTED) { - stopNetworkExtensionTimerLocked(); - throw new MmsNetworkException("Cannot acquire MMS network: " + - result + " - " + getMmsConnectivityResultString(result)); - } - return false; - } - - private int startMmsConnectivity() { - Log.i(MmsService.TAG, "Start MMS connectivity"); - try { - final Method method = mConnectivityManager.getClass().getMethod( - "startUsingNetworkFeature", Integer.TYPE, String.class); - if (method != null) { - return (Integer) method.invoke( - mConnectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS); - } - } catch (final Exception e) { - Log.w(MmsService.TAG, "ConnectivityManager.startUsingNetworkFeature failed " + e); - } - return APN_REQUEST_FAILED; - } - - private void endMmsConnectivity() { - Log.i(MmsService.TAG, "End MMS connectivity"); - try { - final Method method = mConnectivityManager.getClass().getMethod( - "stopUsingNetworkFeature", Integer.TYPE, String.class); - if (method != null) { - method.invoke( - mConnectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS); - } - } catch (final Exception e) { - Log.w(MmsService.TAG, "ConnectivityManager.stopUsingNetworkFeature failed " + e); - } - } - - private void registerConnectivityChangeReceiverLocked() { - if (!mReceiverRegistered) { - mContext.registerReceiver(mConnectivityChangeReceiver, mConnectivityIntentFilter); - mReceiverRegistered = true; - } - } - - private void unregisterConnectivityChangeReceiverLocked() { - if (mReceiverRegistered) { - mContext.unregisterReceiver(mConnectivityChangeReceiver); - mReceiverRegistered = false; - } - } - - /** - * The absence of a connection type. - */ - private static final int TYPE_NONE = -1; - - /** - * Get the network type of the connectivity change - * - * @param intent the broadcast intent of connectivity change - * @return The change's network type - */ - private static int getConnectivityChangeNetworkType(final Intent intent) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE); - } else { - final NetworkInfo info = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - if (info != null) { - return info.getType(); - } - } - return TYPE_NONE; - } - - private static String getMmsConnectivityResultString(int result) { - if (result < 0 || result >= APN_RESULT_STRING.length) { - result = APN_RESULT_STRING.length - 1; - } - return APN_RESULT_STRING[result]; - } - - private boolean isMobileDataEnabled() { - try { - final Class cmClass = mConnectivityManager.getClass(); - final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled"); - method.setAccessible(true); // Make the method callable - // get the setting for "mobile data" - return (Boolean) method.invoke(mConnectivityManager); - } catch (final Exception e) { - Log.w(MmsService.TAG, "TelephonyManager.getMobileDataEnabled failed", e); - } - return false; - } -} diff --git a/src/android/support/v7/mms/MmsRequest.java b/src/android/support/v7/mms/MmsRequest.java deleted file mode 100644 index edf3606..0000000 --- a/src/android/support/v7/mms/MmsRequest.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v7.mms.pdu.GenericPdu; -import android.support.v7.mms.pdu.PduHeaders; -import android.support.v7.mms.pdu.PduParser; -import android.support.v7.mms.pdu.SendConf; -import android.telephony.SmsManager; -import android.text.TextUtils; -import android.util.Log; - -import java.lang.reflect.Method; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * MMS request base class. This handles the execution of any MMS request. - */ -abstract class MmsRequest implements Parcelable { - /** - * Prepare to make the HTTP request - will download message for sending - * - * @param context the Context - * @param mmsConfig carrier config values to use - * @return true if loading request PDU from calling app succeeds, false otherwise - */ - protected abstract boolean loadRequest(Context context, Bundle mmsConfig); - - /** - * Transfer the received response to the caller - * - * @param context the Context - * @param fillIn the content of pending intent to be returned - * @param response the pdu to transfer - * @return true if transferring response PDU to calling app succeeds, false otherwise - */ - protected abstract boolean transferResponse(Context context, Intent fillIn, byte[] response); - - /** - * Making the HTTP request to MMSC - * - * @param context The context - * @param netMgr The current {@link MmsNetworkManager} - * @param apn The APN - * @param mmsConfig The carrier configuration values to use - * @param userAgent The User-Agent header value - * @param uaProfUrl The UA Prof URL header value - * @return The HTTP response data - * @throws MmsHttpException If any network error happens - */ - protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, - ApnSettingsLoader.Apn apn, Bundle mmsConfig, String userAgent, String uaProfUrl) - throws MmsHttpException; - - /** - * Get the HTTP request URL for this MMS request - * - * @param apn The APN to use - * @return The HTTP request URL in text - */ - protected abstract String getHttpRequestUrl(ApnSettingsLoader.Apn apn); - - // Maximum time to spend waiting to read data from a content provider before failing with error. - protected static final int TASK_TIMEOUT_MS = 30 * 1000; - - protected final String mLocationUrl; - protected final Uri mPduUri; - protected final PendingIntent mPendingIntent; - // Thread pool for transferring PDU with MMS apps - protected final ExecutorService mPduTransferExecutor = Executors.newCachedThreadPool(); - - // Whether this request should acquire wake lock - private boolean mUseWakeLock; - - protected MmsRequest(final String locationUrl, final Uri pduUri, - final PendingIntent pendingIntent) { - mLocationUrl = locationUrl; - mPduUri = pduUri; - mPendingIntent = pendingIntent; - mUseWakeLock = true; - } - - void setUseWakeLock(final boolean useWakeLock) { - mUseWakeLock = useWakeLock; - } - - boolean getUseWakeLock() { - return mUseWakeLock; - } - - /** - * Run the MMS request. - * - * @param context the context to use - * @param networkManager the MmsNetworkManager to use to setup MMS network - * @param apnSettingsLoader the APN loader - * @param carrierConfigValuesLoader the carrier config loader - * @param userAgentInfoLoader the user agent info loader - */ - void execute(final Context context, final MmsNetworkManager networkManager, - final ApnSettingsLoader apnSettingsLoader, - final CarrierConfigValuesLoader carrierConfigValuesLoader, - final UserAgentInfoLoader userAgentInfoLoader) { - Log.i(MmsService.TAG, "Execute " + this.getClass().getSimpleName()); - int result = SmsManager.MMS_ERROR_UNSPECIFIED; - int httpStatusCode = 0; - byte[] response = null; - final Bundle mmsConfig = carrierConfigValuesLoader.get(MmsManager.DEFAULT_SUB_ID); - if (mmsConfig == null) { - Log.e(MmsService.TAG, "Failed to load carrier configuration values"); - result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR; - } else if (!loadRequest(context, mmsConfig)) { - Log.e(MmsService.TAG, "Failed to load PDU"); - result = SmsManager.MMS_ERROR_IO_ERROR; - } else { - // Everything's OK. Now execute the request. - try { - // Acquire the MMS network - networkManager.acquireNetwork(); - // Load the potential APNs. In most cases there should be only one APN available. - // On some devices on which we can't obtain APN from system, we look up our own - // APN list. Since we don't have exact information, we may get a list of potential - // APNs to try. Whenever we found a successful APN, we signal it and return. - final String apnName = networkManager.getApnName(); - final List apns = apnSettingsLoader.get(apnName); - if (apns.size() < 1) { - throw new ApnException("No valid APN"); - } else { - Log.d(MmsService.TAG, "Trying " + apns.size() + " APNs"); - } - final String userAgent = userAgentInfoLoader.getUserAgent(); - final String uaProfUrl = userAgentInfoLoader.getUAProfUrl(); - MmsHttpException lastException = null; - for (ApnSettingsLoader.Apn apn : apns) { - Log.i(MmsService.TAG, "Using APN [" - + "MMSC=" + apn.getMmsc() + ", " - + "PROXY=" + apn.getMmsProxy() + ", " - + "PORT=" + apn.getMmsProxyPort() + "]"); - try { - final String url = getHttpRequestUrl(apn); - // Request a global route for the host to connect - requestRoute(networkManager.getConnectivityManager(), apn, url); - // Perform the HTTP request - response = doHttp( - context, networkManager, apn, mmsConfig, userAgent, uaProfUrl); - // Additional check of whether this is a success - if (isWrongApnResponse(response, mmsConfig)) { - throw new MmsHttpException(0/*statusCode*/, "Invalid sending address"); - } - // Notify APN loader this is a valid APN - apn.setSuccess(); - result = Activity.RESULT_OK; - break; - } catch (MmsHttpException e) { - Log.w(MmsService.TAG, "HTTP or network failure", e); - lastException = e; - } - } - if (lastException != null) { - throw lastException; - } - } catch (ApnException e) { - Log.e(MmsService.TAG, "MmsRequest: APN failure", e); - result = SmsManager.MMS_ERROR_INVALID_APN; - } catch (MmsNetworkException e) { - Log.e(MmsService.TAG, "MmsRequest: MMS network acquiring failure", e); - result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS; - } catch (MmsHttpException e) { - Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e); - result = SmsManager.MMS_ERROR_HTTP_FAILURE; - httpStatusCode = e.getStatusCode(); - } catch (Exception e) { - Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e); - result = SmsManager.MMS_ERROR_UNSPECIFIED; - } finally { - // Release MMS network - networkManager.releaseNetwork(); - } - } - // Process result and send back via PendingIntent - returnResult(context, result, response, httpStatusCode); - } - - /** - * Check if the response indicates a failure when we send to wrong APN. - * Sometimes even if you send to the wrong APN, a response in valid PDU format can still - * be sent back but with an error status. Check one specific case here. - * - * TODO: maybe there are other possibilities. - * - * @param response the response data - * @param mmsConfig the carrier configuration values to use - * @return false if we find an invalid response case, otherwise true - */ - static boolean isWrongApnResponse(final byte[] response, final Bundle mmsConfig) { - if (response != null && response.length > 0) { - try { - final GenericPdu pdu = new PduParser( - response, - mmsConfig.getBoolean( - CarrierConfigValuesLoader - .CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, - CarrierConfigValuesLoader - .CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT)) - .parse(); - if (pdu != null && pdu instanceof SendConf) { - final SendConf sendConf = (SendConf) pdu; - final int responseStatus = sendConf.getResponseStatus(); - return responseStatus == - PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED || - responseStatus == - PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED; - } - } catch (RuntimeException e) { - Log.w(MmsService.TAG, "Parsing response failed", e); - } - } - return false; - } - - /** - * Return the result back via pending intent - * - * @param context The context - * @param result The result code of execution - * @param response The response body - * @param httpStatusCode The optional http status code in case of http failure - */ - void returnResult(final Context context, int result, final byte[] response, - final int httpStatusCode) { - if (mPendingIntent == null) { - // Result not needed - return; - } - // Extra information to send back with the pending intent - final Intent fillIn = new Intent(); - if (response != null) { - if (!transferResponse(context, fillIn, response)) { - // Failed to send PDU data back to caller - result = SmsManager.MMS_ERROR_IO_ERROR; - } - } - if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) { - // For HTTP failure, fill in the status code for more information - fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode); - } - try { - mPendingIntent.send(context, result, fillIn); - } catch (PendingIntent.CanceledException e) { - Log.e(MmsService.TAG, "Sending pending intent canceled", e); - } - } - - /** - * Request the route to the APN (either proxy host or the MMSC host) - * - * @param connectivityManager the ConnectivityManager to use - * @param apn the current APN - * @param url the URL to connect to - * @throws MmsHttpException for unknown host or route failure - */ - private static void requestRoute(final ConnectivityManager connectivityManager, - final ApnSettingsLoader.Apn apn, final String url) throws MmsHttpException { - String host = apn.getMmsProxy(); - if (TextUtils.isEmpty(host)) { - final Uri uri = Uri.parse(url); - host = uri.getHost(); - } - boolean success = false; - // Request route to all resolved host addresses - try { - for (final InetAddress addr : InetAddress.getAllByName(host)) { - final boolean requested = requestRouteToHostAddress(connectivityManager, addr); - if (requested) { - success = true; - Log.i(MmsService.TAG, "Requested route to " + addr); - } else { - Log.i(MmsService.TAG, "Could not requested route to " + addr); - } - } - if (!success) { - throw new MmsHttpException(0/*statusCode*/, "No route requested"); - } - } catch (UnknownHostException e) { - Log.w(MmsService.TAG, "Unknown host " + host); - throw new MmsHttpException(0/*statusCode*/, "Unknown host"); - } - } - - private static final Integer TYPE_MOBILE_MMS = - Integer.valueOf(ConnectivityManager.TYPE_MOBILE_MMS); - /** - * Wrapper for platform API requestRouteToHostAddress - * - * We first try the hidden but correct method on ConnectivityManager. If we can't, use - * the old but buggy one - * - * @param connMgr the ConnectivityManager instance - * @param inetAddr the InetAddress to request - * @return true if route is successfully setup, false otherwise - */ - private static boolean requestRouteToHostAddress(final ConnectivityManager connMgr, - final InetAddress inetAddr) { - // First try the good method using reflection - try { - final Method method = connMgr.getClass().getMethod("requestRouteToHostAddress", - Integer.TYPE, InetAddress.class); - if (method != null) { - return (Boolean) method.invoke(connMgr, TYPE_MOBILE_MMS, inetAddr); - } - } catch (Exception e) { - Log.w(MmsService.TAG, "ConnectivityManager.requestRouteToHostAddress failed " + e); - } - // If we fail, try the old but buggy one - if (inetAddr instanceof Inet4Address) { - try { - final Method method = connMgr.getClass().getMethod("requestRouteToHost", - Integer.TYPE, Integer.TYPE); - if (method != null) { - return (Boolean) method.invoke(connMgr, TYPE_MOBILE_MMS, - inetAddressToInt(inetAddr)); - } - } catch (Exception e) { - Log.w(MmsService.TAG, "ConnectivityManager.requestRouteToHost failed " + e); - } - } - return false; - } - - /** - * Convert a IPv4 address from an InetAddress to an integer - * - * @param inetAddr is an InetAddress corresponding to the IPv4 address - * @return the IP address as an integer in network byte order - */ - private static int inetAddressToInt(final InetAddress inetAddr) - throws IllegalArgumentException { - final byte [] addr = inetAddr.getAddress(); - return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) | - ((addr[1] & 0xff) << 8) | (addr[0] & 0xff); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeByte((byte) (mUseWakeLock ? 1 : 0)); - parcel.writeString(mLocationUrl); - parcel.writeParcelable(mPduUri, 0); - parcel.writeParcelable(mPendingIntent, 0); - } - - protected MmsRequest(final Parcel in) { - final ClassLoader classLoader = MmsRequest.class.getClassLoader(); - mUseWakeLock = in.readByte() != 0; - mLocationUrl = in.readString(); - mPduUri = in.readParcelable(classLoader); - mPendingIntent = in.readParcelable(classLoader); - } -} diff --git a/src/android/support/v7/mms/MmsService.java b/src/android/support/v7/mms/MmsService.java deleted file mode 100644 index 650562f..0000000 --- a/src/android/support/v7/mms/MmsService.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.PowerManager; -import android.os.Process; -import android.telephony.SmsManager; -import android.util.Log; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; - -/** - * Service to execute MMS requests using deprecated legacy APIs on older platform (prior to L) - */ -public class MmsService extends Service { - static final String TAG = "MmsLib"; - - //The default number of threads allowed to run MMS requests - private static final int DEFAULT_THREAD_POOL_SIZE = 4; - // Delay before stopping the service - private static final int SERVICE_STOP_DELAY_MILLIS = 2000; - - private static final String EXTRA_REQUEST = "request"; - private static final String EXTRA_MYPID = "mypid"; - - private static final String WAKELOCK_ID = "mmslib_wakelock"; - - /** - * Thread pool size for each request queue - */ - private static volatile int sThreadPoolSize = DEFAULT_THREAD_POOL_SIZE; - - /** - * Optional wake lock to use - */ - private static volatile boolean sUseWakeLock = true; - private static volatile PowerManager.WakeLock sWakeLock = null; - private static final Object sWakeLockLock = new Object(); - - /** - * Carrier configuration values loader - */ - private static volatile CarrierConfigValuesLoader sCarrierConfigValuesLoader = null; - - /** - * APN loader - */ - private static volatile ApnSettingsLoader sApnSettingsLoader = null; - - /** - * UserAgent and UA Prof URL loader - */ - private static volatile UserAgentInfoLoader sUserAgentInfoLoader = null; - - /** - * Set the size of thread pool for request execution. - * Default is DEFAULT_THREAD_POOL_SIZE - * - * @param size thread pool size - */ - static void setThreadPoolSize(final int size) { - sThreadPoolSize = size; - } - - /** - * Set whether to use wake lock - * - * @param useWakeLock true to use wake lock, false otherwise - */ - static void setUseWakeLock(final boolean useWakeLock) { - sUseWakeLock = useWakeLock; - } - - /** - * Set the optional carrier config values - * - * @param loader the carrier config values loader - */ - static void setCarrierConfigValuesLoader(final CarrierConfigValuesLoader loader) { - sCarrierConfigValuesLoader = loader; - } - - /** - * Get the current carrier config values loader - * - * @return the carrier config values loader currently set - */ - static CarrierConfigValuesLoader getCarrierConfigValuesLoader() { - return sCarrierConfigValuesLoader; - } - - /** - * Set APN settings loader - * - * @param loader the APN settings loader - */ - static void setApnSettingsLoader(final ApnSettingsLoader loader) { - sApnSettingsLoader = loader; - } - - /** - * Get the current APN settings loader - * - * @return the APN settings loader currently set - */ - static ApnSettingsLoader getApnSettingsLoader() { - return sApnSettingsLoader; - } - - /** - * Set user agent info loader - * - * @param loader the user agent info loader - */ - static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) { - sUserAgentInfoLoader = loader; - } - - /** - * Get the current user agent info loader - * - * @return the user agent info loader currently set - */ - static UserAgentInfoLoader getUserAgentInfoLoader() { - return sUserAgentInfoLoader; - } - - /** - * Make sure loaders are not null. Set to default if that's the case - * - * @param context the Context to use - */ - private static void ensureLoaders(final Context context) { - if (sUserAgentInfoLoader == null) { - sUserAgentInfoLoader = new DefaultUserAgentInfoLoader(context); - } - if (sCarrierConfigValuesLoader == null) { - sCarrierConfigValuesLoader = new DefaultCarrierConfigValuesLoader(context); - } - if (sApnSettingsLoader == null) { - sApnSettingsLoader = new DefaultApnSettingsLoader(context); - } - } - - /** - * Acquire the wake lock - * - * @param context the context to use - */ - private static void acquireWakeLock(final Context context) { - synchronized (sWakeLockLock) { - if (sWakeLock == null) { - final PowerManager pm = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_ID); - } - sWakeLock.acquire(); - } - } - - /** - * Release the wake lock - */ - private static void releaseWakeLock() { - boolean releasedEmptyWakeLock = false; - synchronized (sWakeLockLock) { - if (sWakeLock != null) { - sWakeLock.release(); - } else { - releasedEmptyWakeLock = true; - } - } - if (releasedEmptyWakeLock) { - Log.w(TAG, "Releasing empty wake lock"); - } - } - - /** - * Check if wake lock is not held (e.g. when service stops) - */ - private static void verifyWakeLockNotHeld() { - boolean wakeLockHeld = false; - synchronized (sWakeLockLock) { - wakeLockHeld = sWakeLock != null && sWakeLock.isHeld(); - } - if (wakeLockHeld) { - Log.e(TAG, "Wake lock still held!"); - } - } - - // Remember my PID to discard restarted intent - private static volatile int sMyPid = -1; - - /** - * Get the current PID - * - * @return the current PID - */ - private static int getMyPid() { - if (sMyPid < 0) { - sMyPid = Process.myPid(); - } - return sMyPid; - } - - /** - * Check if the intent is coming from this process - * - * @param intent the incoming intent for the service - * @return true if the intent is from the current process - */ - private static boolean fromThisProcess(final Intent intent) { - final int pid = intent.getIntExtra(EXTRA_MYPID, -1); - return pid == getMyPid(); - } - - // Request execution thread pools. One thread pool for sending and one for downloading. - // The size of the thread pool controls the parallelism of request execution. - // See {@link setThreadPoolSize} - private ExecutorService[] mExecutors = new ExecutorService[2]; - - // Active request count - private int mActiveRequestCount; - // The latest intent startId, used for safely stopping service - private int mLastStartId; - - private MmsNetworkManager mNetworkManager; - - // Handler for scheduling service stop - private final Handler mHandler = new Handler(); - // Service stop task - private final Runnable mServiceStopRunnable = new Runnable() { - @Override - public void run() { - tryStopService(); - } - }; - - /** - * Start the service with a request - * - * @param context the Context to use - * @param request the request to start - */ - public static void startRequest(final Context context, final MmsRequest request) { - final boolean useWakeLock = sUseWakeLock; - request.setUseWakeLock(useWakeLock); - final Intent intent = new Intent(context, MmsService.class); - intent.putExtra(EXTRA_REQUEST, request); - intent.putExtra(EXTRA_MYPID, getMyPid()); - if (useWakeLock) { - acquireWakeLock(context); - } - if (context.startService(intent) == null) { - if (useWakeLock) { - releaseWakeLock(); - } - } - } - - @Override - public void onCreate() { - super.onCreate(); - - ensureLoaders(this); - - for (int i = 0; i < mExecutors.length; i++) { - mExecutors[i] = Executors.newFixedThreadPool(sThreadPoolSize); - } - - mNetworkManager = new MmsNetworkManager(this); - - synchronized (this) { - mActiveRequestCount = 0; - mLastStartId = -1; - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - - for (ExecutorService executor : mExecutors) { - executor.shutdown(); - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // Always remember the latest startId for use when we try releasing the service - synchronized (this) { - mLastStartId = startId; - } - boolean scheduled = false; - if (intent != null) { - // There is a rare situation that right after a intent is started, - // the service gets killed. Then the service will restart with - // the old intent which we don't want it to run since it will - // break our assumption for wake lock. Check the process ID - // embedded in the intent to make sure it is indeed from the - // the current life of this service. - if (fromThisProcess(intent)) { - final MmsRequest request = intent.getParcelableExtra(EXTRA_REQUEST); - if (request != null) { - try { - retainService(request, new Runnable() { - @Override - public void run() { - try { - request.execute( - MmsService.this, - mNetworkManager, - getApnSettingsLoader(), - getCarrierConfigValuesLoader(), - getUserAgentInfoLoader()); - } catch (Exception e) { - Log.w(TAG, "Unexpected execution failure", e); - } finally { - if (request.getUseWakeLock()) { - releaseWakeLock(); - } - releaseService(); - } - } - }); - scheduled = true; - } catch (RejectedExecutionException e) { - // Rare thing happened. Send back failure using the pending intent - // and also release the wake lock. - Log.w(TAG, "Executing request failed " + e); - request.returnResult(this, SmsManager.MMS_ERROR_UNSPECIFIED, - null/*response*/, 0/*httpStatusCode*/); - if (request.getUseWakeLock()) { - releaseWakeLock(); - } - } - } else { - Log.w(TAG, "Empty request"); - } - } else { - Log.w(TAG, "Got a restarted intent from previous incarnation"); - } - } else { - Log.w(TAG, "Empty intent"); - } - if (!scheduled) { - // If the request is not started successfully, we need to try shutdown the service - // if nobody is using it. - tryScheduleStop(); - } - return START_NOT_STICKY; - } - - /** - * Retain the service for executing the request in service thread pool - * - * @param request The request to execute - * @param runnable The runnable to run the request in thread pool - */ - private void retainService(final MmsRequest request, final Runnable runnable) { - final ExecutorService executor = getRequestExecutor(request); - synchronized (this) { - executor.execute(runnable); - mActiveRequestCount++; - } - } - - /** - * Release the service from the request. If nobody is using it, schedule service stop. - */ - private void releaseService() { - synchronized (this) { - mActiveRequestCount--; - if (mActiveRequestCount <= 0) { - mActiveRequestCount = 0; - rescheduleServiceStop(); - } - } - } - - /** - * Schedule the service stop if there is no active request - */ - private void tryScheduleStop() { - synchronized (this) { - if (mActiveRequestCount == 0) { - rescheduleServiceStop(); - } - } - } - - /** - * Reschedule service stop task - */ - private void rescheduleServiceStop() { - mHandler.removeCallbacks(mServiceStopRunnable); - mHandler.postDelayed(mServiceStopRunnable, SERVICE_STOP_DELAY_MILLIS); - } - - /** - * Really try to stop the service if there is not active request - */ - private void tryStopService() { - Boolean stopped = null; - synchronized (this) { - if (mActiveRequestCount == 0) { - stopped = stopSelfResult(mLastStartId); - } - } - logServiceStop(stopped); - } - - /** - * Log the result of service stopping. Also check wake lock status when service stops. - * - * @param stopped Not empty if service stop is performed: true if really stopped, false - * if cancelled. - */ - private void logServiceStop(final Boolean stopped) { - if (stopped != null) { - if (stopped) { - Log.i(TAG, "Service successfully stopped"); - verifyWakeLockNotHeld(); - } else { - Log.i(TAG, "Service stopping cancelled"); - } - } - } - - private ExecutorService getRequestExecutor(final MmsRequest request) { - if (request instanceof SendRequest) { - // Send - return mExecutors[0]; - } else { - // Download - return mExecutors[1]; - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/src/android/support/v7/mms/MmsXmlResourceParser.java b/src/android/support/v7/mms/MmsXmlResourceParser.java deleted file mode 100644 index 1ee73e2..0000000 --- a/src/android/support/v7/mms/MmsXmlResourceParser.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.util.Log; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -/** - * Base class for a parser of XML resources - */ -abstract class MmsXmlResourceParser { - /** - * Parse the content - * - * @throws IOException - * @throws XmlPullParserException - */ - protected abstract void parseRecord() throws IOException, XmlPullParserException; - - /** - * Get the root tag of the content - * - * @return the text of root tag - */ - protected abstract String getRootTag(); - - private final StringBuilder mLogStringBuilder = new StringBuilder(); - - protected final XmlPullParser mInputParser; - - protected MmsXmlResourceParser(XmlPullParser parser) { - mInputParser = parser; - } - - void parse() { - try { - // Find the first element - if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) { - throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @" - + xmlParserDebugContext()); - } - if (!getRootTag().equals(mInputParser.getName())) { - Log.w(MmsService.TAG, "Carrier config does not start with " + getRootTag()); - return; - } - // We are at the start tag - for (;;) { - int nextEvent; - // Skipping spaces - while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT); - if (nextEvent == XmlPullParser.START_TAG) { - // Parse one record - parseRecord(); - } else if (nextEvent == XmlPullParser.END_TAG) { - break; - } else { - throw new XmlPullParserException("Expecting start or end tag @" - + xmlParserDebugContext()); - } - } - } catch (IOException e) { - Log.w(MmsService.TAG, "XmlResourceParser: I/O failure", e); - } catch (XmlPullParserException e) { - Log.w(MmsService.TAG, "XmlResourceParser: parsing failure", e); - } - } - - /** - * Move XML parser forward to next event type or the end of doc - * - * @param eventType - * @return The final event type we meet - * @throws XmlPullParserException - * @throws IOException - */ - protected int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException { - for (;;) { - int nextEvent = mInputParser.next(); - if (nextEvent == eventType - || nextEvent == XmlPullParser.END_DOCUMENT) { - return nextEvent; - } - } - } - - /** - * @return The debugging information of the parser's current position - */ - protected String xmlParserDebugContext() { - mLogStringBuilder.setLength(0); - if (mInputParser != null) { - try { - final int eventType = mInputParser.getEventType(); - mLogStringBuilder.append(xmlParserEventString(eventType)); - if (eventType == XmlPullParser.START_TAG - || eventType == XmlPullParser.END_TAG - || eventType == XmlPullParser.TEXT) { - mLogStringBuilder.append('<').append(mInputParser.getName()); - for (int i = 0; i < mInputParser.getAttributeCount(); i++) { - mLogStringBuilder.append(' ') - .append(mInputParser.getAttributeName(i)) - .append('=') - .append(mInputParser.getAttributeValue(i)); - } - mLogStringBuilder.append("/>"); - } - return mLogStringBuilder.toString(); - } catch (XmlPullParserException e) { - Log.w(MmsService.TAG, "XmlResourceParser exception", e); - } - } - return "Unknown"; - } - - private static String xmlParserEventString(int event) { - switch (event) { - case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT"; - case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT"; - case XmlPullParser.START_TAG: return "START_TAG"; - case XmlPullParser.END_TAG: return "END_TAG"; - case XmlPullParser.TEXT: return "TEXT"; - } - return Integer.toString(event); - } -} diff --git a/src/android/support/v7/mms/PhoneNumberHelper.java b/src/android/support/v7/mms/PhoneNumberHelper.java deleted file mode 100644 index 99551c0..0000000 --- a/src/android/support/v7/mms/PhoneNumberHelper.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.text.TextUtils; -import android.util.Log; - -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber; - -/** - * Helper methods for phone number formatting - * This is isolated into a standalone class since it depends on libphonenumber - */ -public class PhoneNumberHelper { - /** - * Given a phone number, get its national part without country code - * - * @param number the original number - * @param country the country ISO code - * @return the national number - */ - static String getNumberNoCountryCode(final String number, final String country) { - if (!TextUtils.isEmpty(number)) { - final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - try { - final Phonenumber.PhoneNumber phoneNumber = phoneNumberUtil.parse(number, country); - if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) { - return phoneNumberUtil - .format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.NATIONAL) - .replaceAll("\\D", ""); - } - } catch (final NumberParseException e) { - Log.w(MmsService.TAG, "getNumberNoCountryCode: invalid number " + e); - } - } - return number; - } -} diff --git a/src/android/support/v7/mms/SendRequest.java b/src/android/support/v7/mms/SendRequest.java deleted file mode 100644 index 1b7b1db..0000000 --- a/src/android/support/v7/mms/SendRequest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.ParcelFileDescriptor; -import android.os.Parcelable; -import android.telephony.SmsManager; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - * Request to send an MMS - */ -class SendRequest extends MmsRequest { - // Max send response PDU size in bytes (exceeding this may cause problem with - // system intent delivery). - private static final int MAX_SEND_RESPONSE_SIZE = 1000 * 1024; - - private byte[] mPduData; - - SendRequest(final String locationUrl, final Uri pduUri, final PendingIntent sentIntent) { - super(locationUrl, pduUri, sentIntent); - } - - @Override - protected boolean loadRequest(final Context context, final Bundle mmsConfig) { - mPduData = readPduFromContentUri( - context, - mPduUri, - mmsConfig.getInt( - CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, - CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT)); - return (mPduData != null); - } - - @Override - protected boolean transferResponse(final Context context, final Intent fillIn, - final byte[] response) { - // SendConf pdus are always small and can be included in the intent - if (response != null && fillIn != null) { - if (response.length > MAX_SEND_RESPONSE_SIZE) { - // If the response PDU is too large, it won't be able to fit in - // the PendingIntent to be transferred via system IPC. - return false; - } - fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response); - } - return true; - } - - @Override - protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettingsLoader.Apn apn, - Bundle mmsConfig, String userAgent, String uaProfUrl) throws MmsHttpException { - final MmsHttpClient httpClient = netMgr.getHttpClient(); - return httpClient.execute(getHttpRequestUrl(apn), mPduData, MmsHttpClient.METHOD_POST, - !TextUtils.isEmpty(apn.getMmsProxy()), apn.getMmsProxy(), apn.getMmsProxyPort(), - mmsConfig, userAgent, uaProfUrl); - } - - @Override - protected String getHttpRequestUrl(final ApnSettingsLoader.Apn apn) { - return !TextUtils.isEmpty(mLocationUrl) ? mLocationUrl : apn.getMmsc(); - } - - /** - * Read pdu from content provider uri - * - * @param contentUri content provider uri from which to read - * @param maxSize maximum number of bytes to read - * @return pdu bytes if succeeded else null - */ - public byte[] readPduFromContentUri(final Context context, final Uri contentUri, - final int maxSize) { - if (contentUri == null) { - return null; - } - final Callable copyPduToArray = new Callable() { - public byte[] call() { - ParcelFileDescriptor.AutoCloseInputStream inStream = null; - try { - final ContentResolver cr = context.getContentResolver(); - final ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "r"); - inStream = new ParcelFileDescriptor.AutoCloseInputStream(pduFd); - // Request one extra byte to make sure file not bigger than maxSize - final byte[] readBuf = new byte[maxSize+1]; - final int bytesRead = inStream.read(readBuf, 0, maxSize+1); - if (bytesRead <= 0) { - Log.e(MmsService.TAG, "Reading PDU from sender: empty PDU"); - return null; - } - if (bytesRead > maxSize) { - Log.e(MmsService.TAG, "Reading PDU from sender: PDU too large"); - return null; - } - // Copy and return the exact length of bytes - final byte[] result = new byte[bytesRead]; - System.arraycopy(readBuf, 0, result, 0, bytesRead); - return result; - } catch (IOException e) { - Log.e(MmsService.TAG, "Reading PDU from sender: IO exception", e); - return null; - } finally { - if (inStream != null) { - try { - inStream.close(); - } catch (IOException ex) { - // Ignore - } - } - } - } - }; - final Future pendingResult = mPduTransferExecutor.submit(copyPduToArray); - try { - return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - // Typically a timeout occurred - cancel task - pendingResult.cancel(true); - } - return null; - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public SendRequest createFromParcel(Parcel in) { - return new SendRequest(in); - } - - public SendRequest[] newArray(int size) { - return new SendRequest[size]; - } - }; - - private SendRequest(Parcel in) { - super(in); - } -} diff --git a/src/android/support/v7/mms/UserAgentInfoLoader.java b/src/android/support/v7/mms/UserAgentInfoLoader.java deleted file mode 100644 index 214d58d..0000000 --- a/src/android/support/v7/mms/UserAgentInfoLoader.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -/** - * Interface to load UserAgent and UA Prof URL - */ -public interface UserAgentInfoLoader { - // Carrier configuration keys for passing as config overrides into system MMS service - public static final String CONFIG_USER_AGENT = "userAgent"; - public static final String CONFIG_UA_PROF_URL = "uaProfUrl"; - - /** - * Get UserAgent value - * - * @return the text of UserAgent - */ - String getUserAgent(); - - /** - * Get UA Profile URL - * - * @return the URL of UA profile - */ - String getUAProfUrl(); -} diff --git a/src/android/support/v7/mms/Utils.java b/src/android/support/v7/mms/Utils.java deleted file mode 100644 index cb55c2e..0000000 --- a/src/android/support/v7/mms/Utils.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms; - -import android.content.Context; -import android.content.res.Configuration; -import android.os.Build; -import android.telephony.SmsManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Utility methods - */ -class Utils { - /** - * Check if MMS API is available - * - * @return true if MMS API is available, false otherwise - */ - static boolean hasMmsApi() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - } - - /** - * Check if support multi-SIM - * - * @return true if MSIM is supported, false otherwise - */ - static boolean supportMSim() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1; - } - - /** - * Check if support APIs for getting UserAgent and UAProfUrl - * - * @return true if those APIs are supported, false otherwise - */ - static boolean hasUserAgentApi() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - } - - /** - * Get system SmsManager - * - * @param subId the subscription ID of the SmsManager - * @return the SmsManager for the input subId - */ - static SmsManager getSmsManager(final int subId) { - if (supportMSim()) { - return SmsManager.getSmsManagerForSubscriptionId(subId); - } else { - return SmsManager.getDefault(); - } - } - - /** - * Get the real subscription ID if the input is -1 - * - * @param subId input subscription ID - * @return the default SMS subscription ID if the input is -1, otherwise the original - */ - static int getEffectiveSubscriptionId(int subId) { - if (supportMSim()) { - if (subId == MmsManager.DEFAULT_SUB_ID) { - subId = SmsManager.getDefaultSmsSubscriptionId(); - } - } - if (subId < 0) { - subId = MmsManager.DEFAULT_SUB_ID; - } - return subId; - } - - /** - * Get MCC/MNC of an SIM subscription - * - * @param context the Context to use - * @param subId the SIM subId - * @return a non-empty array with exactly two elements, first is mcc and last is mnc. - */ - static int[] getMccMnc(final Context context, final int subId) { - final int[] mccMnc = new int[] { 0, 0 }; - if (Utils.supportMSim()) { - final SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - final SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subId); - if (subInfo != null) { - mccMnc[0] = subInfo.getMcc(); - mccMnc[1] = subInfo.getMnc(); - } - } else { - final TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - final String mccMncString = telephonyManager.getSimOperator(); - try { - mccMnc[0] = Integer.parseInt(mccMncString.substring(0, 3)); - mccMnc[1] = Integer.parseInt(mccMncString.substring(3)); - } catch (Exception e) { - Log.w(MmsService.TAG, "Invalid mcc/mnc from system " + mccMncString + ": " + e); - mccMnc[0] = 0; - mccMnc[1] = 0; - } - } - return mccMnc; - } - - /** - * Get a subscription's Context so we can load resources from it - * - * @param context the sub-independent Context - * @param subId the SIM's subId - * @return the sub-dependent Context - */ - static Context getSubDepContext(final Context context, final int subId) { - if (!supportMSim()) { - return context; - } - final int[] mccMnc = getMccMnc(context, subId); - final int mcc = mccMnc[0]; - final int mnc = mccMnc[1]; - if (mcc == 0 && mnc == 0) { - return context; - } - final Configuration subConfig = new Configuration(); - subConfig.mcc = mcc; - subConfig.mnc = mnc; - return context.createConfigurationContext(subConfig); - } - - /** - * Redact the URL for non-VERBOSE logging. Replace url with only the host part and the length - * of the input URL string. - * - * @param urlString - * @return - */ - static String redactUrlForNonVerbose(String urlString) { - if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) { - // Don't redact for VERBOSE level logging - return urlString; - } - if (TextUtils.isEmpty(urlString)) { - return urlString; - } - String protocol = "http"; - String host = ""; - try { - final URL url = new URL(urlString); - protocol = url.getProtocol(); - host = url.getHost(); - } catch (MalformedURLException e) { - // Ignore - } - // Print "http://host[length]" - final StringBuilder sb = new StringBuilder(); - sb.append(protocol).append("://").append(host) - .append("[").append(urlString.length()).append("]"); - return sb.toString(); - } -} diff --git a/src/android/support/v7/mms/pdu/AcknowledgeInd.java b/src/android/support/v7/mms/pdu/AcknowledgeInd.java deleted file mode 100644 index 10f9e35..0000000 --- a/src/android/support/v7/mms/pdu/AcknowledgeInd.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * M-Acknowledge.ind PDU. - */ -public class AcknowledgeInd extends GenericPdu { - /** - * Constructor, used when composing a M-Acknowledge.ind pdu. - * - * @param mmsVersion current viersion of mms - * @param transactionId the transaction-id value - * @throws InvalidHeaderValueException if parameters are invalid. - * NullPointerException if transactionId is null. - */ - public AcknowledgeInd(int mmsVersion, byte[] transactionId) - throws InvalidHeaderValueException { - super(); - - setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - AcknowledgeInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Report-Allowed field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getReportAllowed() { - return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Report-Allowed field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReportAllowed(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } -} diff --git a/src/android/support/v7/mms/pdu/Base64.java b/src/android/support/v7/mms/pdu/Base64.java deleted file mode 100644 index 9f3418f..0000000 --- a/src/android/support/v7/mms/pdu/Base64.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class Base64 { - /** - * Used to get the number of Quadruples. - */ - static final int FOURBYTE = 4; - - /** - * Byte used to pad output. - */ - static final byte PAD = (byte) '='; - - /** - * The base length. - */ - static final int BASELENGTH = 255; - - // Create arrays to hold the base64 characters - private static byte[] base64Alphabet = new byte[BASELENGTH]; - - // Populating the character arrays - static { - for (int i = 0; i < BASELENGTH; i++) { - base64Alphabet[i] = (byte) -1; - } - for (int i = 'Z'; i >= 'A'; i--) { - base64Alphabet[i] = (byte) (i - 'A'); - } - for (int i = 'z'; i >= 'a'; i--) { - base64Alphabet[i] = (byte) (i - 'a' + 26); - } - for (int i = '9'; i >= '0'; i--) { - base64Alphabet[i] = (byte) (i - '0' + 52); - } - - base64Alphabet['+'] = 62; - base64Alphabet['/'] = 63; - } - - /** - * Decodes Base64 data into octects - * - * @param base64Data Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(byte[] base64Data) { - // RFC 2045 requires that we discard ALL non-Base64 characters - base64Data = discardNonBase64(base64Data); - - // handle the edge case, so we don't have to worry about it later - if (base64Data.length == 0) { - return new byte[0]; - } - - int numberQuadruple = base64Data.length / FOURBYTE; - byte decodedData[] = null; - byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; - - // Throw away anything not in base64Data - - int encodedIndex = 0; - int dataIndex = 0; - { - // this sizes the output array properly - rlw - int lastData = base64Data.length; - // ignore the '=' padding - while (base64Data[lastData - 1] == PAD) { - if (--lastData == 0) { - return new byte[0]; - } - } - decodedData = new byte[lastData - numberQuadruple]; - } - - for (int i = 0; i < numberQuadruple; i++) { - dataIndex = i * 4; - marker0 = base64Data[dataIndex + 2]; - marker1 = base64Data[dataIndex + 3]; - - b1 = base64Alphabet[base64Data[dataIndex]]; - b2 = base64Alphabet[base64Data[dataIndex + 1]]; - - if (marker0 != PAD && marker1 != PAD) { - //No PAD e.g 3cQl - b3 = base64Alphabet[marker0]; - b4 = base64Alphabet[marker1]; - - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - decodedData[encodedIndex + 1] = - (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); - decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); - } else if (marker0 == PAD) { - //Two PAD e.g. 3c[Pad][Pad] - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - } else if (marker1 == PAD) { - //One PAD e.g. 3cQ[Pad] - b3 = base64Alphabet[marker0]; - - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - decodedData[encodedIndex + 1] = - (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); - } - encodedIndex += 3; - } - return decodedData; - } - - /** - * Check octect wheter it is a base64 encoding. - * - * @param octect to be checked byte - * @return ture if it is base64 encoding, false otherwise. - */ - private static boolean isBase64(byte octect) { - if (octect == PAD) { - return true; - } else if (base64Alphabet[octect] == -1) { - return false; - } else { - return true; - } - } - - /** - * Discards any characters outside of the base64 alphabet, per - * the requirements on page 25 of RFC 2045 - "Any characters - * outside of the base64 alphabet are to be ignored in base64 - * encoded data." - * - * @param data The base-64 encoded data to groom - * @return The data, less non-base64 characters (see RFC 2045). - */ - static byte[] discardNonBase64(byte[] data) { - byte groomedData[] = new byte[data.length]; - int bytesCopied = 0; - - for (int i = 0; i < data.length; i++) { - if (isBase64(data[i])) { - groomedData[bytesCopied++] = data[i]; - } - } - - byte packedData[] = new byte[bytesCopied]; - - System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); - - return packedData; - } -} diff --git a/src/android/support/v7/mms/pdu/CharacterSets.java b/src/android/support/v7/mms/pdu/CharacterSets.java deleted file mode 100644 index 8ce2a02..0000000 --- a/src/android/support/v7/mms/pdu/CharacterSets.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import java.io.UnsupportedEncodingException; -import java.util.HashMap; - -public class CharacterSets { - /** - * IANA assigned MIB enum numbers. - * - * From wap-230-wsp-20010705-a.pdf - * Any-charset = - * Equivalent to the special RFC2616 charset value "*" - */ - public static final int ANY_CHARSET = 0x00; - public static final int US_ASCII = 0x03; - public static final int ISO_8859_1 = 0x04; - public static final int ISO_8859_2 = 0x05; - public static final int ISO_8859_3 = 0x06; - public static final int ISO_8859_4 = 0x07; - public static final int ISO_8859_5 = 0x08; - public static final int ISO_8859_6 = 0x09; - public static final int ISO_8859_7 = 0x0A; - public static final int ISO_8859_8 = 0x0B; - public static final int ISO_8859_9 = 0x0C; - public static final int SHIFT_JIS = 0x11; - public static final int UTF_8 = 0x6A; - public static final int BIG5 = 0x07EA; - public static final int UCS2 = 0x03E8; - public static final int UTF_16 = 0x03F7; - - /** - * If the encoding of given data is unsupported, use UTF_8 to decode it. - */ - public static final int DEFAULT_CHARSET = UTF_8; - - /** - * Array of MIB enum numbers. - */ - private static final int[] MIBENUM_NUMBERS = { - ANY_CHARSET, - US_ASCII, - ISO_8859_1, - ISO_8859_2, - ISO_8859_3, - ISO_8859_4, - ISO_8859_5, - ISO_8859_6, - ISO_8859_7, - ISO_8859_8, - ISO_8859_9, - SHIFT_JIS, - UTF_8, - BIG5, - UCS2, - UTF_16, - }; - - /** - * The Well-known-charset Mime name. - */ - public static final String MIMENAME_ANY_CHARSET = "*"; - public static final String MIMENAME_US_ASCII = "us-ascii"; - public static final String MIMENAME_ISO_8859_1 = "iso-8859-1"; - public static final String MIMENAME_ISO_8859_2 = "iso-8859-2"; - public static final String MIMENAME_ISO_8859_3 = "iso-8859-3"; - public static final String MIMENAME_ISO_8859_4 = "iso-8859-4"; - public static final String MIMENAME_ISO_8859_5 = "iso-8859-5"; - public static final String MIMENAME_ISO_8859_6 = "iso-8859-6"; - public static final String MIMENAME_ISO_8859_7 = "iso-8859-7"; - public static final String MIMENAME_ISO_8859_8 = "iso-8859-8"; - public static final String MIMENAME_ISO_8859_9 = "iso-8859-9"; - public static final String MIMENAME_SHIFT_JIS = "shift_JIS"; - public static final String MIMENAME_UTF_8 = "utf-8"; - public static final String MIMENAME_BIG5 = "big5"; - public static final String MIMENAME_UCS2 = "iso-10646-ucs-2"; - public static final String MIMENAME_UTF_16 = "utf-16"; - - public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8; - - /** - * Array of the names of character sets. - */ - private static final String[] MIME_NAMES = { - MIMENAME_ANY_CHARSET, - MIMENAME_US_ASCII, - MIMENAME_ISO_8859_1, - MIMENAME_ISO_8859_2, - MIMENAME_ISO_8859_3, - MIMENAME_ISO_8859_4, - MIMENAME_ISO_8859_5, - MIMENAME_ISO_8859_6, - MIMENAME_ISO_8859_7, - MIMENAME_ISO_8859_8, - MIMENAME_ISO_8859_9, - MIMENAME_SHIFT_JIS, - MIMENAME_UTF_8, - MIMENAME_BIG5, - MIMENAME_UCS2, - MIMENAME_UTF_16, - }; - - private static final HashMap MIBENUM_TO_NAME_MAP; - private static final HashMap NAME_TO_MIBENUM_MAP; - - static { - // Create the HashMaps. - MIBENUM_TO_NAME_MAP = new HashMap(); - NAME_TO_MIBENUM_MAP = new HashMap(); - assert(MIBENUM_NUMBERS.length == MIME_NAMES.length); - int count = MIBENUM_NUMBERS.length - 1; - for(int i = 0; i <= count; i++) { - MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]); - NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]); - } - } - - private CharacterSets() {} // Non-instantiatable - - /** - * Map an MIBEnum number to the name of the charset which this number - * is assigned to by IANA. - * - * @param mibEnumValue An IANA assigned MIBEnum number. - * @return The name string of the charset. - * @throws UnsupportedEncodingException - */ - public static String getMimeName(int mibEnumValue) - throws UnsupportedEncodingException { - String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue); - if (name == null) { - throw new UnsupportedEncodingException(); - } - return name; - } - - /** - * Map a well-known charset name to its assigned MIBEnum number. - * - * @param mimeName The charset name. - * @return The MIBEnum number assigned by IANA for this charset. - * @throws UnsupportedEncodingException - */ - public static int getMibEnumValue(String mimeName) - throws UnsupportedEncodingException { - if(null == mimeName) { - return -1; - } - - Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName); - if (mibEnumValue == null) { - throw new UnsupportedEncodingException(); - } - return mibEnumValue; - } -} diff --git a/src/android/support/v7/mms/pdu/ContentType.java b/src/android/support/v7/mms/pdu/ContentType.java deleted file mode 100644 index 7ba961d..0000000 --- a/src/android/support/v7/mms/pdu/ContentType.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import java.util.ArrayList; - -public class ContentType { - public static final String MMS_MESSAGE = "application/vnd.wap.mms-message"; - // The phony content type for generic PDUs (e.g. ReadOrig.ind, - // Notification.ind, Delivery.ind). - public static final String MMS_GENERIC = "application/vnd.wap.mms-generic"; - public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed"; - public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related"; - public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative"; - - public static final String TEXT_PLAIN = "text/plain"; - public static final String TEXT_HTML = "text/html"; - public static final String TEXT_VCALENDAR = "text/x-vCalendar"; - public static final String TEXT_VCARD = "text/x-vCard"; - - public static final String IMAGE_UNSPECIFIED = "image/*"; - public static final String IMAGE_JPEG = "image/jpeg"; - public static final String IMAGE_JPG = "image/jpg"; - public static final String IMAGE_GIF = "image/gif"; - public static final String IMAGE_WBMP = "image/vnd.wap.wbmp"; - public static final String IMAGE_PNG = "image/png"; - public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp"; - - public static final String AUDIO_UNSPECIFIED = "audio/*"; - public static final String AUDIO_AAC = "audio/aac"; - public static final String AUDIO_AMR = "audio/amr"; - public static final String AUDIO_IMELODY = "audio/imelody"; - public static final String AUDIO_MID = "audio/mid"; - public static final String AUDIO_MIDI = "audio/midi"; - public static final String AUDIO_MP3 = "audio/mp3"; - public static final String AUDIO_MPEG3 = "audio/mpeg3"; - public static final String AUDIO_MPEG = "audio/mpeg"; - public static final String AUDIO_MPG = "audio/mpg"; - public static final String AUDIO_MP4 = "audio/mp4"; - public static final String AUDIO_X_MID = "audio/x-mid"; - public static final String AUDIO_X_MIDI = "audio/x-midi"; - public static final String AUDIO_X_MP3 = "audio/x-mp3"; - public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3"; - public static final String AUDIO_X_MPEG = "audio/x-mpeg"; - public static final String AUDIO_X_MPG = "audio/x-mpg"; - public static final String AUDIO_3GPP = "audio/3gpp"; - public static final String AUDIO_X_WAV = "audio/x-wav"; - public static final String AUDIO_OGG = "application/ogg"; - - public static final String VIDEO_UNSPECIFIED = "video/*"; - public static final String VIDEO_3GPP = "video/3gpp"; - public static final String VIDEO_3G2 = "video/3gpp2"; - public static final String VIDEO_H263 = "video/h263"; - public static final String VIDEO_MP4 = "video/mp4"; - - public static final String APP_SMIL = "application/smil"; - public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml"; - public static final String APP_XHTML = "application/xhtml+xml"; - - public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content"; - public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message"; - - private static final ArrayList sSupportedContentTypes = new ArrayList(); - private static final ArrayList sSupportedImageTypes = new ArrayList(); - private static final ArrayList sSupportedAudioTypes = new ArrayList(); - private static final ArrayList sSupportedVideoTypes = new ArrayList(); - - static { - sSupportedContentTypes.add(TEXT_PLAIN); - sSupportedContentTypes.add(TEXT_HTML); - sSupportedContentTypes.add(TEXT_VCALENDAR); - sSupportedContentTypes.add(TEXT_VCARD); - - sSupportedContentTypes.add(IMAGE_JPEG); - sSupportedContentTypes.add(IMAGE_GIF); - sSupportedContentTypes.add(IMAGE_WBMP); - sSupportedContentTypes.add(IMAGE_PNG); - sSupportedContentTypes.add(IMAGE_JPG); - sSupportedContentTypes.add(IMAGE_X_MS_BMP); - //supportedContentTypes.add(IMAGE_SVG); not yet supported. - - sSupportedContentTypes.add(AUDIO_AAC); - sSupportedContentTypes.add(AUDIO_AMR); - sSupportedContentTypes.add(AUDIO_IMELODY); - sSupportedContentTypes.add(AUDIO_MID); - sSupportedContentTypes.add(AUDIO_MIDI); - sSupportedContentTypes.add(AUDIO_MP3); - sSupportedContentTypes.add(AUDIO_MP4); - sSupportedContentTypes.add(AUDIO_MPEG3); - sSupportedContentTypes.add(AUDIO_MPEG); - sSupportedContentTypes.add(AUDIO_MPG); - sSupportedContentTypes.add(AUDIO_X_MID); - sSupportedContentTypes.add(AUDIO_X_MIDI); - sSupportedContentTypes.add(AUDIO_X_MP3); - sSupportedContentTypes.add(AUDIO_X_MPEG3); - sSupportedContentTypes.add(AUDIO_X_MPEG); - sSupportedContentTypes.add(AUDIO_X_MPG); - sSupportedContentTypes.add(AUDIO_X_WAV); - sSupportedContentTypes.add(AUDIO_3GPP); - sSupportedContentTypes.add(AUDIO_OGG); - - sSupportedContentTypes.add(VIDEO_3GPP); - sSupportedContentTypes.add(VIDEO_3G2); - sSupportedContentTypes.add(VIDEO_H263); - sSupportedContentTypes.add(VIDEO_MP4); - - sSupportedContentTypes.add(APP_SMIL); - sSupportedContentTypes.add(APP_WAP_XHTML); - sSupportedContentTypes.add(APP_XHTML); - - sSupportedContentTypes.add(APP_DRM_CONTENT); - sSupportedContentTypes.add(APP_DRM_MESSAGE); - - // add supported image types - sSupportedImageTypes.add(IMAGE_JPEG); - sSupportedImageTypes.add(IMAGE_GIF); - sSupportedImageTypes.add(IMAGE_WBMP); - sSupportedImageTypes.add(IMAGE_PNG); - sSupportedImageTypes.add(IMAGE_JPG); - sSupportedImageTypes.add(IMAGE_X_MS_BMP); - - // add supported audio types - sSupportedAudioTypes.add(AUDIO_AAC); - sSupportedAudioTypes.add(AUDIO_AMR); - sSupportedAudioTypes.add(AUDIO_IMELODY); - sSupportedAudioTypes.add(AUDIO_MID); - sSupportedAudioTypes.add(AUDIO_MIDI); - sSupportedAudioTypes.add(AUDIO_MP3); - sSupportedAudioTypes.add(AUDIO_MPEG3); - sSupportedAudioTypes.add(AUDIO_MPEG); - sSupportedAudioTypes.add(AUDIO_MPG); - sSupportedAudioTypes.add(AUDIO_MP4); - sSupportedAudioTypes.add(AUDIO_X_MID); - sSupportedAudioTypes.add(AUDIO_X_MIDI); - sSupportedAudioTypes.add(AUDIO_X_MP3); - sSupportedAudioTypes.add(AUDIO_X_MPEG3); - sSupportedAudioTypes.add(AUDIO_X_MPEG); - sSupportedAudioTypes.add(AUDIO_X_MPG); - sSupportedAudioTypes.add(AUDIO_X_WAV); - sSupportedAudioTypes.add(AUDIO_3GPP); - sSupportedAudioTypes.add(AUDIO_OGG); - - // add supported video types - sSupportedVideoTypes.add(VIDEO_3GPP); - sSupportedVideoTypes.add(VIDEO_3G2); - sSupportedVideoTypes.add(VIDEO_H263); - sSupportedVideoTypes.add(VIDEO_MP4); - } - - // This class should never be instantiated. - private ContentType() { - } - - public static boolean isSupportedType(String contentType) { - return (null != contentType) && sSupportedContentTypes.contains(contentType); - } - - public static boolean isSupportedImageType(String contentType) { - return isImageType(contentType) && isSupportedType(contentType); - } - - public static boolean isSupportedAudioType(String contentType) { - return isAudioType(contentType) && isSupportedType(contentType); - } - - public static boolean isSupportedVideoType(String contentType) { - return isVideoType(contentType) && isSupportedType(contentType); - } - - public static boolean isTextType(String contentType) { - return (null != contentType) && contentType.startsWith("text/"); - } - - public static boolean isImageType(String contentType) { - return (null != contentType) && contentType.startsWith("image/"); - } - - public static boolean isAudioType(String contentType) { - return (null != contentType) && contentType.startsWith("audio/"); - } - - public static boolean isVideoType(String contentType) { - return (null != contentType) && contentType.startsWith("video/"); - } - - public static boolean isDrmType(String contentType) { - return (null != contentType) - && (contentType.equals(APP_DRM_CONTENT) - || contentType.equals(APP_DRM_MESSAGE)); - } - - public static boolean isUnspecified(String contentType) { - return (null != contentType) && contentType.endsWith("*"); - } - - @SuppressWarnings("unchecked") - public static ArrayList getImageTypes() { - return (ArrayList) sSupportedImageTypes.clone(); - } - - @SuppressWarnings("unchecked") - public static ArrayList getAudioTypes() { - return (ArrayList) sSupportedAudioTypes.clone(); - } - - @SuppressWarnings("unchecked") - public static ArrayList getVideoTypes() { - return (ArrayList) sSupportedVideoTypes.clone(); - } - - @SuppressWarnings("unchecked") - public static ArrayList getSupportedTypes() { - return (ArrayList) sSupportedContentTypes.clone(); - } -} diff --git a/src/android/support/v7/mms/pdu/DeliveryInd.java b/src/android/support/v7/mms/pdu/DeliveryInd.java deleted file mode 100644 index d7d72d9..0000000 --- a/src/android/support/v7/mms/pdu/DeliveryInd.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * M-Delivery.Ind Pdu. - */ -public class DeliveryInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public DeliveryInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - DeliveryInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value, should not be null - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get Status value. - * - * @return the value - */ - public int getStatus() { - return mPduHeaders.getOctet(PduHeaders.STATUS); - } - - /** - * Set Status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.STATUS); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public EncodedStringValue getStatusText() {return null;} - * public void setStatusText(EncodedStringValue value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/EncodedStringValue.java b/src/android/support/v7/mms/pdu/EncodedStringValue.java deleted file mode 100644 index d8fcacf..0000000 --- a/src/android/support/v7/mms/pdu/EncodedStringValue.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - -/** - * Encoded-string-value = Text-string | Value-length Char-set Text-string - */ -public class EncodedStringValue implements Cloneable { - private static final String TAG = "EncodedStringValue"; - private static final boolean DEBUG = false; - private static final boolean LOCAL_LOGV = false; - - /** - * The Char-set value. - */ - private int mCharacterSet; - - /** - * The Text-string value. - */ - private byte[] mData; - - /** - * Constructor. - * - * @param charset the Char-set value - * @param data the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public EncodedStringValue(int charset, byte[] data) { - // TODO: CharSet needs to be validated against MIBEnum. - if(null == data) { - throw new NullPointerException("EncodedStringValue: Text-string is null."); - } - - mCharacterSet = charset; - mData = new byte[data.length]; - System.arraycopy(data, 0, mData, 0, data.length); - } - - /** - * Constructor. - * - * @param data the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public EncodedStringValue(byte[] data) { - this(CharacterSets.DEFAULT_CHARSET, data); - } - - public EncodedStringValue(String data) { - try { - mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME); - mCharacterSet = CharacterSets.DEFAULT_CHARSET; - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Default encoding must be supported.", e); - } - } - - /** - * Get Char-set value. - * - * @return the value - */ - public int getCharacterSet() { - return mCharacterSet; - } - - /** - * Set Char-set value. - * - * @param charset the Char-set value - */ - public void setCharacterSet(int charset) { - // TODO: CharSet needs to be validated against MIBEnum. - mCharacterSet = charset; - } - - /** - * Get Text-string value. - * - * @return the value - */ - public byte[] getTextString() { - byte[] byteArray = new byte[mData.length]; - - System.arraycopy(mData, 0, byteArray, 0, mData.length); - return byteArray; - } - - /** - * Set Text-string value. - * - * @param textString the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public void setTextString(byte[] textString) { - if(null == textString) { - throw new NullPointerException("EncodedStringValue: Text-string is null."); - } - - mData = new byte[textString.length]; - System.arraycopy(textString, 0, mData, 0, textString.length); - } - - /** - * Convert this object to a {@link java.lang.String}. If the encoding of - * the EncodedStringValue is null or unsupported, it will be - * treated as iso-8859-1 encoding. - * - * @return The decoded String. - */ - public String getString() { - if (CharacterSets.ANY_CHARSET == mCharacterSet) { - return new String(mData); // system default encoding. - } else { - try { - String name = CharacterSets.getMimeName(mCharacterSet); - return new String(mData, name); - } catch (UnsupportedEncodingException e) { - if (LOCAL_LOGV) { - Log.v(TAG, e.getMessage(), e); - } - try { - return new String(mData, CharacterSets.MIMENAME_ISO_8859_1); - } catch (UnsupportedEncodingException _) { - return new String(mData); // system default encoding. - } - } - } - } - - /** - * Append to Text-string. - * - * @param textString the textString to append - * @throws NullPointerException if the text String is null - * or an IOException occured. - */ - public void appendTextString(byte[] textString) { - if(null == textString) { - throw new NullPointerException("Text-string is null."); - } - - if(null == mData) { - mData = new byte[textString.length]; - System.arraycopy(textString, 0, mData, 0, textString.length); - } else { - ByteArrayOutputStream newTextString = new ByteArrayOutputStream(); - try { - newTextString.write(mData); - newTextString.write(textString); - } catch (IOException e) { - e.printStackTrace(); - throw new NullPointerException( - "appendTextString: failed when write a new Text-string"); - } - - mData = newTextString.toByteArray(); - } - } - - /* - * (non-Javadoc) - * @see java.lang.Object#clone() - */ - @Override - public Object clone() throws CloneNotSupportedException { - super.clone(); - int len = mData.length; - byte[] dstBytes = new byte[len]; - System.arraycopy(mData, 0, dstBytes, 0, len); - - try { - return new EncodedStringValue(mCharacterSet, dstBytes); - } catch (Exception e) { - Log.e(TAG, "failed to clone an EncodedStringValue: " + this); - e.printStackTrace(); - throw new CloneNotSupportedException(e.getMessage()); - } - } - - /** - * Split this encoded string around matches of the given pattern. - * - * @param pattern the delimiting pattern - * @return the array of encoded strings computed by splitting this encoded - * string around matches of the given pattern - */ - public EncodedStringValue[] split(String pattern) { - String[] temp = getString().split(pattern); - EncodedStringValue[] ret = new EncodedStringValue[temp.length]; - for (int i = 0; i < ret.length; ++i) { - try { - ret[i] = new EncodedStringValue(mCharacterSet, - temp[i].getBytes()); - } catch (NullPointerException _) { - // Can't arrive here - return null; - } - } - return ret; - } - - /** - * Extract an EncodedStringValue[] from a given String. - */ - public static EncodedStringValue[] extract(String src) { - String[] values = src.split(";"); - - ArrayList list = new ArrayList(); - for (int i = 0; i < values.length; i++) { - if (values[i].length() > 0) { - list.add(new EncodedStringValue(values[i])); - } - } - - int len = list.size(); - if (len > 0) { - return list.toArray(new EncodedStringValue[len]); - } else { - return null; - } - } - - /** - * Concatenate an EncodedStringValue[] into a single String. - */ - public static String concat(EncodedStringValue[] addr) { - StringBuilder sb = new StringBuilder(); - int maxIndex = addr.length - 1; - for (int i = 0; i <= maxIndex; i++) { - sb.append(addr[i].getString()); - if (i < maxIndex) { - sb.append(";"); - } - } - - return sb.toString(); - } - - public static EncodedStringValue copy(EncodedStringValue value) { - if (value == null) { - return null; - } - - return new EncodedStringValue(value.mCharacterSet, value.mData); - } - - public static EncodedStringValue[] encodeStrings(String[] array) { - int count = array.length; - if (count > 0) { - EncodedStringValue[] encodedArray = new EncodedStringValue[count]; - for (int i = 0; i < count; i++) { - encodedArray[i] = new EncodedStringValue(array[i]); - } - return encodedArray; - } - return null; - } -} diff --git a/src/android/support/v7/mms/pdu/GenericPdu.java b/src/android/support/v7/mms/pdu/GenericPdu.java deleted file mode 100644 index 2f0d167..0000000 --- a/src/android/support/v7/mms/pdu/GenericPdu.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class GenericPdu { - /** - * The headers of pdu. - */ - PduHeaders mPduHeaders = null; - - /** - * Constructor. - */ - public GenericPdu() { - mPduHeaders = new PduHeaders(); - } - - /** - * Constructor. - * - * @param headers Headers for this PDU. - */ - GenericPdu(PduHeaders headers) { - mPduHeaders = headers; - } - - /** - * Get the headers of this PDU. - * - * @return A PduHeaders of this PDU. - */ - PduHeaders getPduHeaders() { - return mPduHeaders; - } - - /** - * Get X-Mms-Message-Type field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getMessageType() { - return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE); - } - - /** - * Set X-Mms-Message-Type field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if field's value is not Octet. - */ - public void setMessageType(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE); - } - - /** - * Get X-Mms-MMS-Version field value. - * - * @return the X-Mms-MMS-Version value - */ - public int getMmsVersion() { - return mPduHeaders.getOctet(PduHeaders.MMS_VERSION); - } - - /** - * Set X-Mms-MMS-Version field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if field's value is not Octet. - */ - public void setMmsVersion(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } -} diff --git a/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java b/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java deleted file mode 100644 index 4b1de02..0000000 --- a/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * Thrown when an invalid header value was set. - */ -public class InvalidHeaderValueException extends MmsException { - private static final long serialVersionUID = -2053384496042052262L; - - /** - * Constructs an InvalidHeaderValueException with no detailed message. - */ - public InvalidHeaderValueException() { - super(); - } - - /** - * Constructs an InvalidHeaderValueException with the specified detailed message. - * - * @param message the detailed message. - */ - public InvalidHeaderValueException(String message) { - super(message); - } -} diff --git a/src/android/support/v7/mms/pdu/MmsException.java b/src/android/support/v7/mms/pdu/MmsException.java deleted file mode 100644 index 1fd1a02..0000000 --- a/src/android/support/v7/mms/pdu/MmsException.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * A generic exception that is thrown by the Mms client. - */ -public class MmsException extends Exception { - private static final long serialVersionUID = -7323249827281485390L; - - /** - * Creates a new MmsException. - */ - public MmsException() { - super(); - } - - /** - * Creates a new MmsException with the specified detail message. - * - * @param message the detail message. - */ - public MmsException(String message) { - super(message); - } - - /** - * Creates a new MmsException with the specified cause. - * - * @param cause the cause. - */ - public MmsException(Throwable cause) { - super(cause); - } - - /** - * Creates a new MmsException with the specified detail message and cause. - * - * @param message the detail message. - * @param cause the cause. - */ - public MmsException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java b/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java deleted file mode 100644 index 12f3b6d..0000000 --- a/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * Multimedia message PDU. - */ -public class MultimediaMessagePdu extends GenericPdu{ - /** - * The body. - */ - private PduBody mMessageBody; - - /** - * Constructor. - */ - public MultimediaMessagePdu() { - super(); - } - - /** - * Constructor. - * - * @param header the header of this PDU - * @param body the body of this PDU - */ - public MultimediaMessagePdu(PduHeaders header, PduBody body) { - super(header); - mMessageBody = body; - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - MultimediaMessagePdu(PduHeaders headers) { - super(headers); - } - - /** - * Get body of the PDU. - * - * @return the body - */ - public PduBody getBody() { - return mMessageBody; - } - - /** - * Set body of the PDU. - * - * @param body the body - */ - public void setBody(PduBody body) { - mMessageBody = body; - } - - /** - * Get subject. - * - * @return the value - */ - public EncodedStringValue getSubject() { - return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); - } - - /** - * Set subject. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setSubject(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Add a "To" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addTo(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO); - } - - /** - * Get X-Mms-Priority value. - * - * @return the value - */ - public int getPriority() { - return mPduHeaders.getOctet(PduHeaders.PRIORITY); - } - - /** - * Set X-Mms-Priority value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setPriority(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.PRIORITY); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value in seconds. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } -} diff --git a/src/android/support/v7/mms/pdu/NotificationInd.java b/src/android/support/v7/mms/pdu/NotificationInd.java deleted file mode 100644 index 16aa23d..0000000 --- a/src/android/support/v7/mms/pdu/NotificationInd.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * M-Notification.ind PDU. - */ -public class NotificationInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - * RuntimeException if an undeclared error occurs. - */ - public NotificationInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - NotificationInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Content-Class Value. - * - * @return the value - */ - public int getContentClass() { - return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS); - } - - /** - * Set X-Mms-Content-Class Value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if an undeclared error occurs. - */ - public void setContentClass(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS); - } - - /** - * Get X-Mms-Content-Location value. - * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: - * Content-location-value = Uri-value - * - * @return the value - */ - public byte[] getContentLocation() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION); - } - - /** - * Set X-Mms-Content-Location value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setContentLocation(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION); - } - - /** - * Get X-Mms-Expiry value. - * - * Expiry-value = Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) - * - * @return the value - */ - public long getExpiry() { - return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); - } - - /** - * Set X-Mms-Expiry value. - * - * @param value the value - * @throws RuntimeException if an undeclared error occurs. - */ - public void setExpiry(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get X-Mms-Message-Size value. - * Message-size-value = Long-integer - * - * @return the value - */ - public long getMessageSize() { - return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); - } - - /** - * Set X-Mms-Message-Size value. - * - * @param value the value - * @throws RuntimeException if an undeclared error occurs. - */ - public void setMessageSize(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); - } - - /** - * Get subject. - * - * @return the value - */ - public EncodedStringValue getSubject() { - return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); - } - - /** - * Set subject. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setSubject(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); - } - - /** - * Get X-Mms-Transaction-Id. - * - * @return the value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /** - * Get X-Mms-Delivery-Report Value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report Value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if an undeclared error occurs. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public byte getDistributionIndicator() {return 0x00;} - * public void setDistributionIndicator(byte value) {} - * - * public ElementDescriptorValue getElementDescriptor() {return null;} - * public void getElementDescriptor(ElementDescriptorValue value) {} - * - * public byte getPriority() {return 0x00;} - * public void setPriority(byte value) {} - * - * public byte getRecommendedRetrievalMode() {return 0x00;} - * public void setRecommendedRetrievalMode(byte value) {} - * - * public byte getRecommendedRetrievalModeText() {return 0x00;} - * public void setRecommendedRetrievalModeText(byte value) {} - * - * public byte[] getReplaceId() {return 0x00;} - * public void setReplaceId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - * - * public byte getStored() {return 0x00;} - * public void setStored(byte value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/NotifyRespInd.java b/src/android/support/v7/mms/pdu/NotifyRespInd.java deleted file mode 100644 index cb8e92b..0000000 --- a/src/android/support/v7/mms/pdu/NotifyRespInd.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * M-NofifyResp.ind PDU. - */ -public class NotifyRespInd extends GenericPdu { - /** - * Constructor, used when composing a M-NotifyResp.ind pdu. - * - * @param mmsVersion current version of mms - * @param transactionId the transaction-id value - * @param status the status value - * @throws InvalidHeaderValueException if parameters are invalid. - * NullPointerException if transactionId is null. - * RuntimeException if an undeclared error occurs. - */ - public NotifyRespInd(int mmsVersion, - byte[] transactionId, - int status) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - setStatus(status); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - NotifyRespInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Report-Allowed field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getReportAllowed() { - return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Report-Allowed field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if an undeclared error occurs. - */ - public void setReportAllowed(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Status field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * RuntimeException if an undeclared error occurs. - */ - public void setStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.STATUS); - } - - /** - * GetX-Mms-Status field value. - * - * @return the X-Mms-Status value - */ - public int getStatus() { - return mPduHeaders.getOctet(PduHeaders.STATUS); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * RuntimeException if an undeclared error occurs. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } -} diff --git a/src/android/support/v7/mms/pdu/PduBody.java b/src/android/support/v7/mms/pdu/PduBody.java deleted file mode 100644 index 9d983fc..0000000 --- a/src/android/support/v7/mms/pdu/PduBody.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; - -public class PduBody { - private Vector mParts = null; - - private Map mPartMapByContentId = null; - private Map mPartMapByContentLocation = null; - private Map mPartMapByName = null; - private Map mPartMapByFileName = null; - - /** - * Constructor. - */ - public PduBody() { - mParts = new Vector(); - - mPartMapByContentId = new HashMap(); - mPartMapByContentLocation = new HashMap(); - mPartMapByName = new HashMap(); - mPartMapByFileName = new HashMap(); - } - - private void putPartToMaps(PduPart part) { - // Put part to mPartMapByContentId. - byte[] contentId = part.getContentId(); - if(null != contentId) { - mPartMapByContentId.put(new String(contentId), part); - } - - // Put part to mPartMapByContentLocation. - byte[] contentLocation = part.getContentLocation(); - if(null != contentLocation) { - String clc = new String(contentLocation); - mPartMapByContentLocation.put(clc, part); - } - - // Put part to mPartMapByName. - byte[] name = part.getName(); - if(null != name) { - String clc = new String(name); - mPartMapByName.put(clc, part); - } - - // Put part to mPartMapByFileName. - byte[] fileName = part.getFilename(); - if(null != fileName) { - String clc = new String(fileName); - mPartMapByFileName.put(clc, part); - } - } - - /** - * Appends the specified part to the end of this body. - * - * @param part part to be appended - * @return true when success, false when fail - * @throws NullPointerException when part is null - */ - public boolean addPart(PduPart part) { - if(null == part) { - throw new NullPointerException(); - } - - putPartToMaps(part); - return mParts.add(part); - } - - /** - * Inserts the specified part at the specified position. - * - * @param index index at which the specified part is to be inserted - * @param part part to be inserted - * @throws NullPointerException when part is null - */ - public void addPart(int index, PduPart part) { - if(null == part) { - throw new NullPointerException(); - } - - putPartToMaps(part); - mParts.add(index, part); - } - - /** - * Removes the part at the specified position. - * - * @param index index of the part to return - * @return part at the specified index - */ - public PduPart removePart(int index) { - return mParts.remove(index); - } - - /** - * Remove all of the parts. - */ - public void removeAll() { - mParts.clear(); - } - - /** - * Get the part at the specified position. - * - * @param index index of the part to return - * @return part at the specified index - */ - public PduPart getPart(int index) { - return mParts.get(index); - } - - /** - * Get the index of the specified part. - * - * @param part the part object - * @return index the index of the first occurrence of the part in this body - */ - public int getPartIndex(PduPart part) { - return mParts.indexOf(part); - } - - /** - * Get the number of parts. - * - * @return the number of parts - */ - public int getPartsNum() { - return mParts.size(); - } - - /** - * Get pdu part by content id. - * - * @param cid the value of content id. - * @return the pdu part. - */ - public PduPart getPartByContentId(String cid) { - return mPartMapByContentId.get(cid); - } - - /** - * Get pdu part by Content-Location. Content-Location of part is - * the same as filename and name(param of content-type). - * - * @param contentLocation the content location. - * @return the pdu part. - */ - public PduPart getPartByContentLocation(String contentLocation) { - return mPartMapByContentLocation.get(contentLocation); - } - - /** - * Get pdu part by name. - * - * @param name the value of filename. - * @return the pdu part. - */ - public PduPart getPartByName(String name) { - return mPartMapByName.get(name); - } - - /** - * Get pdu part by filename. - * - * @param filename the value of filename. - * @return the pdu part. - */ - public PduPart getPartByFileName(String filename) { - return mPartMapByFileName.get(filename); - } -} diff --git a/src/android/support/v7/mms/pdu/PduContentTypes.java b/src/android/support/v7/mms/pdu/PduContentTypes.java deleted file mode 100644 index 7cd0ecf..0000000 --- a/src/android/support/v7/mms/pdu/PduContentTypes.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class PduContentTypes { - /** - * All content types. From: - * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm - */ - static final String[] contentTypes = { - "*/*", /* 0x00 */ - "text/*", /* 0x01 */ - "text/html", /* 0x02 */ - "text/plain", /* 0x03 */ - "text/x-hdml", /* 0x04 */ - "text/x-ttml", /* 0x05 */ - "text/x-vCalendar", /* 0x06 */ - "text/x-vCard", /* 0x07 */ - "text/vnd.wap.wml", /* 0x08 */ - "text/vnd.wap.wmlscript", /* 0x09 */ - "text/vnd.wap.wta-event", /* 0x0A */ - "multipart/*", /* 0x0B */ - "multipart/mixed", /* 0x0C */ - "multipart/form-data", /* 0x0D */ - "multipart/byterantes", /* 0x0E */ - "multipart/alternative", /* 0x0F */ - "application/*", /* 0x10 */ - "application/java-vm", /* 0x11 */ - "application/x-www-form-urlencoded", /* 0x12 */ - "application/x-hdmlc", /* 0x13 */ - "application/vnd.wap.wmlc", /* 0x14 */ - "application/vnd.wap.wmlscriptc", /* 0x15 */ - "application/vnd.wap.wta-eventc", /* 0x16 */ - "application/vnd.wap.uaprof", /* 0x17 */ - "application/vnd.wap.wtls-ca-certificate", /* 0x18 */ - "application/vnd.wap.wtls-user-certificate", /* 0x19 */ - "application/x-x509-ca-cert", /* 0x1A */ - "application/x-x509-user-cert", /* 0x1B */ - "image/*", /* 0x1C */ - "image/gif", /* 0x1D */ - "image/jpeg", /* 0x1E */ - "image/tiff", /* 0x1F */ - "image/png", /* 0x20 */ - "image/vnd.wap.wbmp", /* 0x21 */ - "application/vnd.wap.multipart.*", /* 0x22 */ - "application/vnd.wap.multipart.mixed", /* 0x23 */ - "application/vnd.wap.multipart.form-data", /* 0x24 */ - "application/vnd.wap.multipart.byteranges", /* 0x25 */ - "application/vnd.wap.multipart.alternative", /* 0x26 */ - "application/xml", /* 0x27 */ - "text/xml", /* 0x28 */ - "application/vnd.wap.wbxml", /* 0x29 */ - "application/x-x968-cross-cert", /* 0x2A */ - "application/x-x968-ca-cert", /* 0x2B */ - "application/x-x968-user-cert", /* 0x2C */ - "text/vnd.wap.si", /* 0x2D */ - "application/vnd.wap.sic", /* 0x2E */ - "text/vnd.wap.sl", /* 0x2F */ - "application/vnd.wap.slc", /* 0x30 */ - "text/vnd.wap.co", /* 0x31 */ - "application/vnd.wap.coc", /* 0x32 */ - "application/vnd.wap.multipart.related", /* 0x33 */ - "application/vnd.wap.sia", /* 0x34 */ - "text/vnd.wap.connectivity-xml", /* 0x35 */ - "application/vnd.wap.connectivity-wbxml", /* 0x36 */ - "application/pkcs7-mime", /* 0x37 */ - "application/vnd.wap.hashed-certificate", /* 0x38 */ - "application/vnd.wap.signed-certificate", /* 0x39 */ - "application/vnd.wap.cert-response", /* 0x3A */ - "application/xhtml+xml", /* 0x3B */ - "application/wml+xml", /* 0x3C */ - "text/css", /* 0x3D */ - "application/vnd.wap.mms-message", /* 0x3E */ - "application/vnd.wap.rollover-certificate", /* 0x3F */ - "application/vnd.wap.locc+wbxml", /* 0x40 */ - "application/vnd.wap.loc+xml", /* 0x41 */ - "application/vnd.syncml.dm+wbxml", /* 0x42 */ - "application/vnd.syncml.dm+xml", /* 0x43 */ - "application/vnd.syncml.notification", /* 0x44 */ - "application/vnd.wap.xhtml+xml", /* 0x45 */ - "application/vnd.wv.csp.cir", /* 0x46 */ - "application/vnd.oma.dd+xml", /* 0x47 */ - "application/vnd.oma.drm.message", /* 0x48 */ - "application/vnd.oma.drm.content", /* 0x49 */ - "application/vnd.oma.drm.rights+xml", /* 0x4A */ - "application/vnd.oma.drm.rights+wbxml", /* 0x4B */ - "application/vnd.wv.csp+xml", /* 0x4C */ - "application/vnd.wv.csp+wbxml", /* 0x4D */ - "application/vnd.syncml.ds.notification", /* 0x4E */ - "audio/*", /* 0x4F */ - "video/*", /* 0x50 */ - "application/vnd.oma.dd2+xml", /* 0x51 */ - "application/mikey" /* 0x52 */ - }; -} diff --git a/src/android/support/v7/mms/pdu/PduHeaders.java b/src/android/support/v7/mms/pdu/PduHeaders.java deleted file mode 100644 index 0271e58..0000000 --- a/src/android/support/v7/mms/pdu/PduHeaders.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import java.util.ArrayList; -import java.util.HashMap; - -public class PduHeaders { - /** - * All pdu header fields. - */ - public static final int BCC = 0x81; - public static final int CC = 0x82; - public static final int CONTENT_LOCATION = 0x83; - public static final int CONTENT_TYPE = 0x84; - public static final int DATE = 0x85; - public static final int DELIVERY_REPORT = 0x86; - public static final int DELIVERY_TIME = 0x87; - public static final int EXPIRY = 0x88; - public static final int FROM = 0x89; - public static final int MESSAGE_CLASS = 0x8A; - public static final int MESSAGE_ID = 0x8B; - public static final int MESSAGE_TYPE = 0x8C; - public static final int MMS_VERSION = 0x8D; - public static final int MESSAGE_SIZE = 0x8E; - public static final int PRIORITY = 0x8F; - - public static final int READ_REPLY = 0x90; - public static final int READ_REPORT = 0x90; - public static final int REPORT_ALLOWED = 0x91; - public static final int RESPONSE_STATUS = 0x92; - public static final int RESPONSE_TEXT = 0x93; - public static final int SENDER_VISIBILITY = 0x94; - public static final int STATUS = 0x95; - public static final int SUBJECT = 0x96; - public static final int TO = 0x97; - public static final int TRANSACTION_ID = 0x98; - public static final int RETRIEVE_STATUS = 0x99; - public static final int RETRIEVE_TEXT = 0x9A; - public static final int READ_STATUS = 0x9B; - public static final int REPLY_CHARGING = 0x9C; - public static final int REPLY_CHARGING_DEADLINE = 0x9D; - public static final int REPLY_CHARGING_ID = 0x9E; - public static final int REPLY_CHARGING_SIZE = 0x9F; - - public static final int PREVIOUSLY_SENT_BY = 0xA0; - public static final int PREVIOUSLY_SENT_DATE = 0xA1; - public static final int STORE = 0xA2; - public static final int MM_STATE = 0xA3; - public static final int MM_FLAGS = 0xA4; - public static final int STORE_STATUS = 0xA5; - public static final int STORE_STATUS_TEXT = 0xA6; - public static final int STORED = 0xA7; - public static final int ATTRIBUTES = 0xA8; - public static final int TOTALS = 0xA9; - public static final int MBOX_TOTALS = 0xAA; - public static final int QUOTAS = 0xAB; - public static final int MBOX_QUOTAS = 0xAC; - public static final int MESSAGE_COUNT = 0xAD; - public static final int CONTENT = 0xAE; - public static final int START = 0xAF; - - public static final int ADDITIONAL_HEADERS = 0xB0; - public static final int DISTRIBUTION_INDICATOR = 0xB1; - public static final int ELEMENT_DESCRIPTOR = 0xB2; - public static final int LIMIT = 0xB3; - public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4; - public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5; - public static final int STATUS_TEXT = 0xB6; - public static final int APPLIC_ID = 0xB7; - public static final int REPLY_APPLIC_ID = 0xB8; - public static final int AUX_APPLIC_ID = 0xB9; - public static final int CONTENT_CLASS = 0xBA; - public static final int DRM_CONTENT = 0xBB; - public static final int ADAPTATION_ALLOWED = 0xBC; - public static final int REPLACE_ID = 0xBD; - public static final int CANCEL_ID = 0xBE; - public static final int CANCEL_STATUS = 0xBF; - - /** - * X-Mms-Message-Type field types. - */ - public static final int MESSAGE_TYPE_SEND_REQ = 0x80; - public static final int MESSAGE_TYPE_SEND_CONF = 0x81; - public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82; - public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83; - public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; - public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85; - public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86; - public static final int MESSAGE_TYPE_READ_REC_IND = 0x87; - public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88; - public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89; - public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A; - public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B; - public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C; - public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D; - public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E; - public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F; - public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90; - public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91; - public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92; - public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93; - public static final int MESSAGE_TYPE_DELETE_REQ = 0x94; - public static final int MESSAGE_TYPE_DELETE_CONF = 0x95; - public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96; - public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97; - - /** - * X-Mms-Delivery-Report | - * X-Mms-Read-Report | - * X-Mms-Report-Allowed | - * X-Mms-Sender-Visibility | - * X-Mms-Store | - * X-Mms-Stored | - * X-Mms-Totals | - * X-Mms-Quotas | - * X-Mms-Distribution-Indicator | - * X-Mms-DRM-Content | - * X-Mms-Adaptation-Allowed | - * field types. - */ - public static final int VALUE_YES = 0x80; - public static final int VALUE_NO = 0x81; - - /** - * Delivery-Time | - * Expiry and Reply-Charging-Deadline | - * field type components. - */ - public static final int VALUE_ABSOLUTE_TOKEN = 0x80; - public static final int VALUE_RELATIVE_TOKEN = 0x81; - - /** - * X-Mms-MMS-Version field types. - */ - public static final int MMS_VERSION_1_3 = ((1 << 4) | 3); - public static final int MMS_VERSION_1_2 = ((1 << 4) | 2); - public static final int MMS_VERSION_1_1 = ((1 << 4) | 1); - public static final int MMS_VERSION_1_0 = ((1 << 4) | 0); - - // Current version is 1.2. - public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2; - - /** - * From field type components. - */ - public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80; - public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81; - - public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token"; - public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token"; - - /** - * X-Mms-Status Field. - */ - public static final int STATUS_EXPIRED = 0x80; - public static final int STATUS_RETRIEVED = 0x81; - public static final int STATUS_REJECTED = 0x82; - public static final int STATUS_DEFERRED = 0x83; - public static final int STATUS_UNRECOGNIZED = 0x84; - public static final int STATUS_INDETERMINATE = 0x85; - public static final int STATUS_FORWARDED = 0x86; - public static final int STATUS_UNREACHABLE = 0x87; - - /** - * MM-Flags field type components. - */ - public static final int MM_FLAGS_ADD_TOKEN = 0x80; - public static final int MM_FLAGS_REMOVE_TOKEN = 0x81; - public static final int MM_FLAGS_FILTER_TOKEN = 0x82; - - /** - * X-Mms-Message-Class field types. - */ - public static final int MESSAGE_CLASS_PERSONAL = 0x80; - public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81; - public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82; - public static final int MESSAGE_CLASS_AUTO = 0x83; - - public static final String MESSAGE_CLASS_PERSONAL_STR = "personal"; - public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement"; - public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational"; - public static final String MESSAGE_CLASS_AUTO_STR = "auto"; - - /** - * X-Mms-Priority field types. - */ - public static final int PRIORITY_LOW = 0x80; - public static final int PRIORITY_NORMAL = 0x81; - public static final int PRIORITY_HIGH = 0x82; - - /** - * X-Mms-Response-Status field types. - */ - public static final int RESPONSE_STATUS_OK = 0x80; - public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81; - public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82; - - public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83; - public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84; - - public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85; - public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86; - public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87; - public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4; - - public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 0xE3; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE4; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 0xE5; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 0xE6; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 0xE8; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 0xE9; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 0xEA; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID = 0xEB; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_END = 0xFF; - - /** - * X-Mms-Retrieve-Status field types. - */ - public static final int RETRIEVE_STATUS_OK = 0x80; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3; - public static final int RETRIEVE_STATUS_ERROR_END = 0xFF; - - /** - * X-Mms-Sender-Visibility field types. - */ - public static final int SENDER_VISIBILITY_HIDE = 0x80; - public static final int SENDER_VISIBILITY_SHOW = 0x81; - - /** - * X-Mms-Read-Status field types. - */ - public static final int READ_STATUS_READ = 0x80; - public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81; - - /** - * X-Mms-Cancel-Status field types. - */ - public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80; - public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81; - - /** - * X-Mms-Reply-Charging field types. - */ - public static final int REPLY_CHARGING_REQUESTED = 0x80; - public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81; - public static final int REPLY_CHARGING_ACCEPTED = 0x82; - public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83; - - /** - * X-Mms-MM-State field types. - */ - public static final int MM_STATE_DRAFT = 0x80; - public static final int MM_STATE_SENT = 0x81; - public static final int MM_STATE_NEW = 0x82; - public static final int MM_STATE_RETRIEVED = 0x83; - public static final int MM_STATE_FORWARDED = 0x84; - - /** - * X-Mms-Recommended-Retrieval-Mode field types. - */ - public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80; - - /** - * X-Mms-Content-Class field types. - */ - public static final int CONTENT_CLASS_TEXT = 0x80; - public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81; - public static final int CONTENT_CLASS_IMAGE_RICH = 0x82; - public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83; - public static final int CONTENT_CLASS_VIDEO_RICH = 0x84; - public static final int CONTENT_CLASS_MEGAPIXEL = 0x85; - public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86; - public static final int CONTENT_CLASS_CONTENT_RICH = 0x87; - - /** - * X-Mms-Store-Status field types. - */ - public static final int STORE_STATUS_SUCCESS = 0x80; - public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1; - public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; - public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; - public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; - public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3; - public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4; - public static final int STORE_STATUS_ERROR_END = 0xFF; - - /** - * The map contains the value of all headers. - */ - private HashMap mHeaderMap = null; - - /** - * Constructor of PduHeaders. - */ - public PduHeaders() { - mHeaderMap = new HashMap(); - } - - /** - * Get octet value by header field. - * - * @param field the field - * @return the octet value of the pdu header - * with specified header field. Return 0 if - * the value is not set. - */ - protected int getOctet(int field) { - Integer octet = (Integer) mHeaderMap.get(field); - if (null == octet) { - return 0; - } - - return octet; - } - - /** - * Set octet value to pdu header by header field. - * - * @param value the value - * @param field the field - * @throws InvalidHeaderValueException if the value is invalid. - */ - protected void setOctet(int value, int field) - throws InvalidHeaderValueException{ - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - switch (field) { - case REPORT_ALLOWED: - case ADAPTATION_ALLOWED: - case DELIVERY_REPORT: - case DRM_CONTENT: - case DISTRIBUTION_INDICATOR: - case QUOTAS: - case READ_REPORT: - case STORE: - case STORED: - case TOTALS: - case SENDER_VISIBILITY: - if ((VALUE_YES != value) && (VALUE_NO != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case READ_STATUS: - if ((READ_STATUS_READ != value) && - (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case CANCEL_STATUS: - if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) && - (CANCEL_STATUS_REQUEST_CORRUPTED != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case PRIORITY: - if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case STATUS: - if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case REPLY_CHARGING: - if ((value < REPLY_CHARGING_REQUESTED) - || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case MM_STATE: - if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case RECOMMENDED_RETRIEVAL_MODE: - if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case CONTENT_CLASS: - if ((value < CONTENT_CLASS_TEXT) - || (value > CONTENT_CLASS_CONTENT_RICH)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case RETRIEVE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value. - if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && - (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) && - (value <= RETRIEVE_STATUS_ERROR_END)) { - value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; - } else if ((value < RETRIEVE_STATUS_OK) || - ((value > RETRIEVE_STATUS_OK) && - (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > RETRIEVE_STATUS_ERROR_END)) { - value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case STORE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value. - if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && - (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = STORE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) && - (value <= STORE_STATUS_ERROR_END)) { - value = STORE_STATUS_ERROR_PERMANENT_FAILURE; - } else if ((value < STORE_STATUS_SUCCESS) || - ((value > STORE_STATUS_SUCCESS) && - (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > STORE_STATUS_ERROR_END)) { - value = STORE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case RESPONSE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value. - if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) && - (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) && - (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) || - (value < RESPONSE_STATUS_OK) || - ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) && - (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) { - value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case MMS_VERSION: - if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) { - value = CURRENT_MMS_VERSION; // Current version is the default value. - } - break; - case MESSAGE_TYPE: - if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - default: - // This header value should not be Octect. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } - - /** - * Get TextString value by header field. - * - * @param field the field - * @return the TextString value of the pdu header - * with specified header field - */ - protected byte[] getTextString(int field) { - return (byte[]) mHeaderMap.get(field); - } - - /** - * Set TextString value to pdu header by header field. - * - * @param value the value - * @param field the field - * @return the TextString value of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setTextString(byte[] value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case TRANSACTION_ID: - case REPLY_CHARGING_ID: - case AUX_APPLIC_ID: - case APPLIC_ID: - case REPLY_APPLIC_ID: - case MESSAGE_ID: - case REPLACE_ID: - case CANCEL_ID: - case CONTENT_LOCATION: - case MESSAGE_CLASS: - case CONTENT_TYPE: - break; - default: - // This header value should not be Text-String. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } - - /** - * Get EncodedStringValue value by header field. - * - * @param field the field - * @return the EncodedStringValue value of the pdu header - * with specified header field - */ - protected EncodedStringValue getEncodedStringValue(int field) { - return (EncodedStringValue) mHeaderMap.get(field); - } - - /** - * Get TO, CC or BCC header value. - * - * @param field the field - * @return the EncodeStringValue array of the pdu header - * with specified header field - */ - protected EncodedStringValue[] getEncodedStringValues(int field) { - ArrayList list = - (ArrayList) mHeaderMap.get(field); - if (null == list) { - return null; - } - EncodedStringValue[] values = new EncodedStringValue[list.size()]; - return list.toArray(values); - } - - /** - * Set EncodedStringValue value to pdu header by header field. - * - * @param value the value - * @param field the field - * @return the EncodedStringValue value of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setEncodedStringValue(EncodedStringValue value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case SUBJECT: - case RECOMMENDED_RETRIEVAL_MODE_TEXT: - case RETRIEVE_TEXT: - case STATUS_TEXT: - case STORE_STATUS_TEXT: - case RESPONSE_TEXT: - case FROM: - case PREVIOUSLY_SENT_BY: - case MM_FLAGS: - break; - default: - // This header value should not be Encoded-String-Value. - throw new RuntimeException("Invalid header field!"); - } - - mHeaderMap.put(field, value); - } - - /** - * Set TO, CC or BCC header value. - * - * @param value the value - * @param field the field - * @return the EncodedStringValue value array of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setEncodedStringValues(EncodedStringValue[] value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case BCC: - case CC: - case TO: - break; - default: - // This header value should not be Encoded-String-Value. - throw new RuntimeException("Invalid header field!"); - } - - ArrayList list = new ArrayList(); - for (int i = 0; i < value.length; i++) { - list.add(value[i]); - } - mHeaderMap.put(field, list); - } - - /** - * Append one EncodedStringValue to another. - * - * @param value the EncodedStringValue to append - * @param field the field - * @throws NullPointerException if the value is null. - */ - protected void appendEncodedStringValue(EncodedStringValue value, - int field) { - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case BCC: - case CC: - case TO: - break; - default: - throw new RuntimeException("Invalid header field!"); - } - - ArrayList list = - (ArrayList) mHeaderMap.get(field); - if (null == list) { - list = new ArrayList(); - } - list.add(value); - mHeaderMap.put(field, list); - } - - /** - * Get LongInteger value by header field. - * - * @param field the field - * @return the LongInteger value of the pdu header - * with specified header field. if return -1, the - * field is not existed in pdu header. - */ - protected long getLongInteger(int field) { - Long longInteger = (Long) mHeaderMap.get(field); - if (null == longInteger) { - return -1; - } - - return longInteger.longValue(); - } - - /** - * Set LongInteger value to pdu header by header field. - * - * @param value the value - * @param field the field - */ - protected void setLongInteger(long value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - switch (field) { - case DATE: - case REPLY_CHARGING_SIZE: - case MESSAGE_SIZE: - case MESSAGE_COUNT: - case START: - case LIMIT: - case DELIVERY_TIME: - case EXPIRY: - case REPLY_CHARGING_DEADLINE: - case PREVIOUSLY_SENT_DATE: - break; - default: - // This header value should not be LongInteger. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } -} diff --git a/src/android/support/v7/mms/pdu/PduParser.java b/src/android/support/v7/mms/pdu/PduParser.java deleted file mode 100755 index 7d30df5..0000000 --- a/src/android/support/v7/mms/pdu/PduParser.java +++ /dev/null @@ -1,2008 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.HashMap; - -public class PduParser { - /** - * The next are WAP values defined in WSP specification. - */ - private static final int QUOTE = 127; - private static final int LENGTH_QUOTE = 31; - private static final int TEXT_MIN = 32; - private static final int TEXT_MAX = 127; - private static final int SHORT_INTEGER_MAX = 127; - private static final int SHORT_LENGTH_MAX = 30; - private static final int LONG_INTEGER_LENGTH_MAX = 8; - private static final int QUOTED_STRING_FLAG = 34; - private static final int END_STRING_FLAG = 0x00; - //The next two are used by the interface "parseWapString" to - //distinguish Text-String and Quoted-String. - private static final int TYPE_TEXT_STRING = 0; - private static final int TYPE_QUOTED_STRING = 1; - private static final int TYPE_TOKEN_STRING = 2; - - /** - * Specify the part position. - */ - private static final int THE_FIRST_PART = 0; - private static final int THE_LAST_PART = 1; - - /** - * The pdu data. - */ - private ByteArrayInputStream mPduDataStream = null; - - /** - * Store pdu headers - */ - private PduHeaders mHeaders = null; - - /** - * Store pdu parts. - */ - private PduBody mBody = null; - - /** - * Store the "type" parameter in "Content-Type" header field. - */ - private static byte[] mTypeParam = null; - - /** - * Store the "start" parameter in "Content-Type" header field. - */ - private static byte[] mStartParam = null; - - /** - * The log tag. - */ - private static final String LOG_TAG = "PduParser"; - private static final boolean DEBUG = false; - private static final boolean LOCAL_LOGV = false; - - /** - * Whether to parse content-disposition part header - */ - private final boolean mParseContentDisposition; - - /** - * Constructor. - * - * @param pduDataStream pdu data to be parsed - * @param parseContentDisposition whether to parse the Content-Disposition part header - */ - public PduParser(byte[] pduDataStream, boolean parseContentDisposition) { - mPduDataStream = new ByteArrayInputStream(pduDataStream); - mParseContentDisposition = parseContentDisposition; - } - - /** - * Parse the pdu. - * - * @return the pdu structure if parsing successfully. - * null if parsing error happened or mandatory fields are not set. - */ - public GenericPdu parse(){ - if (mPduDataStream == null) { - return null; - } - - /* parse headers */ - mHeaders = parseHeaders(mPduDataStream); - if (null == mHeaders) { - // Parse headers failed. - return null; - } - - /* get the message type */ - int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE); - - /* check mandatory header fields */ - if (false == checkMandatoryHeader(mHeaders)) { - log("check mandatory headers failed!"); - return null; - } - - if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) || - (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) { - /* need to parse the parts */ - mBody = parseParts(mPduDataStream); - if (null == mBody) { - // Parse parts failed. - return null; - } - } - - switch (messageType) { - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ"); - } - SendReq sendReq = new SendReq(mHeaders, mBody); - return sendReq; - case PduHeaders.MESSAGE_TYPE_SEND_CONF: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF"); - } - SendConf sendConf = new SendConf(mHeaders); - return sendConf; - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND"); - } - NotificationInd notificationInd = - new NotificationInd(mHeaders); - return notificationInd; - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND"); - } - NotifyRespInd notifyRespInd = - new NotifyRespInd(mHeaders); - return notifyRespInd; - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF"); - } - RetrieveConf retrieveConf = - new RetrieveConf(mHeaders, mBody); - - byte[] contentType = retrieveConf.getContentType(); - if (null == contentType) { - return null; - } - String ctTypeStr = new String(contentType); - if (ctTypeStr.equals(ContentType.MULTIPART_MIXED) - || ctTypeStr.equals(ContentType.MULTIPART_RELATED) - || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { - // The MMS content type must be "application/vnd.wap.multipart.mixed" - // or "application/vnd.wap.multipart.related" - // or "application/vnd.wap.multipart.alternative" - return retrieveConf; - } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { - // "application/vnd.wap.multipart.alternative" - // should take only the first part. - PduPart firstPart = mBody.getPart(0); - mBody.removeAll(); - mBody.addPart(0, firstPart); - return retrieveConf; - } - return null; - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND"); - } - DeliveryInd deliveryInd = - new DeliveryInd(mHeaders); - return deliveryInd; - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND"); - } - AcknowledgeInd acknowledgeInd = - new AcknowledgeInd(mHeaders); - return acknowledgeInd; - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND"); - } - ReadOrigInd readOrigInd = - new ReadOrigInd(mHeaders); - return readOrigInd; - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND"); - } - ReadRecInd readRecInd = - new ReadRecInd(mHeaders); - return readRecInd; - default: - log("Parser doesn't support this message type in this version!"); - return null; - } - } - - /** - * Parse pdu headers. - * - * @param pduDataStream pdu data input stream - * @return headers in PduHeaders structure, null when parse fail - */ - protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){ - if (pduDataStream == null) { - return null; - } - boolean keepParsing = true; - PduHeaders headers = new PduHeaders(); - - while (keepParsing && (pduDataStream.available() > 0)) { - pduDataStream.mark(1); - int headerField = extractByteValue(pduDataStream); - /* parse custom text header */ - if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { - pduDataStream.reset(); - byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); - } - /* we should ignore it at the moment */ - continue; - } - switch (headerField) { - case PduHeaders.MESSAGE_TYPE: - { - int messageType = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType); - } - switch (messageType) { - // We don't support these kind of messages now. - case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: - case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: - case PduHeaders.MESSAGE_TYPE_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: - case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: - return null; - } - try { - headers.setOctet(messageType, headerField); - } catch(InvalidHeaderValueException e) { - log("Set invalid Octet value: " + messageType + - " into the header filed: " + headerField); - return null; - } catch(RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - /* Octect value */ - case PduHeaders.REPORT_ALLOWED: - case PduHeaders.ADAPTATION_ALLOWED: - case PduHeaders.DELIVERY_REPORT: - case PduHeaders.DRM_CONTENT: - case PduHeaders.DISTRIBUTION_INDICATOR: - case PduHeaders.QUOTAS: - case PduHeaders.READ_REPORT: - case PduHeaders.STORE: - case PduHeaders.STORED: - case PduHeaders.TOTALS: - case PduHeaders.SENDER_VISIBILITY: - case PduHeaders.READ_STATUS: - case PduHeaders.CANCEL_STATUS: - case PduHeaders.PRIORITY: - case PduHeaders.STATUS: - case PduHeaders.REPLY_CHARGING: - case PduHeaders.MM_STATE: - case PduHeaders.RECOMMENDED_RETRIEVAL_MODE: - case PduHeaders.CONTENT_CLASS: - case PduHeaders.RETRIEVE_STATUS: - case PduHeaders.STORE_STATUS: - /** - * The following field has a different value when - * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. - * For now we ignore this fact, since we do not support these PDUs - */ - case PduHeaders.RESPONSE_STATUS: - { - int value = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " + - value); - } - - try { - headers.setOctet(value, headerField); - } catch(InvalidHeaderValueException e) { - log("Set invalid Octet value: " + value + - " into the header filed: " + headerField); - return null; - } catch(RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - - /* Long-Integer */ - case PduHeaders.DATE: - case PduHeaders.REPLY_CHARGING_SIZE: - case PduHeaders.MESSAGE_SIZE: - { - try { - long value = parseLongInteger(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " + - value); - } - headers.setLongInteger(value, headerField); - } catch(RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - /* Integer-Value */ - case PduHeaders.MESSAGE_COUNT: - case PduHeaders.START: - case PduHeaders.LIMIT: - { - try { - long value = parseIntegerValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " + - value); - } - headers.setLongInteger(value, headerField); - } catch(RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - /* Text-String */ - case PduHeaders.TRANSACTION_ID: - case PduHeaders.REPLY_CHARGING_ID: - case PduHeaders.AUX_APPLIC_ID: - case PduHeaders.APPLIC_ID: - case PduHeaders.REPLY_APPLIC_ID: - /** - * The next three header fields are email addresses - * as defined in RFC2822, - * not including the characters "<" and ">" - */ - case PduHeaders.MESSAGE_ID: - case PduHeaders.REPLACE_ID: - case PduHeaders.CANCEL_ID: - /** - * The following field has a different value when - * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. - * For now we ignore this fact, since we do not support these PDUs - */ - case PduHeaders.CONTENT_LOCATION: - { - byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != value) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " + - new String(value)); - } - headers.setTextString(value, headerField); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - break; - } - - /* Encoded-string-value */ - case PduHeaders.SUBJECT: - case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT: - case PduHeaders.RETRIEVE_TEXT: - case PduHeaders.STATUS_TEXT: - case PduHeaders.STORE_STATUS_TEXT: - /* the next one is not support - * M-Mbox-Delete.conf and M-Delete.conf now */ - case PduHeaders.RESPONSE_TEXT: - { - EncodedStringValue value = - parseEncodedStringValue(pduDataStream); - if (null != value) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField - + " value: " + value.getString()); - } - headers.setEncodedStringValue(value, headerField); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - /* Addressing model */ - case PduHeaders.BCC: - case PduHeaders.CC: - case PduHeaders.TO: - { - EncodedStringValue value = - parseEncodedStringValue(pduDataStream); - if (null != value) { - byte[] address = value.getTextString(); - if (null != address) { - String str = new String(address); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField - + " value: " + str); - } - int endIndex = str.indexOf("/"); - if (endIndex > 0) { - str = str.substring(0, endIndex); - } - try { - value.setTextString(str.getBytes()); - } catch(NullPointerException e) { - log("null pointer error!"); - return null; - } - } - - try { - headers.appendEncodedStringValue(value, headerField); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - /* Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) */ - case PduHeaders.DELIVERY_TIME: - case PduHeaders.EXPIRY: - case PduHeaders.REPLY_CHARGING_DEADLINE: - { - /* parse Value-length */ - parseValueLength(pduDataStream); - - /* Absolute-token or Relative-token */ - int token = extractByteValue(pduDataStream); - - /* Date-value or Delta-seconds-value */ - long timeValue; - try { - timeValue = parseLongInteger(pduDataStream); - } catch(RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - if (PduHeaders.VALUE_RELATIVE_TOKEN == token) { - /* need to convert the Delta-seconds-value - * into Date-value */ - timeValue = System.currentTimeMillis()/1000 + timeValue; - } - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: time value: " + headerField - + " value: " + timeValue); - } - headers.setLongInteger(timeValue, headerField); - } catch(RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - case PduHeaders.FROM: { - /* From-value = - * Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - */ - EncodedStringValue from = null; - parseValueLength(pduDataStream); /* parse value-length */ - - /* Address-present-token or Insert-address-token */ - int fromToken = extractByteValue(pduDataStream); - - /* Address-present-token or Insert-address-token */ - if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) { - /* Encoded-string-value */ - from = parseEncodedStringValue(pduDataStream); - if (null != from) { - byte[] address = from.getTextString(); - if (null != address) { - String str = new String(address); - int endIndex = str.indexOf("/"); - if (endIndex > 0) { - str = str.substring(0, endIndex); - } - try { - from.setTextString(str.getBytes()); - } catch(NullPointerException e) { - log("null pointer error!"); - return null; - } - } - } - } else { - try { - from = new EncodedStringValue( - PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()); - } catch(NullPointerException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: from address: " + headerField - + " value: " + from.getString()); - } - headers.setEncodedStringValue(from, PduHeaders.FROM); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - break; - } - - case PduHeaders.MESSAGE_CLASS: { - /* Message-class-value = Class-identifier | Token-text */ - pduDataStream.mark(1); - int messageClass = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField - + " value: " + messageClass); - } - - if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) { - /* Class-identifier */ - try { - if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } else { - /* Token-text */ - pduDataStream.reset(); - byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != messageClassString) { - try { - headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - } - break; - } - - case PduHeaders.MMS_VERSION: { - int version = parseShortInteger(pduDataStream); - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField - + " value: " + version); - } - headers.setOctet(version, PduHeaders.MMS_VERSION); - } catch(InvalidHeaderValueException e) { - log("Set invalid Octet value: " + version + - " into the header filed: " + headerField); - return null; - } catch(RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - - case PduHeaders.PREVIOUSLY_SENT_BY: { - /* Previously-sent-by-value = - * Value-length Forwarded-count-value Encoded-string-value */ - /* parse value-length */ - parseValueLength(pduDataStream); - - /* parse Forwarded-count-value */ - try { - parseIntegerValue(pduDataStream); - } catch(RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* parse Encoded-string-value */ - EncodedStringValue previouslySentBy = - parseEncodedStringValue(pduDataStream); - if (null != previouslySentBy) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField - + " value: " + previouslySentBy.getString()); - } - headers.setEncodedStringValue(previouslySentBy, - PduHeaders.PREVIOUSLY_SENT_BY); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - case PduHeaders.PREVIOUSLY_SENT_DATE: { - /* Previously-sent-date-value = - * Value-length Forwarded-count-value Date-value */ - /* parse value-length */ - parseValueLength(pduDataStream); - - /* parse Forwarded-count-value */ - try { - parseIntegerValue(pduDataStream); - } catch(RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* Date-value */ - try { - long perviouslySentDate = parseLongInteger(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField - + " value: " + perviouslySentDate); - } - headers.setLongInteger(perviouslySentDate, - PduHeaders.PREVIOUSLY_SENT_DATE); - } catch(RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - case PduHeaders.MM_FLAGS: { - /* MM-flags-value = - * Value-length - * ( Add-token | Remove-token | Filter-token ) - * Encoded-string-value - */ - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField - + " NOT REALLY SUPPORTED"); - } - - /* parse Value-length */ - parseValueLength(pduDataStream); - - /* Add-token | Remove-token | Filter-token */ - extractByteValue(pduDataStream); - - /* Encoded-string-value */ - parseEncodedStringValue(pduDataStream); - - /* not store this header filed in "headers", - * because now PduHeaders doesn't support it */ - break; - } - - /* Value-length - * (Message-total-token | Size-total-token) Integer-Value */ - case PduHeaders.MBOX_TOTALS: - case PduHeaders.MBOX_QUOTAS: - { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField); - } - /* Value-length */ - parseValueLength(pduDataStream); - - /* Message-total-token | Size-total-token */ - extractByteValue(pduDataStream); - - /*Integer-Value*/ - try { - parseIntegerValue(pduDataStream); - } catch(RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* not store these headers filed in "headers", - because now PduHeaders doesn't support them */ - break; - } - - case PduHeaders.ELEMENT_DESCRIPTOR: { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField); - } - parseContentType(pduDataStream, null); - - /* not store this header filed in "headers", - because now PduHeaders doesn't support it */ - break; - } - - case PduHeaders.CONTENT_TYPE: { - HashMap map = - new HashMap(); - byte[] contentType = - parseContentType(pduDataStream, map); - - if (null != contentType) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField + - Arrays.toString(contentType)); - } - headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); - } catch(NullPointerException e) { - log("null pointer error!"); - } catch(RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - - /* get start parameter */ - mStartParam = (byte[]) map.get(PduPart.P_START); - - /* get charset parameter */ - mTypeParam= (byte[]) map.get(PduPart.P_TYPE); - - keepParsing = false; - break; - } - - case PduHeaders.CONTENT: - case PduHeaders.ADDITIONAL_HEADERS: - case PduHeaders.ATTRIBUTES: - default: { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField); - } - log("Unknown header"); - } - } - } - - return headers; - } - - /** - * Parse pdu parts. - * - * @param pduDataStream pdu data input stream - * @return parts in PduBody structure - */ - protected PduBody parseParts(ByteArrayInputStream pduDataStream) { - if (pduDataStream == null) { - return null; - } - - int count = parseUnsignedInt(pduDataStream); // get the number of parts - PduBody body = new PduBody(); - - for (int i = 0 ; i < count ; i++) { - int headerLength = parseUnsignedInt(pduDataStream); - int dataLength = parseUnsignedInt(pduDataStream); - PduPart part = new PduPart(); - int startPos = pduDataStream.available(); - if (startPos <= 0) { - // Invalid part. - return null; - } - - /* parse part's content-type */ - HashMap map = new HashMap(); - byte[] contentType = parseContentType(pduDataStream, map); - if (null != contentType) { - part.setContentType(contentType); - } else { - part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*" - } - - /* get name parameter */ - byte[] name = (byte[]) map.get(PduPart.P_NAME); - if (null != name) { - part.setName(name); - } - - /* get charset parameter */ - Integer charset = (Integer) map.get(PduPart.P_CHARSET); - if (null != charset) { - part.setCharset(charset); - } - - /* parse part's headers */ - int endPos = pduDataStream.available(); - int partHeaderLen = headerLength - (startPos - endPos); - if (partHeaderLen > 0) { - if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) { - // Parse part header faild. - return null; - } - } else if (partHeaderLen < 0) { - // Invalid length of content-type. - return null; - } - - /* FIXME: check content-id, name, filename and content location, - * if not set anyone of them, generate a default content-location - */ - if ((null == part.getContentLocation()) - && (null == part.getName()) - && (null == part.getFilename()) - && (null == part.getContentId())) { - part.setContentLocation(Long.toOctalString( - System.currentTimeMillis()).getBytes()); - } - - /* get part's data */ - if (dataLength > 0) { - byte[] partData = new byte[dataLength]; - String partContentType = new String(part.getContentType()); - pduDataStream.read(partData, 0, dataLength); - if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) { - // parse "multipart/vnd.wap.multipart.alternative". - PduBody childBody = parseParts(new ByteArrayInputStream(partData)); - // take the first part of children. - part = childBody.getPart(0); - } else { - // Check Content-Transfer-Encoding. - byte[] partDataEncoding = part.getContentTransferEncoding(); - if (null != partDataEncoding) { - String encoding = new String(partDataEncoding); - if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { - // Decode "base64" into "binary". - partData = Base64.decodeBase64(partData); - } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { - // Decode "quoted-printable" into "binary". - partData = QuotedPrintable.decodeQuotedPrintable(partData); - } else { - // "binary" is the default encoding. - } - } - if (null == partData) { - log("Decode part data error!"); - return null; - } - part.setData(partData); - } - } - - /* add this part to body */ - if (THE_FIRST_PART == checkPartPosition(part)) { - /* this is the first part */ - body.addPart(0, part); - } else { - /* add the part to the end */ - body.addPart(part); - } - } - - return body; - } - - /** - * Log status. - * - * @param text log information - */ - private static void log(String text) { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, text); - } - } - - /** - * Parse unsigned integer. - * - * @param pduDataStream pdu data input stream - * @return the integer, -1 when failed - */ - protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * The maximum size of a uintvar is 32 bits. - * So it will be encoded in no more than 5 octets. - */ - assert(null != pduDataStream); - int result = 0; - int temp = pduDataStream.read(); - if (temp == -1) { - return temp; - } - - while((temp & 0x80) != 0) { - result = result << 7; - result |= temp & 0x7F; - temp = pduDataStream.read(); - if (temp == -1) { - return temp; - } - } - - result = result << 7; - result |= temp & 0x7F; - - return result; - } - - /** - * Parse value length. - * - * @param pduDataStream pdu data input stream - * @return the integer - */ - protected static int parseValueLength(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Value-length = Short-length | (Length-quote Length) - * Short-length = - * Length-quote = - * Length = Uintvar-integer - * Uintvar-integer = 1*5 OCTET - */ - assert(null != pduDataStream); - int temp = pduDataStream.read(); - assert(-1 != temp); - int first = temp & 0xFF; - - if (first <= SHORT_LENGTH_MAX) { - return first; - } else if (first == LENGTH_QUOTE) { - return parseUnsignedInt(pduDataStream); - } - - throw new RuntimeException("Value length > LENGTH_QUOTE!"); - } - - /** - * Parse encoded string value. - * - * @param pduDataStream pdu data input stream - * @return the EncodedStringValue - */ - protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){ - /** - * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf - * Encoded-string-value = Text-string | Value-length Char-set Text-string - */ - assert(null != pduDataStream); - pduDataStream.mark(1); - EncodedStringValue returnValue = null; - int charset = 0; - int temp = pduDataStream.read(); - assert(-1 != temp); - int first = temp & 0xFF; - if (first == 0) { - return new EncodedStringValue(""); - } - - pduDataStream.reset(); - if (first < TEXT_MIN) { - parseValueLength(pduDataStream); - - charset = parseShortInteger(pduDataStream); //get the "Charset" - } - - byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING); - - try { - if (0 != charset) { - returnValue = new EncodedStringValue(charset, textString); - } else { - returnValue = new EncodedStringValue(textString); - } - } catch(Exception e) { - return null; - } - - return returnValue; - } - - /** - * Parse Text-String or Quoted-String. - * - * @param pduDataStream pdu data input stream - * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING - * @return the string without End-of-string in byte array - */ - protected static byte[] parseWapString(ByteArrayInputStream pduDataStream, - int stringType) { - assert(null != pduDataStream); - /** - * From wap-230-wsp-20010705-a.pdf - * Text-string = [Quote] *TEXT End-of-string - * If the first character in the TEXT is in the range of 128-255, - * a Quote character must precede it. - * Otherwise the Quote character must be omitted. - * The Quote is not part of the contents. - * Quote = - * End-of-string = - * - * Quoted-string = *TEXT End-of-string - * - * Token-text = Token End-of-string - */ - - // Mark supposed beginning of Text-string - // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG - pduDataStream.mark(1); - - // Check first char - int temp = pduDataStream.read(); - assert(-1 != temp); - if ((TYPE_QUOTED_STRING == stringType) && - (QUOTED_STRING_FLAG == temp)) { - // Mark again if QUOTED_STRING_FLAG and ignore it - pduDataStream.mark(1); - } else if ((TYPE_TEXT_STRING == stringType) && - (QUOTE == temp)) { - // Mark again if QUOTE and ignore it - pduDataStream.mark(1); - } else { - // Otherwise go back to origin - pduDataStream.reset(); - } - - // We are now definitely at the beginning of string - /** - * Return *TOKEN or *TEXT (Text-String without QUOTE, - * Quoted-String without QUOTED_STRING_FLAG and without End-of-string) - */ - return getWapString(pduDataStream, stringType); - } - - /** - * Check TOKEN data defined in RFC2616. - * @param ch checking data - * @return true when ch is TOKEN, false when ch is not TOKEN - */ - protected static boolean isTokenCharacter(int ch) { - /** - * Token = 1* - * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64) - * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34) - * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61) - * | "{"(123) | "}"(125) | SP(32) | HT(9) - * CHAR = - * CTL = - * SP = - * HT = - */ - if((ch < 33) || (ch > 126)) { - return false; - } - - switch(ch) { - case '"': /* '"' */ - case '(': /* '(' */ - case ')': /* ')' */ - case ',': /* ',' */ - case '/': /* '/' */ - case ':': /* ':' */ - case ';': /* ';' */ - case '<': /* '<' */ - case '=': /* '=' */ - case '>': /* '>' */ - case '?': /* '?' */ - case '@': /* '@' */ - case '[': /* '[' */ - case '\\': /* '\' */ - case ']': /* ']' */ - case '{': /* '{' */ - case '}': /* '}' */ - return false; - } - - return true; - } - - /** - * Check TEXT data defined in RFC2616. - * @param ch checking data - * @return true when ch is TEXT, false when ch is not TEXT - */ - protected static boolean isText(int ch) { - /** - * TEXT = - * CTL = - * LWS = [CRLF] 1*( SP | HT ) - * CRLF = CR LF - * CR = - * LF = - */ - if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) { - return true; - } - - switch(ch) { - case '\t': /* '\t' */ - case '\n': /* '\n' */ - case '\r': /* '\r' */ - return true; - } - - return false; - } - - protected static byte[] getWapString(ByteArrayInputStream pduDataStream, - int stringType) { - assert(null != pduDataStream); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int temp = pduDataStream.read(); - assert(-1 != temp); - while((-1 != temp) && ('\0' != temp)) { - // check each of the character - if (stringType == TYPE_TOKEN_STRING) { - if (isTokenCharacter(temp)) { - out.write(temp); - } - } else { - if (isText(temp)) { - out.write(temp); - } - } - - temp = pduDataStream.read(); - assert(-1 != temp); - } - - if (out.size() > 0) { - return out.toByteArray(); - } - - return null; - } - - /** - * Extract a byte value from the input stream. - * - * @param pduDataStream pdu data input stream - * @return the byte - */ - protected static int extractByteValue(ByteArrayInputStream pduDataStream) { - assert(null != pduDataStream); - int temp = pduDataStream.read(); - assert(-1 != temp); - return temp & 0xFF; - } - - /** - * Parse Short-Integer. - * - * @param pduDataStream pdu data input stream - * @return the byte - */ - protected static int parseShortInteger(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Short-integer = OCTET - * Integers in range 0-127 shall be encoded as a one - * octet value with the most significant bit set to one (1xxx xxxx) - * and with the value in the remaining least significant bits. - */ - assert(null != pduDataStream); - int temp = pduDataStream.read(); - assert(-1 != temp); - return temp & 0x7F; - } - - /** - * Parse Long-Integer. - * - * @param pduDataStream pdu data input stream - * @return long integer - */ - protected static long parseLongInteger(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Long-integer = Short-length Multi-octet-integer - * The Short-length indicates the length of the Multi-octet-integer - * Multi-octet-integer = 1*30 OCTET - * The content octets shall be an unsigned integer value - * with the most significant octet encoded first (big-endian representation). - * The minimum number of octets must be used to encode the value. - * Short-length = - */ - assert(null != pduDataStream); - int temp = pduDataStream.read(); - assert(-1 != temp); - int count = temp & 0xFF; - - if (count > LONG_INTEGER_LENGTH_MAX) { - throw new RuntimeException("Octet count greater than 8 and I can't represent that!"); - } - - long result = 0; - - for (int i = 0 ; i < count ; i++) { - temp = pduDataStream.read(); - assert(-1 != temp); - result <<= 8; - result += (temp & 0xFF); - } - - return result; - } - - /** - * Parse Integer-Value. - * - * @param pduDataStream pdu data input stream - * @return long integer - */ - protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Integer-Value = Short-integer | Long-integer - */ - assert(null != pduDataStream); - pduDataStream.mark(1); - int temp = pduDataStream.read(); - assert(-1 != temp); - pduDataStream.reset(); - if (temp > SHORT_INTEGER_MAX) { - return parseShortInteger(pduDataStream); - } else { - return parseLongInteger(pduDataStream); - } - } - - /** - * To skip length of the wap value. - * - * @param pduDataStream pdu data input stream - * @param length area size - * @return the values in this area - */ - protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) { - assert(null != pduDataStream); - byte[] area = new byte[length]; - int readLen = pduDataStream.read(area, 0, length); - if (readLen < length) { //The actually read length is lower than the length - return -1; - } else { - return readLen; - } - } - - /** - * Parse content type parameters. For now we just support - * four parameters used in mms: "type", "start", "name", "charset". - * - * @param pduDataStream pdu data input stream - * @param map to store parameters of Content-Type field - * @param length length of all the parameters - */ - protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream, - HashMap map, Integer length) { - /** - * From wap-230-wsp-20010705-a.pdf - * Parameter = Typed-parameter | Untyped-parameter - * Typed-parameter = Well-known-parameter-token Typed-value - * the actual expected type of the value is implied by the well-known parameter - * Well-known-parameter-token = Integer-value - * the code values used for parameters are specified in the Assigned Numbers appendix - * Typed-value = Compact-value | Text-value - * In addition to the expected type, there may be no value. - * If the value cannot be encoded using the expected type, it shall be encoded as text. - * Compact-value = Integer-value | - * Date-value | Delta-seconds-value | Q-value | Version-value | - * Uri-value - * Untyped-parameter = Token-text Untyped-value - * the type of the value is unknown, but it shall be encoded as an integer, - * if that is possible. - * Untyped-value = Integer-value | Text-value - */ - assert(null != pduDataStream); - assert(length > 0); - - int startPos = pduDataStream.available(); - int tempPos = 0; - int lastLen = length; - while(0 < lastLen) { - int param = pduDataStream.read(); - assert(-1 != param); - lastLen--; - - switch (param) { - /** - * From rfc2387, chapter 3.1 - * The type parameter must be specified and its value is the MIME media - * type of the "root" body part. It permits a MIME user agent to - * determine the content-type without reference to the enclosed body - * part. If the value of the type parameter and the root body part's - * content-type differ then the User Agent's behavior is undefined. - * - * From wap-230-wsp-20010705-a.pdf - * type = Constrained-encoding - * Constrained-encoding = Extension-Media | Short-integer - * Extension-media = *TEXT End-of-string - */ - case PduPart.P_TYPE: - case PduPart.P_CT_MR_TYPE: - pduDataStream.mark(1); - int first = extractByteValue(pduDataStream); - pduDataStream.reset(); - if (first > TEXT_MAX) { - // Short-integer (well-known type) - int index = parseShortInteger(pduDataStream); - - if (index < PduContentTypes.contentTypes.length) { - byte[] type = (PduContentTypes.contentTypes[index]).getBytes(); - map.put(PduPart.P_TYPE, type); - } else { - //not support this type, ignore it. - } - } else { - // Text-String (extension-media) - byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != type) && (null != map)) { - map.put(PduPart.P_TYPE, type); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3. - * Start Parameter Referring to Presentation - * - * From rfc2387, chapter 3.2 - * The start parameter, if given, is the content-ID of the compound - * object's "root". If not present the "root" is the first body part in - * the Multipart/Related entity. The "root" is the element the - * applications processes first. - * - * From wap-230-wsp-20010705-a.pdf - * start = Text-String - */ - case PduPart.P_START: - case PduPart.P_DEP_START: - byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != start) && (null != map)) { - map.put(PduPart.P_START, start); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf - * In creation, the character set SHALL be either us-ascii - * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode]. - * In retrieval, both us-ascii and utf-8 SHALL be supported. - * - * From wap-230-wsp-20010705-a.pdf - * charset = Well-known-charset|Text-String - * Well-known-charset = Any-charset | Integer-value - * Both are encoded using values from Character Set - * Assignments table in Assigned Numbers - * Any-charset = - * Equivalent to the special RFC2616 charset value "*" - */ - case PduPart.P_CHARSET: - pduDataStream.mark(1); - int firstValue = extractByteValue(pduDataStream); - pduDataStream.reset(); - //Check first char - if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) || - (END_STRING_FLAG == firstValue)) { - //Text-String (extension-charset) - byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING); - try { - int charsetInt = CharacterSets.getMibEnumValue( - new String(charsetStr)); - map.put(PduPart.P_CHARSET, charsetInt); - } catch (UnsupportedEncodingException e) { - // Not a well-known charset, use "*". - Log.e(LOG_TAG, Arrays.toString(charsetStr), e); - map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET); - } - } else { - //Well-known-charset - int charset = (int) parseIntegerValue(pduDataStream); - if (map != null) { - map.put(PduPart.P_CHARSET, charset); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf - * A name for multipart object SHALL be encoded using name-parameter - * for Content-Type header in WSP multipart headers. - * - * From wap-230-wsp-20010705-a.pdf - * name = Text-String - */ - case PduPart.P_DEP_NAME: - case PduPart.P_NAME: - byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != name) && (null != map)) { - map.put(PduPart.P_NAME, name); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - default: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Content-Type parameter"); - } - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Content-Type"); - } else { - lastLen = 0; - } - break; - } - } - - if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Content-Type"); - } - } - - /** - * Parse content type. - * - * @param pduDataStream pdu data input stream - * @param map to store parameters in Content-Type header field - * @return Content-Type value - */ - protected static byte[] parseContentType(ByteArrayInputStream pduDataStream, - HashMap map) { - /** - * From wap-230-wsp-20010705-a.pdf - * Content-type-value = Constrained-media | Content-general-form - * Content-general-form = Value-length Media-type - * Media-type = (Well-known-media | Extension-Media) *(Parameter) - */ - assert(null != pduDataStream); - - byte[] contentType = null; - pduDataStream.mark(1); - int temp = pduDataStream.read(); - assert(-1 != temp); - pduDataStream.reset(); - - int cur = (temp & 0xFF); - - if (cur < TEXT_MIN) { - int length = parseValueLength(pduDataStream); - int startPos = pduDataStream.available(); - pduDataStream.mark(1); - temp = pduDataStream.read(); - assert(-1 != temp); - pduDataStream.reset(); - int first = (temp & 0xFF); - - if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) { - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } else if (first > TEXT_MAX) { - int index = parseShortInteger(pduDataStream); - - if (index < PduContentTypes.contentTypes.length) { //well-known type - contentType = (PduContentTypes.contentTypes[index]).getBytes(); - } else { - pduDataStream.reset(); - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } - } else { - Log.e(LOG_TAG, "Corrupt content-type"); - return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" - } - - int endPos = pduDataStream.available(); - int parameterLen = length - (startPos - endPos); - if (parameterLen > 0) {//have parameters - parseContentTypeParams(pduDataStream, map, parameterLen); - } - - if (parameterLen < 0) { - Log.e(LOG_TAG, "Corrupt MMS message"); - return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" - } - } else if (cur <= TEXT_MAX) { - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } else { - contentType = - (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes(); - } - - return contentType; - } - - /** - * Parse part's headers. - * - * @param pduDataStream pdu data input stream - * @param part to store the header informations of the part - * @param length length of the headers - * @return true if parse successfully, false otherwise - */ - protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream, - PduPart part, int length) { - assert(null != pduDataStream); - assert(null != part); - assert(length > 0); - - /** - * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2. - * A name for multipart object SHALL be encoded using name-parameter - * for Content-Type header in WSP multipart headers. - * In decoding, name-parameter of Content-Type SHALL be used if available. - * If name-parameter of Content-Type is not available, - * filename parameter of Content-Disposition header SHALL be used if available. - * If neither name-parameter of Content-Type header nor filename parameter - * of Content-Disposition header is available, - * Content-Location header SHALL be used if available. - * - * Within SMIL part the reference to the media object parts SHALL use - * either Content-ID or Content-Location mechanism [RFC2557] - * and the corresponding WSP part headers in media object parts - * contain the corresponding definitions. - */ - int startPos = pduDataStream.available(); - int tempPos = 0; - int lastLen = length; - while(0 < lastLen) { - int header = pduDataStream.read(); - assert(-1 != header); - lastLen--; - - if (header > TEXT_MAX) { - // Number assigned headers. - switch (header) { - case PduPart.P_CONTENT_LOCATION: - /** - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-location-value = Uri-value - */ - byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != contentLocation) { - part.setContentLocation(contentLocation); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - case PduPart.P_CONTENT_ID: - /** - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-ID-value = Quoted-string - */ - byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING); - if (null != contentId) { - part.setContentId(contentId); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - case PduPart.P_DEP_CONTENT_DISPOSITION: - case PduPart.P_CONTENT_DISPOSITION: - /** - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-disposition-value = Value-length Disposition *(Parameter) - * Disposition = Form-data | Attachment | Inline | Token-text - * Form-data = - * Attachment = - * Inline = - */ - - /* - * some carrier mmsc servers do not support content_disposition - * field correctly - */ - if (mParseContentDisposition) { - int len = parseValueLength(pduDataStream); - pduDataStream.mark(1); - int thisStartPos = pduDataStream.available(); - int thisEndPos = 0; - int value = pduDataStream.read(); - - if (value == PduPart.P_DISPOSITION_FROM_DATA ) { - part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA); - } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) { - part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT); - } else if (value == PduPart.P_DISPOSITION_INLINE) { - part.setContentDisposition(PduPart.DISPOSITION_INLINE); - } else { - pduDataStream.reset(); - /* Token-text */ - part.setContentDisposition(parseWapString(pduDataStream - , TYPE_TEXT_STRING)); - } - - /* get filename parameter and skip other parameters */ - thisEndPos = pduDataStream.available(); - if (thisStartPos - thisEndPos < len) { - value = pduDataStream.read(); - if (value == PduPart.P_FILENAME) { //filename is text-string - part.setFilename(parseWapString(pduDataStream - , TYPE_TEXT_STRING)); - } - - /* skip other parameters */ - thisEndPos = pduDataStream.available(); - if (thisStartPos - thisEndPos < len) { - int last = len - (thisStartPos - thisEndPos); - byte[] temp = new byte[last]; - pduDataStream.read(temp, 0, last); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - } - break; - default: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); - } - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - lastLen = 0; - break; - } - } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) { - // Not assigned header. - byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING); - byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING); - - // Check the header whether it is "Content-Transfer-Encoding". - if (true == - PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) { - part.setContentTransferEncoding(tempValue); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - } else { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); - } - // Skip all headers of this part. - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - lastLen = 0; - } - } - - if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - - return true; - } - - /** - * Check the position of a specified part. - * - * @param part the part to be checked - * @return part position, THE_FIRST_PART when it's the - * first one, THE_LAST_PART when it's the last one. - */ - private static int checkPartPosition(PduPart part) { - assert(null != part); - if ((null == mTypeParam) && - (null == mStartParam)) { - return THE_LAST_PART; - } - - /* check part's content-id */ - if (null != mStartParam) { - byte[] contentId = part.getContentId(); - if (null != contentId) { - if (true == Arrays.equals(mStartParam, contentId)) { - return THE_FIRST_PART; - } - } - // This is not the first part, so append to end (keeping the original order) - // Check b/19607294 for details of this change - return THE_LAST_PART; - } - - /* check part's content-type */ - if (null != mTypeParam) { - byte[] contentType = part.getContentType(); - if (null != contentType) { - if (true == Arrays.equals(mTypeParam, contentType)) { - return THE_FIRST_PART; - } - } - } - - return THE_LAST_PART; - } - - /** - * Check mandatory headers of a pdu. - * - * @param headers pdu headers - * @return true if the pdu has all of the mandatory headers, false otherwise. - */ - protected static boolean checkMandatoryHeader(PduHeaders headers) { - if (null == headers) { - return false; - } - - /* get message type */ - int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE); - - /* check Mms-Version field */ - int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION); - if (0 == mmsVersion) { - // Every message should have Mms-Version field. - return false; - } - - /* check mandatory header fields */ - switch (messageType) { - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - // Content-Type field. - byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); - if (null == srContentType) { - return false; - } - - // From field. - EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == srFrom) { - return false; - } - - // Transaction-Id field. - byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == srTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_SEND_CONF: - // Response-Status field. - int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS); - if (0 == scResponseStatus) { - return false; - } - - // Transaction-Id field. - byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == scTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - // Content-Location field. - byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION); - if (null == niContentLocation) { - return false; - } - - // Expiry field. - long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY); - if (-1 == niExpiry) { - return false; - } - - // Message-Class field. - byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS); - if (null == niMessageClass) { - return false; - } - - // Message-Size field. - long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE); - if (-1 == niMessageSize) { - return false; - } - - // Transaction-Id field. - byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == niTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - // Status field. - int nriStatus = headers.getOctet(PduHeaders.STATUS); - if (0 == nriStatus) { - return false; - } - - // Transaction-Id field. - byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == nriTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - // Content-Type field. - byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); - if (null == rcContentType) { - return false; - } - - // Date field. - long rcDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == rcDate) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - // Date field. - long diDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == diDate) { - return false; - } - - // Message-Id field. - byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == diMessageId) { - return false; - } - - // Status field. - int diStatus = headers.getOctet(PduHeaders.STATUS); - if (0 == diStatus) { - return false; - } - - // To field. - EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == diTo) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - // Transaction-Id field. - byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == aiTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: - // Date field. - long roDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == roDate) { - return false; - } - - // From field. - EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == roFrom) { - return false; - } - - // Message-Id field. - byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == roMessageId) { - return false; - } - - // Read-Status field. - int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS); - if (0 == roReadStatus) { - return false; - } - - // To field. - EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == roTo) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - // From field. - EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == rrFrom) { - return false; - } - - // Message-Id field. - byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == rrMessageId) { - return false; - } - - // Read-Status field. - int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS); - if (0 == rrReadStatus) { - return false; - } - - // To field. - EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == rrTo) { - return false; - } - - break; - default: - // Parser doesn't support this message type in this version. - return false; - } - - return true; - } -} diff --git a/src/android/support/v7/mms/pdu/PduPart.java b/src/android/support/v7/mms/pdu/PduPart.java deleted file mode 100644 index ec8e1a2..0000000 --- a/src/android/support/v7/mms/pdu/PduPart.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import android.net.Uri; - -import java.util.HashMap; -import java.util.Map; - -/** - * The pdu part. - */ -public class PduPart { - /** - * Well-Known Parameters. - */ - public static final int P_Q = 0x80; - public static final int P_CHARSET = 0x81; - public static final int P_LEVEL = 0x82; - public static final int P_TYPE = 0x83; - public static final int P_DEP_NAME = 0x85; - public static final int P_DEP_FILENAME = 0x86; - public static final int P_DIFFERENCES = 0x87; - public static final int P_PADDING = 0x88; - // This value of "TYPE" s used with Content-Type: multipart/related - public static final int P_CT_MR_TYPE = 0x89; - public static final int P_DEP_START = 0x8A; - public static final int P_DEP_START_INFO = 0x8B; - public static final int P_DEP_COMMENT = 0x8C; - public static final int P_DEP_DOMAIN = 0x8D; - public static final int P_MAX_AGE = 0x8E; - public static final int P_DEP_PATH = 0x8F; - public static final int P_SECURE = 0x90; - public static final int P_SEC = 0x91; - public static final int P_MAC = 0x92; - public static final int P_CREATION_DATE = 0x93; - public static final int P_MODIFICATION_DATE = 0x94; - public static final int P_READ_DATE = 0x95; - public static final int P_SIZE = 0x96; - public static final int P_NAME = 0x97; - public static final int P_FILENAME = 0x98; - public static final int P_START = 0x99; - public static final int P_START_INFO = 0x9A; - public static final int P_COMMENT = 0x9B; - public static final int P_DOMAIN = 0x9C; - public static final int P_PATH = 0x9D; - - /** - * Header field names. - */ - public static final int P_CONTENT_TYPE = 0x91; - public static final int P_CONTENT_LOCATION = 0x8E; - public static final int P_CONTENT_ID = 0xC0; - public static final int P_DEP_CONTENT_DISPOSITION = 0xAE; - public static final int P_CONTENT_DISPOSITION = 0xC5; - // The next header is unassigned header, use reserved header(0x48) value. - public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8; - - /** - * Content=Transfer-Encoding string. - */ - public static final String CONTENT_TRANSFER_ENCODING = - "Content-Transfer-Encoding"; - - /** - * Value of Content-Transfer-Encoding. - */ - public static final String P_BINARY = "binary"; - public static final String P_7BIT = "7bit"; - public static final String P_8BIT = "8bit"; - public static final String P_BASE64 = "base64"; - public static final String P_QUOTED_PRINTABLE = "quoted-printable"; - - /** - * Value of disposition can be set to PduPart when the value is octet in - * the PDU. - * "from-data" instead of Form-data. - * "attachment" instead of Attachment. - * "inline" instead of Inline. - */ - static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes(); - static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes(); - static final byte[] DISPOSITION_INLINE = "inline".getBytes(); - - /** - * Content-Disposition value. - */ - public static final int P_DISPOSITION_FROM_DATA = 0x80; - public static final int P_DISPOSITION_ATTACHMENT = 0x81; - public static final int P_DISPOSITION_INLINE = 0x82; - - /** - * Header of part. - */ - private Map mPartHeader = null; - - /** - * Data uri. - */ - private Uri mUri = null; - - /** - * Part data. - */ - private byte[] mPartData = null; - - private static final String TAG = "PduPart"; - - /** - * Empty Constructor. - */ - public PduPart() { - mPartHeader = new HashMap(); - } - - /** - * Set part data. The data are stored as byte array. - * - * @param data the data - */ - public void setData(byte[] data) { - if(data == null) { - return; - } - - mPartData = new byte[data.length]; - System.arraycopy(data, 0, mPartData, 0, data.length); - } - - /** - * @return A copy of the part data or null if the data wasn't set or - * the data is stored as Uri. - * @see #getDataUri - */ - public byte[] getData() { - if(mPartData == null) { - return null; - } - - byte[] byteArray = new byte[mPartData.length]; - System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length); - return byteArray; - } - - /** - * @return The length of the data, if this object have data, else 0. - */ - public int getDataLength() { - if(mPartData != null){ - return mPartData.length; - } else { - return 0; - } - } - - - /** - * Set data uri. The data are stored as Uri. - * - * @param uri the uri - */ - public void setDataUri(Uri uri) { - mUri = uri; - } - - /** - * @return The Uri of the part data or null if the data wasn't set or - * the data is stored as byte array. - * @see #getData - */ - public Uri getDataUri() { - return mUri; - } - - /** - * Set Content-id value - * - * @param contentId the content-id value - * @throws NullPointerException if the value is null. - */ - public void setContentId(byte[] contentId) { - if((contentId == null) || (contentId.length == 0)) { - throw new IllegalArgumentException( - "Content-Id may not be null or empty."); - } - - if ((contentId.length > 1) - && ((char) contentId[0] == '<') - && ((char) contentId[contentId.length - 1] == '>')) { - mPartHeader.put(P_CONTENT_ID, contentId); - return; - } - - // Insert beginning '<' and trailing '>' for Content-Id. - byte[] buffer = new byte[contentId.length + 2]; - buffer[0] = (byte) (0xff & '<'); - buffer[buffer.length - 1] = (byte) (0xff & '>'); - System.arraycopy(contentId, 0, buffer, 1, contentId.length); - mPartHeader.put(P_CONTENT_ID, buffer); - } - - /** - * Get Content-id value. - * - * @return the value - */ - public byte[] getContentId() { - return (byte[]) mPartHeader.get(P_CONTENT_ID); - } - - /** - * Set Char-set value. - * - * @param charset the value - */ - public void setCharset(int charset) { - mPartHeader.put(P_CHARSET, charset); - } - - /** - * Get Char-set value - * - * @return the charset value. Return 0 if charset was not set. - */ - public int getCharset() { - Integer charset = (Integer) mPartHeader.get(P_CHARSET); - if(charset == null) { - return 0; - } else { - return charset.intValue(); - } - } - - /** - * Set Content-Location value. - * - * @param contentLocation the value - * @throws NullPointerException if the value is null. - */ - public void setContentLocation(byte[] contentLocation) { - if(contentLocation == null) { - throw new NullPointerException("null content-location"); - } - - mPartHeader.put(P_CONTENT_LOCATION, contentLocation); - } - - /** - * Get Content-Location value. - * - * @return the value - * return PduPart.disposition[0] instead of (Form-data). - * return PduPart.disposition[1] instead of (Attachment). - * return PduPart.disposition[2] instead of (Inline). - */ - public byte[] getContentLocation() { - return (byte[]) mPartHeader.get(P_CONTENT_LOCATION); - } - - /** - * Set Content-Disposition value. - * Use PduPart.disposition[0] instead of (Form-data). - * Use PduPart.disposition[1] instead of (Attachment). - * Use PduPart.disposition[2] instead of (Inline). - * - * @param contentDisposition the value - * @throws NullPointerException if the value is null. - */ - public void setContentDisposition(byte[] contentDisposition) { - if(contentDisposition == null) { - throw new NullPointerException("null content-disposition"); - } - - mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition); - } - - /** - * Get Content-Disposition value. - * - * @return the value - */ - public byte[] getContentDisposition() { - return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION); - } - - /** - * Set Content-Type value. - * - * @param contentType the content type - * @throws NullPointerException if the value is null. - */ - public void setContentType(byte[] contentType) { - if(contentType == null) { - throw new NullPointerException("null content-type"); - } - - mPartHeader.put(P_CONTENT_TYPE, contentType); - } - - /** - * Get Content-Type value of part. - * - * @return the value - */ - public byte[] getContentType() { - return (byte[]) mPartHeader.get(P_CONTENT_TYPE); - } - - /** - * Set Content-Transfer-Encoding value - * - * @param contentTransferEncoding the Content-Transfer-Encoding value - * @throws NullPointerException if the value is null. - */ - public void setContentTransferEncoding(byte[] contentTransferEncoding) { - if(contentTransferEncoding == null) { - throw new NullPointerException("null content-transfer-encoding"); - } - - mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding); - } - - /** - * Get Content-Transfer-Encoding value. - * - * @return the value - */ - public byte[] getContentTransferEncoding() { - return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING); - } - - /** - * Set Content-type parameter: name. - * - * @param name the name value - * @throws NullPointerException if the value is null. - */ - public void setName(byte[] name) { - if(null == name) { - throw new NullPointerException("null content-id"); - } - - mPartHeader.put(P_NAME, name); - } - - /** - * Get content-type parameter: name. - * - * @return the name - */ - public byte[] getName() { - return (byte[]) mPartHeader.get(P_NAME); - } - - /** - * Get Content-disposition parameter: filename - * - * @param fileName the filename value - * @throws NullPointerException if the value is null. - */ - public void setFilename(byte[] fileName) { - if(null == fileName) { - throw new NullPointerException("null content-id"); - } - - mPartHeader.put(P_FILENAME, fileName); - } - - /** - * Set Content-disposition parameter: filename - * - * @return the filename - */ - public byte[] getFilename() { - return (byte[]) mPartHeader.get(P_FILENAME); - } - - public String generateLocation() { - // Assumption: At least one of the content-location / name / filename - // or content-id should be set. This is guaranteed by the PduParser - // for incoming messages and by MM composer for outgoing messages. - byte[] location = (byte[]) mPartHeader.get(P_NAME); - if(null == location) { - location = (byte[]) mPartHeader.get(P_FILENAME); - - if (null == location) { - location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION); - } - } - - if (null == location) { - byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID); - return "cid:" + new String(contentId); - } else { - return new String(location); - } - } -} - diff --git a/src/android/support/v7/mms/pdu/QuotedPrintable.java b/src/android/support/v7/mms/pdu/QuotedPrintable.java deleted file mode 100644 index ea0abc5..0000000 --- a/src/android/support/v7/mms/pdu/QuotedPrintable.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import java.io.ByteArrayOutputStream; - -public class QuotedPrintable { - private static byte ESCAPE_CHAR = '='; - - /** - * Decodes an array quoted-printable characters into an array of original bytes. - * Escaped characters are converted back to their original representation. - * - *

- * This function implements a subset of - * quoted-printable encoding specification (rule #1 and rule #2) - * as defined in RFC 1521. - *

- * - * @param bytes array of quoted-printable characters - * @return array of original bytes, - * null if quoted-printable decoding is unsuccessful. - */ - public static final byte[] decodeQuotedPrintable(byte[] bytes) { - if (bytes == null) { - return null; - } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (int i = 0; i < bytes.length; i++) { - int b = bytes[i]; - if (b == ESCAPE_CHAR) { - try { - if('\r' == (char)bytes[i + 1] && - '\n' == (char)bytes[i + 2]) { - i += 2; - continue; - } - int u = Character.digit((char) bytes[++i], 16); - int l = Character.digit((char) bytes[++i], 16); - if (u == -1 || l == -1) { - return null; - } - buffer.write((char) ((u << 4) + l)); - } catch (ArrayIndexOutOfBoundsException e) { - return null; - } - } else { - buffer.write(b); - } - } - return buffer.toByteArray(); - } -} diff --git a/src/android/support/v7/mms/pdu/ReadOrigInd.java b/src/android/support/v7/mms/pdu/ReadOrigInd.java deleted file mode 100644 index 500a59d..0000000 --- a/src/android/support/v7/mms/pdu/ReadOrigInd.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class ReadOrigInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public ReadOrigInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - ReadOrigInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-MMS-Read-status value. - * - * @return the value - */ - public int getReadStatus() { - return mPduHeaders.getOctet(PduHeaders.READ_STATUS); - } - - /** - * Set X-MMS-Read-status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/ReadRecInd.java b/src/android/support/v7/mms/pdu/ReadRecInd.java deleted file mode 100644 index 9683d00..0000000 --- a/src/android/support/v7/mms/pdu/ReadRecInd.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class ReadRecInd extends GenericPdu { - /** - * Constructor, used when composing a M-ReadRec.ind pdu. - * - * @param from the from value - * @param messageId the message ID value - * @param mmsVersion current viersion of mms - * @param readStatus the read status value - * @param to the to value - * @throws InvalidHeaderValueException if parameters are invalid. - * NullPointerException if messageId or to is null. - */ - public ReadRecInd(EncodedStringValue from, - byte[] messageId, - int mmsVersion, - int readStatus, - EncodedStringValue[] to) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND); - setFrom(from); - setMessageId(messageId); - setMmsVersion(mmsVersion); - setTo(to); - setReadStatus(readStatus); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - ReadRecInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /** - * Get X-MMS-Read-status value. - * - * @return the value - */ - public int getReadStatus() { - return mPduHeaders.getOctet(PduHeaders.READ_STATUS); - } - - /** - * Set X-MMS-Read-status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/RetrieveConf.java b/src/android/support/v7/mms/pdu/RetrieveConf.java deleted file mode 100644 index 752ca04..0000000 --- a/src/android/support/v7/mms/pdu/RetrieveConf.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -/** - * M-Retrive.conf Pdu. - */ -public class RetrieveConf extends MultimediaMessagePdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public RetrieveConf() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - RetrieveConf(PduHeaders headers) { - super(headers); - } - - /** - * Constructor with given headers and body - * - * @param headers Headers for this PDU. - * @param body Body of this PDu. - */ - RetrieveConf(PduHeaders headers, PduBody body) { - super(headers, body); - } - - /** - * Get CC value. - * - * @return the value - */ - public EncodedStringValue[] getCc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.CC); - } - - /** - * Add a "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addCc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); - } - - /** - * Get Content-type value. - * - * @return the value - */ - public byte[] getContentType() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); - } - - /** - * Set Content-type value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setContentType(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); - } - - /** - * Get X-Mms-Delivery-Report value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-Mms-Read-Report value. - * - * @return the value - */ - public int getReadReport() { - return mPduHeaders.getOctet(PduHeaders.READ_REPORT); - } - - /** - * Set X-Mms-Read-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); - } - - /** - * Get X-Mms-Retrieve-Status value. - * - * @return the value - */ - public int getRetrieveStatus() { - return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS); - } - - /** - * Set X-Mms-Retrieve-Status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setRetrieveStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS); - } - - /** - * Get X-Mms-Retrieve-Text value. - * - * @return the value - */ - public EncodedStringValue getRetrieveText() { - return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT); - } - - /** - * Set X-Mms-Retrieve-Text value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setRetrieveText(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT); - } - - /** - * Get X-Mms-Transaction-Id. - * - * @return the value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getContentClass() {return 0x00;} - * public void setApplicId(byte value) {} - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public byte getDistributionIndicator() {return 0x00;} - * public void setDistributionIndicator(byte value) {} - * - * public PreviouslySentByValue getPreviouslySentBy() {return null;} - * public void setPreviouslySentBy(PreviouslySentByValue value) {} - * - * public PreviouslySentDateValue getPreviouslySentDate() {} - * public void setPreviouslySentDate(PreviouslySentDateValue value) {} - * - * public MmFlagsValue getMmFlags() {return null;} - * public void setMmFlags(MmFlagsValue value) {} - * - * public MmStateValue getMmState() {return null;} - * public void getMmState(MmStateValue value) {} - * - * public byte[] getReplaceId() {return 0x00;} - * public void setReplaceId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/SendConf.java b/src/android/support/v7/mms/pdu/SendConf.java deleted file mode 100644 index 5486398..0000000 --- a/src/android/support/v7/mms/pdu/SendConf.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -public class SendConf extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public SendConf() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - SendConf(PduHeaders headers) { - super(headers); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-Mms-Response-Status. - * - * @return the value - */ - public int getResponseStatus() { - return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS); - } - - /** - * Set X-Mms-Response-Status. - * - * @param value the values - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setResponseStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getContentLocation() {return null;} - * public void setContentLocation(byte[] value) {} - * - * public EncodedStringValue getResponseText() {return null;} - * public void setResponseText(EncodedStringValue value) {} - * - * public byte getStoreStatus() {return 0x00;} - * public void setStoreStatus(byte value) {} - * - * public byte[] getStoreStatusText() {return null;} - * public void setStoreStatusText(byte[] value) {} - */ -} diff --git a/src/android/support/v7/mms/pdu/SendReq.java b/src/android/support/v7/mms/pdu/SendReq.java deleted file mode 100644 index 0b78cc3..0000000 --- a/src/android/support/v7/mms/pdu/SendReq.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v7.mms.pdu; - -import android.util.Log; - -public class SendReq extends MultimediaMessagePdu { - private static final String TAG = "SendReq"; - - public SendReq() { - super(); - - try { - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); - setMmsVersion(PduHeaders.CURRENT_MMS_VERSION); - // FIXME: Content-type must be decided according to whether - // SMIL part present. - setContentType("application/vnd.wap.multipart.related".getBytes()); - setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes())); - setTransactionId(generateTransactionId()); - } catch (InvalidHeaderValueException e) { - // Impossible to reach here since all headers we set above are valid. - Log.e(TAG, "Unexpected InvalidHeaderValueException.", e); - throw new RuntimeException(e); - } - } - - private byte[] generateTransactionId() { - String transactionId = "T" + Long.toHexString(System.currentTimeMillis()); - return transactionId.getBytes(); - } - - /** - * Constructor, used when composing a M-Send.req pdu. - * - * @param contentType the content type value - * @param from the from value - * @param mmsVersion current viersion of mms - * @param transactionId the transaction-id value - * @throws InvalidHeaderValueException if parameters are invalid. - * NullPointerException if contentType, form or transactionId is null. - */ - public SendReq(byte[] contentType, - EncodedStringValue from, - int mmsVersion, - byte[] transactionId) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); - setContentType(contentType); - setFrom(from); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - SendReq(PduHeaders headers) { - super(headers); - } - - /** - * Constructor with given headers and body - * - * @param headers Headers for this PDU. - * @param body Body of this PDu. - */ - SendReq(PduHeaders headers, PduBody body) { - super(headers, body); - } - - /** - * Get Bcc value. - * - * @return the value - */ - public EncodedStringValue[] getBcc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.BCC); - } - - /** - * Add a "BCC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addBcc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC); - } - - /** - * Set "BCC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setBcc(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC); - } - - /** - * Get CC value. - * - * @return the value - */ - public EncodedStringValue[] getCc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.CC); - } - - /** - * Add a "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addCc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); - } - - /** - * Set "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setCc(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.CC); - } - - /** - * Get Content-type value. - * - * @return the value - */ - public byte[] getContentType() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); - } - - /** - * Set Content-type value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setContentType(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); - } - - /** - * Get X-Mms-Delivery-Report value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /** - * Get X-Mms-Expiry value. - * - * Expiry-value = Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) - * - * @return the value - */ - public long getExpiry() { - return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); - } - - /** - * Set X-Mms-Expiry value. - * - * @param value the value - */ - public void setExpiry(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); - } - - /** - * Get X-Mms-MessageSize value. - * - * Expiry-value = size of message - * - * @return the value - */ - public long getMessageSize() { - return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); - } - - /** - * Set X-Mms-MessageSize value. - * - * @param value the value - */ - public void setMessageSize(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get X-Mms-Read-Report value. - * - * @return the value - */ - public int getReadReport() { - return mPduHeaders.getOctet(PduHeaders.READ_REPORT); - } - - /** - * Set X-Mms-Read-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); - } - - /** - * Set "To" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte getAdaptationAllowed() {return 0}; - * public void setAdaptationAllowed(btye value) {}; - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getContentClass() {return 0x00;} - * public void setApplicId(byte value) {} - * - * public long getDeliveryTime() {return 0}; - * public void setDeliveryTime(long value) {}; - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public MmFlagsValue getMmFlags() {return null;} - * public void setMmFlags(MmFlagsValue value) {} - * - * public MmStateValue getMmState() {return null;} - * public void getMmState(MmStateValue value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getStore() {return 0x00;} - * public void setStore(byte value) {} - */ -} diff --git a/src/com/android/messaging/BugleApplication.java b/src/com/android/messaging/BugleApplication.java deleted file mode 100644 index a5aea9f..0000000 --- a/src/com/android/messaging/BugleApplication.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.app.Application; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.os.Handler; -import android.os.Looper; -import android.support.v7.mms.CarrierConfigValuesLoader; -import android.support.v7.mms.MmsManager; -import android.telephony.CarrierConfigManager; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.receiver.SmsReceiver; -import com.android.messaging.sms.ApnDatabase; -import com.android.messaging.sms.BugleApnSettingsLoader; -import com.android.messaging.sms.BugleUserAgentInfoLoader; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.Trace; -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.lang.Thread.UncaughtExceptionHandler; - -/** - * The application object - */ -public class BugleApplication extends Application implements UncaughtExceptionHandler { - private static final String TAG = LogUtil.BUGLE_TAG; - - private UncaughtExceptionHandler sSystemUncaughtExceptionHandler; - private static boolean sRunningTests = false; - - @VisibleForTesting - protected static void setTestsRunning() { - sRunningTests = true; - } - - /** - * @return true if we're running unit tests. - */ - public static boolean isRunningTests() { - return sRunningTests; - } - - @Override - public void onCreate() { - Trace.beginSection("app.onCreate"); - super.onCreate(); - - // Note onCreate is called in both test and real application environments - if (!sRunningTests) { - // Only create the factory if not running tests - FactoryImpl.register(getApplicationContext(), this); - } else { - LogUtil.e(TAG, "BugleApplication.onCreate: FactoryImpl.register skipped for test run"); - } - - sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); - Trace.endSection(); - } - - @Override - public void onConfigurationChanged(final Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - // Update conversation drawables when changing writing systems - // (Right-To-Left / Left-To-Right) - ConversationDrawables.get().updateDrawables(); - } - - // Called by the "real" factory from FactoryImpl.register() (i.e. not run in tests) - public void initializeSync(final Factory factory) { - Trace.beginSection("app.initializeSync"); - final Context context = factory.getApplicationContext(); - final BugleGservices bugleGservices = factory.getBugleGservices(); - final BuglePrefs buglePrefs = factory.getApplicationPrefs(); - final DataModel dataModel = factory.getDataModel(); - final CarrierConfigValuesLoader carrierConfigValuesLoader = - factory.getCarrierConfigValuesLoader(); - - maybeStartProfiling(); - - BugleApplication.updateAppConfig(context); - - // Initialize MMS lib - initMmsLib(context, bugleGservices, carrierConfigValuesLoader); - // Initialize APN database - ApnDatabase.initializeAppContext(context); - // Fixup messages in flight if we crashed and send any pending - dataModel.onApplicationCreated(); - // Register carrier config change receiver - if (OsUtil.isAtLeastM()) { - registerCarrierConfigChangeReceiver(context); - } - - Trace.endSection(); - } - - private static void registerCarrierConfigChangeReceiver(final Context context) { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - LogUtil.i(TAG, "Carrier config changed. Reloading MMS config."); - MmsConfig.loadAsync(); - } - }, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); - } - - private static void initMmsLib(final Context context, final BugleGservices bugleGservices, - final CarrierConfigValuesLoader carrierConfigValuesLoader) { - MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context)); - MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader); - MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context)); - MmsManager.setUseWakeLock(true); - // If Gservices is configured not to use mms api, force MmsManager to always use - // legacy mms sending logic - MmsManager.setForceLegacyMms(!bugleGservices.getBoolean( - BugleGservicesKeys.USE_MMS_API_IF_PRESENT, - BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT)); - bugleGservices.registerForChanges(new Runnable() { - @Override - public void run() { - MmsManager.setForceLegacyMms(!bugleGservices.getBoolean( - BugleGservicesKeys.USE_MMS_API_IF_PRESENT, - BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT)); - } - }); - } - - public static void updateAppConfig(final Context context) { - // Make sure we set the correct state for the SMS/MMS receivers - SmsReceiver.updateSmsReceiveHandler(context); - } - - // Called from thread started in FactoryImpl.register() (i.e. not run in tests) - public void initializeAsync(final Factory factory) { - // Handle shared prefs upgrade & Load MMS Configuration - Trace.beginSection("app.initializeAsync"); - maybeHandleSharedPrefsUpgrade(factory); - MmsConfig.load(); - Trace.endSection(); - } - - @Override - public void onLowMemory() { - super.onLowMemory(); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "BugleApplication.onLowMemory"); - } - Factory.get().reclaimMemory(); - } - - @Override - public void uncaughtException(final Thread thread, final Throwable ex) { - final boolean background = getMainLooper().getThread() != thread; - if (background) { - LogUtil.e(TAG, "Uncaught exception in background thread " + thread, ex); - - final Handler handler = new Handler(getMainLooper()); - handler.post(new Runnable() { - - @Override - public void run() { - sSystemUncaughtExceptionHandler.uncaughtException(thread, ex); - } - }); - } else { - sSystemUncaughtExceptionHandler.uncaughtException(thread, ex); - } - } - - private void maybeStartProfiling() { - // App startup profiling support. To use it: - // adb shell setprop log.tag.BugleProfile DEBUG - // # Start the app, wait for a 30s, download trace file: - // adb pull /data/data/com.android.messaging/cache/startup.trace /tmp - // # Open trace file (using adt/tools/traceview) - if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) { - // Start method tracing with a big enough buffer and let it run for 30s. - // Note we use a logging tag as we don't want to wait for gservices to start up. - final File file = DebugUtils.getDebugFile("startup.trace", true); - if (file != null) { - android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024); - new Handler(Looper.getMainLooper()).postDelayed( - new Runnable() { - @Override - public void run() { - android.os.Debug.stopMethodTracing(); - // Allow world to see trace file - DebugUtils.ensureReadable(file); - LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - " - + file.getAbsolutePath()); - } - }, 30000); - } - } - } - - private void maybeHandleSharedPrefsUpgrade(final Factory factory) { - final int existingVersion = factory.getApplicationPrefs().getInt( - BuglePrefsKeys.SHARED_PREFERENCES_VERSION, - BuglePrefsKeys.SHARED_PREFERENCES_VERSION_DEFAULT); - final int targetVersion = Integer.parseInt(getString(R.string.pref_version)); - if (targetVersion > existingVersion) { - LogUtil.i(LogUtil.BUGLE_TAG, "Upgrading shared prefs from " + existingVersion + - " to " + targetVersion); - try { - // Perform upgrade on application-wide prefs. - factory.getApplicationPrefs().onUpgrade(existingVersion, targetVersion); - // Perform upgrade on each subscription's prefs. - PhoneUtils.forEachActiveSubscription(new PhoneUtils.SubscriptionRunnable() { - @Override - public void runForSubscription(final int subId) { - factory.getSubscriptionPrefs(subId) - .onUpgrade(existingVersion, targetVersion); - } - }); - factory.getApplicationPrefs().putInt(BuglePrefsKeys.SHARED_PREFERENCES_VERSION, - targetVersion); - } catch (final Exception ex) { - // Upgrade failed. Don't crash the app because we can always fall back to the - // default settings. - LogUtil.e(LogUtil.BUGLE_TAG, "Failed to upgrade shared prefs", ex); - } - } else if (targetVersion < existingVersion) { - // We don't care about downgrade since real user shouldn't encounter this, so log it - // and ignore any prefs migration. - LogUtil.e(LogUtil.BUGLE_TAG, "Shared prefs downgrade requested and ignored. " + - "oldVersion = " + existingVersion + ", newVersion = " + targetVersion); - } - } -} diff --git a/src/com/android/messaging/Factory.java b/src/com/android/messaging/Factory.java deleted file mode 100644 index c542a54..0000000 --- a/src/com/android/messaging/Factory.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.Context; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver; -import com.android.messaging.datamodel.media.MediaCacheManager; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.sms.BugleCarrierConfigValuesLoader; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.MediaUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.annotations.VisibleForTesting; - -public abstract class Factory { - - // Making this volatile because on the unit tests, setInstance is called from a unit test - // thread, and then it's read on the UI thread. - private static volatile Factory sInstance; - @VisibleForTesting - protected static boolean sRegistered; - @VisibleForTesting - protected static boolean sInitialized; - - public static Factory get() { - return sInstance; - } - - protected static void setInstance(final Factory factory) { - // Not allowed to call this after real application initialization is complete - Assert.isTrue(!sRegistered); - Assert.isTrue(!sInitialized); - sInstance = factory; - } - public abstract void onRequiredPermissionsAcquired(); - - public abstract Context getApplicationContext(); - public abstract DataModel getDataModel(); - public abstract BugleGservices getBugleGservices(); - public abstract BuglePrefs getApplicationPrefs(); - public abstract BuglePrefs getSubscriptionPrefs(int subId); - public abstract BuglePrefs getWidgetPrefs(); - public abstract UIIntents getUIIntents(); - public abstract MemoryCacheManager getMemoryCacheManager(); - public abstract MediaResourceManager getMediaResourceManager(); - public abstract MediaCacheManager getMediaCacheManager(); - public abstract ContactContentObserver getContactContentObserver(); - public abstract PhoneUtils getPhoneUtils(int subId); - public abstract MediaUtil getMediaUtil(); - public abstract BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader(); - // Note this needs to run from any thread - public abstract void reclaimMemory(); - - public abstract void onActivityResume(); -} diff --git a/src/com/android/messaging/FactoryImpl.java b/src/com/android/messaging/FactoryImpl.java deleted file mode 100644 index a862308..0000000 --- a/src/com/android/messaging/FactoryImpl.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.Context; -import android.os.Process; -import android.telephony.SmsManager; -import android.util.SparseArray; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelImpl; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.media.BugleMediaCacheManager; -import com.android.messaging.datamodel.media.MediaCacheManager; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.sms.BugleCarrierConfigValuesLoader; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.UIIntentsImpl; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleApplicationPrefs; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesImpl; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BugleSubscriptionPrefs; -import com.android.messaging.util.BugleWidgetPrefs; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaUtil; -import com.android.messaging.util.MediaUtilImpl; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.concurrent.ConcurrentHashMap; - -class FactoryImpl extends Factory { - private BugleApplication mApplication; - private DataModel mDataModel; - private BugleGservices mBugleGservices; - private BugleApplicationPrefs mBugleApplicationPrefs; - private BugleWidgetPrefs mBugleWidgetPrefs; - private Context mApplicationContext; - private UIIntents mUIIntents; - private MemoryCacheManager mMemoryCacheManager; - private MediaResourceManager mMediaResourceManager; - private MediaCacheManager mMediaCacheManager; - private ContactContentObserver mContactContentObserver; - private PhoneUtils mPhoneUtils; - private MediaUtil mMediaUtil; - private SparseArray mSubscriptionPrefs; - private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader; - - // Cached instance for Pre-L_MR1 - private static final Object PHONEUTILS_INSTANCE_LOCK = new Object(); - private static PhoneUtils sPhoneUtilsInstancePreLMR1 = null; - // Cached subId->instance for L_MR1 and beyond - private static final ConcurrentHashMap sPhoneUtilsInstanceCacheLMR1 = - new ConcurrentHashMap<>(); - - private FactoryImpl() { - } - - public static Factory register(final Context applicationContext, - final BugleApplication application) { - // This only gets called once (from BugleApplication.onCreate), but its not called in tests. - Assert.isTrue(!sRegistered); - Assert.isNull(Factory.get()); - - final FactoryImpl factory = new FactoryImpl(); - Factory.setInstance(factory); - sRegistered = true; - - // At this point Factory is published. Services can now get initialized and depend on - // Factory.get(). - factory.mApplication = application; - factory.mApplicationContext = applicationContext; - factory.mMemoryCacheManager = new MemoryCacheManager(); - factory.mMediaCacheManager = new BugleMediaCacheManager(); - factory.mMediaResourceManager = new MediaResourceManager(); - factory.mBugleGservices = new BugleGservicesImpl(applicationContext); - factory.mBugleApplicationPrefs = new BugleApplicationPrefs(applicationContext); - factory.mDataModel = new DataModelImpl(applicationContext); - factory.mBugleWidgetPrefs = new BugleWidgetPrefs(applicationContext); - factory.mUIIntents = new UIIntentsImpl(); - factory.mContactContentObserver = new ContactContentObserver(); - factory.mMediaUtil = new MediaUtilImpl(); - factory.mSubscriptionPrefs = new SparseArray(); - factory.mCarrierConfigValuesLoader = new BugleCarrierConfigValuesLoader(applicationContext); - - Assert.initializeGservices(factory.mBugleGservices); - LogUtil.initializeGservices(factory.mBugleGservices); - - if (OsUtil.hasRequiredPermissions()) { - factory.onRequiredPermissionsAcquired(); - } - - return factory; - } - - @Override - public void onRequiredPermissionsAcquired() { - if (sInitialized) { - return; - } - sInitialized = true; - - mApplication.initializeSync(this); - - final Thread asyncInitialization = new Thread() { - @Override - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - mApplication.initializeAsync(FactoryImpl.this); - } - }; - asyncInitialization.start(); - } - - @Override - public Context getApplicationContext() { - return mApplicationContext; - } - - @Override - public DataModel getDataModel() { - return mDataModel; - } - - @Override - public BugleGservices getBugleGservices() { - return mBugleGservices; - } - - @Override - public BuglePrefs getApplicationPrefs() { - return mBugleApplicationPrefs; - } - - @Override - public BuglePrefs getWidgetPrefs() { - return mBugleWidgetPrefs; - } - - @Override - public BuglePrefs getSubscriptionPrefs(int subId) { - subId = PhoneUtils.getDefault().getEffectiveSubId(subId); - BugleSubscriptionPrefs pref = mSubscriptionPrefs.get(subId); - if (pref == null) { - synchronized (this) { - if ((pref = mSubscriptionPrefs.get(subId)) == null) { - pref = new BugleSubscriptionPrefs(getApplicationContext(), subId); - mSubscriptionPrefs.put(subId, pref); - } - } - } - return pref; - } - - @Override - public UIIntents getUIIntents() { - return mUIIntents; - } - - @Override - public MemoryCacheManager getMemoryCacheManager() { - return mMemoryCacheManager; - } - - @Override - public MediaResourceManager getMediaResourceManager() { - return mMediaResourceManager; - } - - @Override - public MediaCacheManager getMediaCacheManager() { - return mMediaCacheManager; - } - - @Override - public ContactContentObserver getContactContentObserver() { - return mContactContentObserver; - } - - @Override - public PhoneUtils getPhoneUtils(int subId) { - if (OsUtil.isAtLeastL_MR1()) { - if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) { - subId = SmsManager.getDefaultSmsSubscriptionId(); - } - if (subId < 0) { - LogUtil.w(LogUtil.BUGLE_TAG, "PhoneUtils.getForLMR1(): invalid subId = " + subId); - subId = ParticipantData.DEFAULT_SELF_SUB_ID; - } - PhoneUtils instance = sPhoneUtilsInstanceCacheLMR1.get(subId); - if (instance == null) { - instance = new PhoneUtils.PhoneUtilsLMR1(subId); - sPhoneUtilsInstanceCacheLMR1.putIfAbsent(subId, instance); - } - return instance; - } else { - Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID); - if (sPhoneUtilsInstancePreLMR1 == null) { - synchronized (PHONEUTILS_INSTANCE_LOCK) { - if (sPhoneUtilsInstancePreLMR1 == null) { - sPhoneUtilsInstancePreLMR1 = new PhoneUtils.PhoneUtilsPreLMR1(); - } - } - } - return sPhoneUtilsInstancePreLMR1; - } - } - - @Override - public void reclaimMemory() { - mMemoryCacheManager.reclaimMemory(); - } - - @Override - public void onActivityResume() { - } - - @Override - public MediaUtil getMediaUtil() { - return mMediaUtil; - } - - @Override - public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() { - return mCarrierConfigValuesLoader; - } -} diff --git a/src/com/android/messaging/annotation/VisibleForAnimation.java b/src/com/android/messaging/annotation/VisibleForAnimation.java deleted file mode 100644 index f97d827..0000000 --- a/src/com/android/messaging/annotation/VisibleForAnimation.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.annotation; - -/** - * An annotation for class members that are made visible for Android's ObjectAnimator to work - * properly through reflection. - */ -public @interface VisibleForAnimation { -} diff --git a/src/com/android/messaging/datamodel/BitmapPool.java b/src/com/android/messaging/datamodel/BitmapPool.java deleted file mode 100644 index 1ec4f76..0000000 --- a/src/com/android/messaging/datamodel/BitmapPool.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.util.SparseArray; - -import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.io.InputStream; - -/** - * Class for creating / loading / reusing bitmaps. This class allow the user to create a new bitmap, - * reuse an bitmap from the pool and to return a bitmap for future reuse. The pool of bitmaps - * allows for faster decode and more efficient memory usage. - * Note: consumers should not create BitmapPool directly, but instead get the pool they want from - * the BitmapPoolManager. - */ -public class BitmapPool implements MemoryCache { - public static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF; - - protected static final boolean VERBOSE = false; - - /** - * Number of reuse failures to skip before reporting. - */ - private static final int FAILED_REPORTING_FREQUENCY = 100; - - /** - * Count of reuse failures which have occurred. - */ - private static volatile int sFailedBitmapReuseCount = 0; - - /** - * Overall pool data structure which currently only supports rectangular bitmaps. The size of - * one of the sides is used to index into the SparseArray. - */ - private final SparseArray mPool; - private final Object mPoolLock = new Object(); - private final String mPoolName; - private final int mMaxSize; - - /** - * Inner structure which holds a pool of bitmaps all the same size (i.e. all have the same - * width as each other and height as each other, but not necessarily the same). - */ - private class SingleSizePool { - int mNumItems; - final Bitmap[] mBitmaps; - - SingleSizePool(final int maxPoolSize) { - mNumItems = 0; - mBitmaps = new Bitmap[maxPoolSize]; - } - } - - /** - * Creates a pool of reused bitmaps with helper decode methods which will attempt to use the - * reclaimed bitmaps. This will help speed up the creation of bitmaps by using already allocated - * bitmaps. - * @param maxSize The overall max size of the pool. When the pool exceeds this size, all calls - * to reclaimBitmap(Bitmap) will result in recycling the bitmap. - * @param name Name of the bitmap pool and only used for logging. Can not be null. - */ - BitmapPool(final int maxSize, @NonNull final String name) { - Assert.isTrue(maxSize > 0); - Assert.isTrue(!TextUtils.isEmpty(name)); - mPoolName = name; - mMaxSize = maxSize; - mPool = new SparseArray(); - } - - @Override - public void reclaim() { - synchronized (mPoolLock) { - for (int p = 0; p < mPool.size(); p++) { - final SingleSizePool singleSizePool = mPool.valueAt(p); - for (int i = 0; i < singleSizePool.mNumItems; i++) { - singleSizePool.mBitmaps[i].recycle(); - singleSizePool.mBitmaps[i] = null; - } - singleSizePool.mNumItems = 0; - } - mPool.clear(); - } - } - - /** - * Creates a new BitmapFactory.Options. - */ - public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled, - final int inputDensity, final int targetDensity) { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = scaled; - options.inDensity = inputDensity; - options.inTargetDensity = targetDensity; - options.inSampleSize = 1; - options.inJustDecodeBounds = false; - options.inMutable = true; - return options; - } - - /** - * @return The pool key for the provided image dimensions or 0 if either width or height is - * greater than the max supported image dimension. - */ - private int getPoolKey(final int width, final int height) { - if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) { - return 0; - } - return (width << 16) | height; - } - - /** - * - * @return A bitmap in the pool with the specified dimensions or null if no bitmap with the - * specified dimension is available. - */ - private Bitmap findPoolBitmap(final int width, final int height) { - final int poolKey = getPoolKey(width, height); - if (poolKey != 0) { - synchronized (mPoolLock) { - // Take a bitmap from the pool if one is available - final SingleSizePool singlePool = mPool.get(poolKey); - if (singlePool != null && singlePool.mNumItems > 0) { - singlePool.mNumItems--; - final Bitmap foundBitmap = singlePool.mBitmaps[singlePool.mNumItems]; - singlePool.mBitmaps[singlePool.mNumItems] = null; - return foundBitmap; - } - } - } - return null; - } - - /** - * Internal function to try and find a bitmap in the pool which matches the desired width and - * height and then set that in the bitmap options properly. - * - * TODO: Why do we take a width/height? Shouldn't this already be in the - * BitmapFactory.Options instance? Can we assert that they match? - * @param optionsTmp The BitmapFactory.Options to update with the bitmap for the system to try - * to reuse. - * @param width The width of the reusable bitmap. - * @param height The height of the reusable bitmap. - */ - private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width, - final int height) { - if (optionsTmp.inJustDecodeBounds) { - return; - } - optionsTmp.inBitmap = findPoolBitmap(width, height); - } - - /** - * Load a resource into a bitmap. Uses a bitmap from the pool if possible to reduce memory - * turnover. - * @param resourceId Resource id to load. - * @param resources Application resources. Cannot be null. - * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot - * be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return The decoded Bitmap with the resource drawn in it. - */ - public Bitmap decodeSampledBitmapFromResource(final int resourceId, - @NonNull final Resources resources, @NonNull final BitmapFactory.Options optionsTmp, - final int width, final int height) { - Assert.notNull(resources); - Assert.notNull(optionsTmp); - Assert.isTrue(width > 0); - Assert.isTrue(height > 0); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp); - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp); - sFailedBitmapReuseCount++; - if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { - LogUtil.w(LogUtil.BUGLE_TAG, - "Pooled bitmap consistently not being reused count = " + - sFailedBitmapReuseCount); - } - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding resource " + resourceId); - reclaim(); - } - return b; - } - - /** - * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce memory - * turnover. - * @param inputStream InputStream load. Cannot be null. - * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot - * be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return The decoded Bitmap with the resource drawn in it. - */ - public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream, - @NonNull final BitmapFactory.Options optionsTmp, - final int width, final int height) { - Assert.notNull(inputStream); - Assert.isTrue(width > 0); - Assert.isTrue(height > 0); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - sFailedBitmapReuseCount++; - if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { - LogUtil.w(LogUtil.BUGLE_TAG, - "Pooled bitmap consistently not being reused count = " + - sFailedBitmapReuseCount); - } - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding inputStream"); - reclaim(); - } - return b; - } - - /** - * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce memory - * turnover. - * @param bytes Encoded bytes to draw on the bitmap. Cannot be null. - * @param optionsTmp The bitmap will set here and the input should be generated from - * getBitmapOptionsForPool(). Cannot be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return A Bitmap with the encoded bytes drawn in it. - */ - public Bitmap decodeByteArray(@NonNull final byte[] bytes, - @NonNull final BitmapFactory.Options optionsTmp, final int width, - final int height) throws OutOfMemoryError { - Assert.notNull(bytes); - Assert.notNull(optionsTmp); - Assert.isTrue(width > 0); - Assert.isTrue(height > 0); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - } catch (final IllegalArgumentException e) { - if (VERBOSE) { - LogUtil.v(LogUtil.BUGLE_TAG, "BitmapPool(" + mPoolName + - ") Unable to use pool bitmap"); - } - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - // (i.e. without the bitmap from the pool) - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - sFailedBitmapReuseCount++; - if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { - LogUtil.w(LogUtil.BUGLE_TAG, - "Pooled bitmap consistently not being reused count = " + - sFailedBitmapReuseCount); - } - } - } - return b; - } - - /** - * Creates a bitmap with the given size, this will reuse a bitmap in the pool, if one is - * available, otherwise this will create a new one. - * @param width The desired width of the bitmap. - * @param height The desired height of the bitmap. - * @return A bitmap with the desired width and height, this maybe a reused bitmap from the pool. - */ - public Bitmap createOrReuseBitmap(final int width, final int height) { - Bitmap b = findPoolBitmap(width, height); - if (b == null) { - b = createBitmap(width, height); - } - return b; - } - - /** - * This will create a new bitmap regardless of pool state. - * @param width The desired width of the bitmap. - * @param height The desired height of the bitmap. - * @return A bitmap with the desired width and height. - */ - private Bitmap createBitmap(final int width, final int height) { - return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - } - - /** - * Called when a bitmap is finished being used so that it can be used for another bitmap in the - * future or recycled. Any bitmaps returned should not be used by the caller again. - * @param b The bitmap to return to the pool for future usage or recycled. This cannot be null. - */ - public void reclaimBitmap(@NonNull final Bitmap b) { - Assert.notNull(b); - final int poolKey = getPoolKey(b.getWidth(), b.getHeight()); - if (poolKey == 0 || !b.isMutable()) { - // Unsupported image dimensions or a immutable bitmap. - b.recycle(); - return; - } - synchronized (mPoolLock) { - SingleSizePool singleSizePool = mPool.get(poolKey); - if (singleSizePool == null) { - singleSizePool = new SingleSizePool(mMaxSize); - mPool.append(poolKey, singleSizePool); - } - if (singleSizePool.mNumItems < singleSizePool.mBitmaps.length) { - singleSizePool.mBitmaps[singleSizePool.mNumItems] = b; - singleSizePool.mNumItems++; - } else { - b.recycle(); - } - } - } - - /** - * @return whether the pool is full for a given width and height. - */ - public boolean isFull(final int width, final int height) { - final int poolKey = getPoolKey(width, height); - synchronized (mPoolLock) { - final SingleSizePool singleSizePool = mPool.get(poolKey); - if (singleSizePool != null && - singleSizePool.mNumItems >= singleSizePool.mBitmaps.length) { - return true; - } - return false; - } - } -} diff --git a/src/com/android/messaging/datamodel/BoundCursorLoader.java b/src/com/android/messaging/datamodel/BoundCursorLoader.java deleted file mode 100644 index 84d38e6..0000000 --- a/src/com/android/messaging/datamodel/BoundCursorLoader.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.content.CursorLoader; -import android.net.Uri; - -/** - * Extension to basic cursor loader that has an attached binding id - */ -public class BoundCursorLoader extends CursorLoader { - private final String mBindingId; - - /** - * Create cursor loader for associated binding id - */ - public BoundCursorLoader(final String bindingId, final Context context, final Uri uri, - final String[] projection, final String selection, final String[] selectionArgs, - final String sortOrder) { - super(context, uri, projection, selection, selectionArgs, sortOrder); - mBindingId = bindingId; - } - - /** - * Binding id associated with this loader - consume can check to verify data still valid - * @return - */ - public String getBindingId() { - return mBindingId; - } -} diff --git a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java deleted file mode 100644 index 8c40177..0000000 --- a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java +++ /dev/null @@ -1,1919 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDoneException; -import android.database.sqlite.SQLiteStatement; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.support.v4.util.ArrayMap; -import android.support.v4.util.SimpleArrayMap; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.ParticipantRefresh.ConversationParticipantsQuery; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.UriUtil; -import com.android.messaging.widget.WidgetConversationProvider; -import com.google.common.annotations.VisibleForTesting; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import javax.annotation.Nullable; - - -/** - * This class manages updating our local database - */ -public class BugleDatabaseOperations { - - private static final String TAG = LogUtil.BUGLE_DATABASE_TAG; - - // Global cache of phone numbers -> participant id mapping since this call is expensive. - private static final ArrayMap sNormalizedPhoneNumberToParticipantIdCache = - new ArrayMap(); - - /** - * Convert list of recipient strings (email/phone number) into list of ConversationParticipants - * - * @param recipients The recipient list - * @param refSubId The subId used to normalize phone numbers in the recipients - */ - static ArrayList getConversationParticipantsFromRecipients( - final List recipients, final int refSubId) { - // Generate a list of partially formed participants - final ArrayList participants = new - ArrayList(); - - if (recipients != null) { - for (final String recipient : recipients) { - participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, refSubId)); - } - } - return participants; - } - - /** - * Sanitize a given list of conversation participants by de-duping and stripping out self - * phone number in group conversation. - */ - @DoesNotRunOnMainThread - public static void sanitizeConversationParticipants(final List participants) { - Assert.isNotMainThread(); - if (participants.size() > 0) { - // First remove redundant phone numbers - final HashSet recipients = new HashSet(); - for (int i = participants.size() - 1; i >= 0; i--) { - final String recipient = participants.get(i).getNormalizedDestination(); - if (!recipients.contains(recipient)) { - recipients.add(recipient); - } else { - participants.remove(i); - } - } - if (participants.size() > 1) { - // Remove self phone number from group conversation. - final HashSet selfNumbers = - PhoneUtils.getDefault().getNormalizedSelfNumbers(); - int removed = 0; - // Do this two-pass scan to avoid unnecessary memory allocation. - // Prescan to count the self numbers in the list - for (final ParticipantData p : participants) { - if (selfNumbers.contains(p.getNormalizedDestination())) { - removed++; - } - } - // If all are self numbers, maybe that's what the user wants, just leave - // the participants as is. Otherwise, do another scan to remove self numbers. - if (removed < participants.size()) { - for (int i = participants.size() - 1; i >= 0; i--) { - final String recipient = participants.get(i).getNormalizedDestination(); - if (selfNumbers.contains(recipient)) { - participants.remove(i); - } - } - } - } - } - } - - /** - * Convert list of ConversationParticipants into recipient strings (email/phone number) - */ - @DoesNotRunOnMainThread - public static ArrayList getRecipientsFromConversationParticipants( - final List participants) { - Assert.isNotMainThread(); - // First find the thread id for this list of participants. - final ArrayList recipients = new ArrayList(); - - for (final ParticipantData participant : participants) { - recipients.add(participant.getSendDestination()); - } - return recipients; - } - - /** - * Get or create a conversation based on the message's thread id - * - * NOTE: There are phones on which you can't get the recipients from the thread id for SMS - * until you have a message, so use getOrCreateConversationFromRecipient instead. - * - * TODO: Should this be in MMS/SMS code? - * - * @param db the database - * @param threadId The message's thread - * @param senderBlocked Flag whether sender of message is in blocked people list - * @param refSubId The reference subId for canonicalize phone numbers - * @return conversationId - */ - @DoesNotRunOnMainThread - public static String getOrCreateConversationFromThreadId(final DatabaseWrapper db, - final long threadId, final boolean senderBlocked, final int refSubId) { - Assert.isNotMainThread(); - final List recipients = MmsUtils.getRecipientsByThread(threadId); - final ArrayList participants = - getConversationParticipantsFromRecipients(recipients, refSubId); - - return getOrCreateConversation(db, threadId, senderBlocked, participants, false, false, - null); - } - - /** - * Get or create a conversation based on provided recipient - * - * @param db the database - * @param threadId The message's thread - * @param senderBlocked Flag whether sender of message is in blocked people list - * @param recipient recipient for thread - * @return conversationId - */ - @DoesNotRunOnMainThread - public static String getOrCreateConversationFromRecipient(final DatabaseWrapper db, - final long threadId, final boolean senderBlocked, final ParticipantData recipient) { - Assert.isNotMainThread(); - final ArrayList recipients = new ArrayList<>(1); - recipients.add(recipient); - return getOrCreateConversation(db, threadId, senderBlocked, recipients, false, false, null); - } - - /** - * Get or create a conversation based on provided participants - * - * @param db the database - * @param threadId The message's thread - * @param archived Flag whether the conversation should be created archived - * @param participants list of conversation participants - * @param noNotification If notification should be disabled - * @param noVibrate If vibrate on notification should be disabled - * @param soundUri If there is custom sound URI - * @return a conversation id - */ - @DoesNotRunOnMainThread - public static String getOrCreateConversation(final DatabaseWrapper db, final long threadId, - final boolean archived, final ArrayList participants, - boolean noNotification, boolean noVibrate, String soundUri) { - Assert.isNotMainThread(); - - // Check to see if this conversation is already in out local db cache - String conversationId = BugleDatabaseOperations.getExistingConversation(db, threadId, - false); - - if (conversationId == null) { - final String conversationName = ConversationListItemData.generateConversationName( - participants); - - // Create the conversation with the default self participant which always maps to - // the system default subscription. - final ParticipantData self = ParticipantData.getSelfParticipant( - ParticipantData.DEFAULT_SELF_SUB_ID); - - db.beginTransaction(); - try { - // Look up the "self" participantId (creating if necessary) - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - // Create a new conversation - conversationId = BugleDatabaseOperations.createConversationInTransaction( - db, threadId, conversationName, selfId, participants, archived, - noNotification, noVibrate, soundUri); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - return conversationId; - } - - /** - * Get a conversation from the local DB based on the message's thread id. - * - * @param dbWrapper The database - * @param threadId The message's thread in the SMS database - * @param senderBlocked Flag whether sender of message is in blocked people list - * @return The existing conversation id or null - */ - @VisibleForTesting - @DoesNotRunOnMainThread - public static String getExistingConversation(final DatabaseWrapper dbWrapper, - final long threadId, final boolean senderBlocked) { - Assert.isNotMainThread(); - String conversationId = null; - - Cursor cursor = null; - try { - // Look for an existing conversation in the db with this thread id - cursor = dbWrapper.rawQuery("SELECT " + ConversationColumns._ID - + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE - + " WHERE " + ConversationColumns.SMS_THREAD_ID + "=" + threadId, - null); - - if (cursor.moveToFirst()) { - Assert.isTrue(cursor.getCount() == 1); - conversationId = cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return conversationId; - } - - /** - * Get the thread id for an existing conversation from the local DB. - * - * @param dbWrapper The database - * @param conversationId The conversation to look up thread for - * @return The thread id. Returns -1 if the conversation was not found or if it was found - * but the thread column was NULL. - */ - @DoesNotRunOnMainThread - public static long getThreadId(final DatabaseWrapper dbWrapper, final String conversationId) { - Assert.isNotMainThread(); - long threadId = -1; - - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns.SMS_THREAD_ID }, - ConversationColumns._ID + " =?", - new String[] { conversationId }, - null, null, null); - - if (cursor.moveToFirst()) { - Assert.isTrue(cursor.getCount() == 1); - if (!cursor.isNull(0)) { - threadId = cursor.getLong(0); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return threadId; - } - - @DoesNotRunOnMainThread - public static boolean isBlockedDestination(final DatabaseWrapper db, final String destination) { - Assert.isNotMainThread(); - return isBlockedParticipant(db, destination, ParticipantColumns.NORMALIZED_DESTINATION); - } - - static boolean isBlockedParticipant(final DatabaseWrapper db, final String participantId) { - return isBlockedParticipant(db, participantId, ParticipantColumns._ID); - } - - static boolean isBlockedParticipant(final DatabaseWrapper db, final String value, - final String column) { - Cursor cursor = null; - try { - cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, - new String[] { ParticipantColumns.BLOCKED }, - column + "=? AND " + ParticipantColumns.SUB_ID + "=?", - new String[] { value, - Integer.toString(ParticipantData.OTHER_THAN_SELF_SUB_ID) }, - null, null, null); - - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - return cursor.getInt(0) == 1; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return false; // if there's no row, it's not blocked :-) - } - - /** - * Create a conversation in the local DB based on the message's thread id. - * - * It's up to the caller to make sure that this is all inside a transaction. It will return - * null if it's not in the local DB. - * - * @param dbWrapper The database - * @param threadId The message's thread - * @param selfId The selfId to make default for this conversation - * @param archived Flag whether the conversation should be created archived - * @param noNotification If notification should be disabled - * @param noVibrate If vibrate on notification should be disabled - * @param soundUri The customized sound - * @return The existing conversation id or new conversation id - */ - static String createConversationInTransaction(final DatabaseWrapper dbWrapper, - final long threadId, final String conversationName, final String selfId, - final List participants, final boolean archived, - boolean noNotification, boolean noVibrate, String soundUri) { - // We want conversation and participant creation to be atomic - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - boolean hasEmailAddress = false; - for (final ParticipantData participant : participants) { - Assert.isTrue(!participant.isSelf()); - if (participant.isEmail()) { - hasEmailAddress = true; - } - } - - // TODO : Conversations state - normal vs. archived - - // Insert a new local conversation for this thread id - final ContentValues values = new ContentValues(); - values.put(ConversationColumns.SMS_THREAD_ID, threadId); - // Start with conversation hidden - sending a message or saving a draft will change that - values.put(ConversationColumns.SORT_TIMESTAMP, 0L); - values.put(ConversationColumns.CURRENT_SELF_ID, selfId); - values.put(ConversationColumns.PARTICIPANT_COUNT, participants.size()); - values.put(ConversationColumns.INCLUDE_EMAIL_ADDRESS, (hasEmailAddress ? 1 : 0)); - if (archived) { - values.put(ConversationColumns.ARCHIVE_STATUS, 1); - } - if (noNotification) { - values.put(ConversationColumns.NOTIFICATION_ENABLED, 0); - } - if (noVibrate) { - values.put(ConversationColumns.NOTIFICATION_VIBRATION, 0); - } - if (!TextUtils.isEmpty(soundUri)) { - values.put(ConversationColumns.NOTIFICATION_SOUND_URI, soundUri); - } - - fillParticipantData(values, participants); - - final long conversationRowId = dbWrapper.insert(DatabaseHelper.CONVERSATIONS_TABLE, null, - values); - - Assert.isTrue(conversationRowId != -1); - if (conversationRowId == -1) { - LogUtil.e(TAG, "BugleDatabaseOperations : failed to insert conversation into table"); - return null; - } - - final String conversationId = Long.toString(conversationRowId); - - // Make sure that participants are added for this conversation - for (final ParticipantData participant : participants) { - // TODO: Use blocking information - addParticipantToConversation(dbWrapper, participant, conversationId); - } - - // Now fully resolved participants available can update conversation name / avatar. - // b/16437575: We cannot use the participants directly, but instead have to call - // getParticipantsForConversation() to retrieve the actual participants. This is needed - // because the call to addParticipantToConversation() won't fill up the ParticipantData - // if the participant already exists in the participant table. For example, say you have - // an existing conversation with John. Now if you create a new group conversation with - // Jeff & John with only their phone numbers, then when we try to add John's number to the - // group conversation, we see that he's already in the participant table, therefore we - // short-circuit any steps to actually fill out the ParticipantData for John other than - // just returning his participant id. Eventually, the ParticipantData we have is still the - // raw data with just the phone number. getParticipantsForConversation(), on the other - // hand, will fill out all the info for each participant from the participants table. - updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId, - getParticipantsForConversation(dbWrapper, conversationId)); - - return conversationId; - } - - private static void fillParticipantData(final ContentValues values, - final List participants) { - if (participants != null && !participants.isEmpty()) { - final Uri avatarUri = AvatarUriUtil.createAvatarUri(participants); - values.put(ConversationColumns.ICON, avatarUri.toString()); - - long contactId; - String lookupKey; - String destination; - if (participants.size() == 1) { - final ParticipantData firstParticipant = participants.get(0); - contactId = firstParticipant.getContactId(); - lookupKey = firstParticipant.getLookupKey(); - destination = firstParticipant.getNormalizedDestination(); - } else { - contactId = 0; - lookupKey = null; - destination = null; - } - - values.put(ConversationColumns.PARTICIPANT_CONTACT_ID, contactId); - values.put(ConversationColumns.PARTICIPANT_LOOKUP_KEY, lookupKey); - values.put(ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION, destination); - } - } - - /** - * Delete conversation and associated messages/parts - */ - @DoesNotRunOnMainThread - public static boolean deleteConversation(final DatabaseWrapper dbWrapper, - final String conversationId, final long cutoffTimestamp) { - Assert.isNotMainThread(); - dbWrapper.beginTransaction(); - boolean conversationDeleted = false; - boolean conversationMessagesDeleted = false; - try { - // Delete existing messages - if (cutoffTimestamp == Long.MAX_VALUE) { - // Delete parts and messages - dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE, - MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId }); - conversationMessagesDeleted = true; - } else { - // Delete all messages prior to the cutoff - dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE, - MessageColumns.CONVERSATION_ID + "=? AND " - + MessageColumns.RECEIVED_TIMESTAMP + "<=?", - new String[] { conversationId, Long.toString(cutoffTimestamp) }); - - // Delete any draft message. The delete above may not always include the draft, - // because under certain scenarios (e.g. sending messages in progress), the draft - // timestamp can be larger than the cutoff time, which is generally the conversation - // sort timestamp. Because of how the sms/mms provider works on some newer - // devices, it's important that we never delete all the messages in a conversation - // without also deleting the conversation itself (see b/20262204 for details). - dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE, - MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?", - new String[] { - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT), - conversationId - }); - - // Check to see if there are any messages left in the conversation - final long count = dbWrapper.queryNumEntries(DatabaseHelper.MESSAGES_TABLE, - MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId }); - conversationMessagesDeleted = (count == 0); - - // Log detail information if there are still messages left in the conversation - if (!conversationMessagesDeleted) { - final long maxTimestamp = - getConversationMaxTimestamp(dbWrapper, conversationId); - LogUtil.w(TAG, "BugleDatabaseOperations:" - + " cannot delete all messages in a conversation" - + ", after deletion: count=" + count - + ", max timestamp=" + maxTimestamp - + ", cutoff timestamp=" + cutoffTimestamp); - } - } - - if (conversationMessagesDeleted) { - // Delete conversation row - final int count = dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE, - ConversationColumns._ID + "=?", new String[] { conversationId }); - conversationDeleted = (count > 0); - } - dbWrapper.setTransactionSuccessful(); - } finally { - dbWrapper.endTransaction(); - } - return conversationDeleted; - } - - private static final String MAX_RECEIVED_TIMESTAMP = - "MAX(" + MessageColumns.RECEIVED_TIMESTAMP + ")"; - /** - * Get the max received timestamp of a conversation's messages - */ - private static long getConversationMaxTimestamp(final DatabaseWrapper dbWrapper, - final String conversationId) { - final Cursor cursor = dbWrapper.query( - DatabaseHelper.MESSAGES_TABLE, - new String[]{ MAX_RECEIVED_TIMESTAMP }, - MessageColumns.CONVERSATION_ID + "=?", - new String[]{ conversationId }, - null, null, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - return cursor.getLong(0); - } - } finally { - cursor.close(); - } - } - return 0; - } - - @DoesNotRunOnMainThread - public static void updateConversationMetadataInTransaction(final DatabaseWrapper dbWrapper, - final String conversationId, final String messageId, final long latestTimestamp, - final boolean keepArchived, final String smsServiceCenter, - final boolean shouldAutoSwitchSelfId) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - - final ContentValues values = new ContentValues(); - values.put(ConversationColumns.LATEST_MESSAGE_ID, messageId); - values.put(ConversationColumns.SORT_TIMESTAMP, latestTimestamp); - if (!TextUtils.isEmpty(smsServiceCenter)) { - values.put(ConversationColumns.SMS_SERVICE_CENTER, smsServiceCenter); - } - - // When the conversation gets updated with new messages, unarchive the conversation unless - // the sender is blocked, or we have been told to keep it archived. - if (!keepArchived) { - values.put(ConversationColumns.ARCHIVE_STATUS, 0); - } - - final MessageData message = readMessage(dbWrapper, messageId); - addSnippetTextAndPreviewToContentValues(message, false /* showDraft */, values); - - if (shouldAutoSwitchSelfId) { - addSelfIdAutoSwitchInfoToContentValues(dbWrapper, message, conversationId, values); - } - - // Conversation always exists as this method is called from ActionService only after - // reading and if necessary creating the conversation. - updateConversationRow(dbWrapper, conversationId, values); - - if (shouldAutoSwitchSelfId && OsUtil.isAtLeastL_MR1()) { - // Normally, the draft message compose UI trusts its UI state for providing up-to-date - // conversation self id. Therefore, notify UI through local broadcast receiver about - // this external change so the change can be properly reflected. - UIIntents.get().broadcastConversationSelfIdChange(dbWrapper.getContext(), - conversationId, getConversationSelfId(dbWrapper, conversationId)); - } - } - - @DoesNotRunOnMainThread - public static void updateConversationMetadataInTransaction(final DatabaseWrapper db, - final String conversationId, final String messageId, final long latestTimestamp, - final boolean keepArchived, final boolean shouldAutoSwitchSelfId) { - Assert.isNotMainThread(); - updateConversationMetadataInTransaction( - db, conversationId, messageId, latestTimestamp, keepArchived, null, - shouldAutoSwitchSelfId); - } - - @DoesNotRunOnMainThread - public static void updateConversationArchiveStatusInTransaction(final DatabaseWrapper dbWrapper, - final String conversationId, final boolean isArchived) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - final ContentValues values = new ContentValues(); - values.put(ConversationColumns.ARCHIVE_STATUS, isArchived ? 1 : 0); - updateConversationRowIfExists(dbWrapper, conversationId, values); - } - - static void addSnippetTextAndPreviewToContentValues(final MessageData message, - final boolean showDraft, final ContentValues values) { - values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0); - values.put(ConversationColumns.SNIPPET_TEXT, message.getMessageText()); - values.put(ConversationColumns.SUBJECT_TEXT, message.getMmsSubject()); - - String type = null; - String uriString = null; - for (final MessagePartData part : message.getParts()) { - if (part.isAttachment() && - ContentType.isConversationListPreviewableType(part.getContentType())) { - uriString = part.getContentUri().toString(); - type = part.getContentType(); - break; - } - } - values.put(ConversationColumns.PREVIEW_CONTENT_TYPE, type); - values.put(ConversationColumns.PREVIEW_URI, uriString); - } - - /** - * Adds self-id auto switch info for a conversation if the last message has a different - * subscription than the conversation's. - * @return true if self id will need to be changed, false otherwise. - */ - static boolean addSelfIdAutoSwitchInfoToContentValues(final DatabaseWrapper dbWrapper, - final MessageData message, final String conversationId, final ContentValues values) { - // Only auto switch conversation self for incoming messages. - if (!OsUtil.isAtLeastL_MR1() || !message.getIsIncoming()) { - return false; - } - - final String conversationSelfId = getConversationSelfId(dbWrapper, conversationId); - final String messageSelfId = message.getSelfId(); - - if (conversationSelfId == null || messageSelfId == null) { - return false; - } - - // Get the sub IDs in effect for both the message and the conversation and compare them: - // 1. If message is unbound (using default sub id), then the message was sent with - // pre-MSIM support. Don't auto-switch because we don't know the subscription for the - // message. - // 2. If message is bound, - // i. If conversation is unbound, use the system default sub id as its effective sub. - // ii. If conversation is bound, use its subscription directly. - // Compare the message sub id with the conversation's effective sub id. If they are - // different, auto-switch the conversation to the message's sub. - final ParticipantData conversationSelf = getExistingParticipant(dbWrapper, - conversationSelfId); - final ParticipantData messageSelf = getExistingParticipant(dbWrapper, messageSelfId); - if (!messageSelf.isActiveSubscription()) { - // Don't switch if the message subscription is no longer active. - return false; - } - final int messageSubId = messageSelf.getSubId(); - if (messageSubId == ParticipantData.DEFAULT_SELF_SUB_ID) { - return false; - } - - final int conversationEffectiveSubId = - PhoneUtils.getDefault().getEffectiveSubId(conversationSelf.getSubId()); - - if (conversationEffectiveSubId != messageSubId) { - return addConversationSelfIdToContentValues(dbWrapper, messageSelf.getId(), values); - } - return false; - } - - /** - * Adds conversation self id updates to ContentValues given. This performs check on the selfId - * to ensure it's valid and active. - * @return true if self id will need to be changed, false otherwise. - */ - static boolean addConversationSelfIdToContentValues(final DatabaseWrapper dbWrapper, - final String selfId, final ContentValues values) { - // Make sure the selfId passed in is valid and active. - final String selection = ParticipantColumns._ID + "=? AND " + - ParticipantColumns.SIM_SLOT_ID + "<>?"; - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - new String[] { ParticipantColumns._ID }, selection, - new String[] { selfId, String.valueOf(ParticipantData.INVALID_SLOT_ID) }, - null, null, null); - - if (cursor != null && cursor.getCount() > 0) { - values.put(ConversationColumns.CURRENT_SELF_ID, selfId); - return true; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return false; - } - - private static void updateConversationDraftSnippetAndPreviewInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId, - final MessageData draftMessage) { - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - - long sortTimestamp = 0L; - Cursor cursor = null; - try { - // Check to find the latest message in the conversation - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - REFRESH_CONVERSATION_MESSAGE_PROJECTION, - MessageColumns.CONVERSATION_ID + "=?", - new String[]{conversationId}, null, null, - MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */); - - if (cursor.moveToFirst()) { - sortTimestamp = cursor.getLong(1); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - - final ContentValues values = new ContentValues(); - if (draftMessage == null || !draftMessage.hasContent()) { - values.put(ConversationColumns.SHOW_DRAFT, 0); - values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, ""); - values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, ""); - values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, ""); - values.put(ConversationColumns.DRAFT_PREVIEW_URI, ""); - } else { - sortTimestamp = Math.max(sortTimestamp, draftMessage.getReceivedTimeStamp()); - values.put(ConversationColumns.SHOW_DRAFT, 1); - values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, draftMessage.getMessageText()); - values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, draftMessage.getMmsSubject()); - String type = null; - String uriString = null; - for (final MessagePartData part : draftMessage.getParts()) { - if (part.isAttachment() && - ContentType.isConversationListPreviewableType(part.getContentType())) { - uriString = part.getContentUri().toString(); - type = part.getContentType(); - break; - } - } - values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, type); - values.put(ConversationColumns.DRAFT_PREVIEW_URI, uriString); - } - values.put(ConversationColumns.SORT_TIMESTAMP, sortTimestamp); - // Called in transaction after reading conversation row - updateConversationRow(dbWrapper, conversationId, values); - } - - @DoesNotRunOnMainThread - public static boolean updateConversationRowIfExists(final DatabaseWrapper dbWrapper, - final String conversationId, final ContentValues values) { - Assert.isNotMainThread(); - return updateRowIfExists(dbWrapper, DatabaseHelper.CONVERSATIONS_TABLE, - ConversationColumns._ID, conversationId, values); - } - - @DoesNotRunOnMainThread - public static void updateConversationRow(final DatabaseWrapper dbWrapper, - final String conversationId, final ContentValues values) { - Assert.isNotMainThread(); - final boolean exists = updateConversationRowIfExists(dbWrapper, conversationId, values); - Assert.isTrue(exists); - } - - @DoesNotRunOnMainThread - public static boolean updateMessageRowIfExists(final DatabaseWrapper dbWrapper, - final String messageId, final ContentValues values) { - Assert.isNotMainThread(); - return updateRowIfExists(dbWrapper, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID, - messageId, values); - } - - @DoesNotRunOnMainThread - public static void updateMessageRow(final DatabaseWrapper dbWrapper, - final String messageId, final ContentValues values) { - Assert.isNotMainThread(); - final boolean exists = updateMessageRowIfExists(dbWrapper, messageId, values); - Assert.isTrue(exists); - } - - @DoesNotRunOnMainThread - public static boolean updatePartRowIfExists(final DatabaseWrapper dbWrapper, - final String partId, final ContentValues values) { - Assert.isNotMainThread(); - return updateRowIfExists(dbWrapper, DatabaseHelper.PARTS_TABLE, PartColumns._ID, - partId, values); - } - - /** - * Returns the default conversation name based on its participants. - */ - private static String getDefaultConversationName(final List participants) { - return ConversationListItemData.generateConversationName(participants); - } - - /** - * Updates a given conversation's name based on its participants. - */ - @DoesNotRunOnMainThread - public static void updateConversationNameAndAvatarInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - - final ArrayList participants = - getParticipantsForConversation(dbWrapper, conversationId); - updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId, participants); - } - - /** - * Updates a given conversation's name based on its participants. - */ - private static void updateConversationNameAndAvatarInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId, - final List participants) { - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - - final ContentValues values = new ContentValues(); - values.put(ConversationColumns.NAME, - getDefaultConversationName(participants)); - - fillParticipantData(values, participants); - - // Used by background thread when refreshing conversation so conversation could be deleted. - updateConversationRowIfExists(dbWrapper, conversationId, values); - - WidgetConversationProvider.notifyConversationRenamed(Factory.get().getApplicationContext(), - conversationId); - } - - /** - * Updates a given conversation's self id. - */ - @DoesNotRunOnMainThread - public static void updateConversationSelfIdInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId, final String selfId) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - final ContentValues values = new ContentValues(); - if (addConversationSelfIdToContentValues(dbWrapper, selfId, values)) { - updateConversationRowIfExists(dbWrapper, conversationId, values); - } - } - - @DoesNotRunOnMainThread - public static String getConversationSelfId(final DatabaseWrapper dbWrapper, - final String conversationId) { - Assert.isNotMainThread(); - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns.CURRENT_SELF_ID }, - ConversationColumns._ID + "=?", - new String[] { conversationId }, - null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - return cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - /** - * Frees up memory associated with phone number to participant id matching. - */ - @DoesNotRunOnMainThread - public static void clearParticipantIdCache() { - Assert.isNotMainThread(); - synchronized (sNormalizedPhoneNumberToParticipantIdCache) { - sNormalizedPhoneNumberToParticipantIdCache.clear(); - } - } - - @DoesNotRunOnMainThread - public static ArrayList getRecipientsForConversation(final DatabaseWrapper dbWrapper, - final String conversationId) { - Assert.isNotMainThread(); - final ArrayList participants = - getParticipantsForConversation(dbWrapper, conversationId); - - final ArrayList recipients = new ArrayList(); - for (final ParticipantData participant : participants) { - recipients.add(participant.getSendDestination()); - } - - return recipients; - } - - @DoesNotRunOnMainThread - public static String getSmsServiceCenterForConversation(final DatabaseWrapper dbWrapper, - final String conversationId) { - Assert.isNotMainThread(); - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns.SMS_SERVICE_CENTER }, - ConversationColumns._ID + "=?", - new String[] { conversationId }, - null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - return cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - @DoesNotRunOnMainThread - public static ParticipantData getExistingParticipant(final DatabaseWrapper dbWrapper, - final String participantId) { - Assert.isNotMainThread(); - ParticipantData participant = null; - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantData.ParticipantsQuery.PROJECTION, - ParticipantColumns._ID + " =?", - new String[] { participantId }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - participant = ParticipantData.getFromCursor(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return participant; - } - - static int getSelfSubscriptionId(final DatabaseWrapper dbWrapper, - final String selfParticipantId) { - final ParticipantData selfParticipant = BugleDatabaseOperations.getExistingParticipant( - dbWrapper, selfParticipantId); - if (selfParticipant != null) { - Assert.isTrue(selfParticipant.isSelf()); - return selfParticipant.getSubId(); - } - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @VisibleForTesting - @DoesNotRunOnMainThread - public static ArrayList getParticipantsForConversation( - final DatabaseWrapper dbWrapper, final String conversationId) { - Assert.isNotMainThread(); - final ArrayList participants = - new ArrayList(); - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantData.ParticipantsQuery.PROJECTION, - ParticipantColumns._ID + " IN ( " + "SELECT " - + ConversationParticipantsColumns.PARTICIPANT_ID + " AS " - + ParticipantColumns._ID - + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE - + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID + " =? )", - new String[] { conversationId }, null, null, null); - - while (cursor.moveToNext()) { - participants.add(ParticipantData.getFromCursor(cursor)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return participants; - } - - @DoesNotRunOnMainThread - public static MessageData readMessage(final DatabaseWrapper dbWrapper, final String messageId) { - Assert.isNotMainThread(); - final MessageData message = readMessageData(dbWrapper, messageId); - if (message != null) { - readMessagePartsData(dbWrapper, message, false); - } - return message; - } - - @VisibleForTesting - static MessagePartData readMessagePartData(final DatabaseWrapper dbWrapper, - final String partId) { - MessagePartData messagePartData = null; - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE, - MessagePartData.getProjection(), PartColumns._ID + "=?", - new String[] { partId }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - messagePartData = MessagePartData.createFromCursor(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return messagePartData; - } - - @DoesNotRunOnMainThread - public static MessageData readMessageData(final DatabaseWrapper dbWrapper, - final Uri smsMessageUri) { - Assert.isNotMainThread(); - MessageData message = null; - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), MessageColumns.SMS_MESSAGE_URI + "=?", - new String[] { smsMessageUri.toString() }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - message = new MessageData(); - message.bind(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return message; - } - - @DoesNotRunOnMainThread - public static MessageData readMessageData(final DatabaseWrapper dbWrapper, - final String messageId) { - Assert.isNotMainThread(); - MessageData message = null; - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), MessageColumns._ID + "=?", - new String[] { messageId }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - message = new MessageData(); - message.bind(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return message; - } - - /** - * Read all the parts for a message - * @param dbWrapper database - * @param message read parts for this message - * @param checkAttachmentFilesExist check each attachment file and only include if file exists - */ - private static void readMessagePartsData(final DatabaseWrapper dbWrapper, - final MessageData message, final boolean checkAttachmentFilesExist) { - final ContentResolver contentResolver = - Factory.get().getApplicationContext().getContentResolver(); - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE, - MessagePartData.getProjection(), PartColumns.MESSAGE_ID + "=?", - new String[] { message.getMessageId() }, null, null, null); - while (cursor.moveToNext()) { - final MessagePartData messagePartData = MessagePartData.createFromCursor(cursor); - if (checkAttachmentFilesExist && messagePartData.isAttachment() && - !UriUtil.isBugleAppResource(messagePartData.getContentUri())) { - try { - // Test that the file exists before adding the attachment to the draft - final ParcelFileDescriptor fileDescriptor = - contentResolver.openFileDescriptor( - messagePartData.getContentUri(), "r"); - if (fileDescriptor != null) { - fileDescriptor.close(); - message.addPart(messagePartData); - } - } catch (final IOException e) { - // The attachment's temp storage no longer exists, just ignore the file - } catch (final SecurityException e) { - // Likely thrown by openFileDescriptor due to an expired access grant. - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "uri: " + messagePartData.getContentUri()); - } - } - } else { - message.addPart(messagePartData); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** - * Write a message part to our local database - * - * @param dbWrapper The database - * @param messagePart The message part to insert - * @return The row id of the newly inserted part - */ - static String insertNewMessagePartInTransaction(final DatabaseWrapper dbWrapper, - final MessagePartData messagePart, final String conversationId) { - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - Assert.isTrue(!TextUtils.isEmpty(messagePart.getMessageId())); - - // Insert a new part row - final SQLiteStatement insert = messagePart.getInsertStatement(dbWrapper, conversationId); - final long rowNumber = insert.executeInsert(); - - Assert.inRange(rowNumber, 0, Long.MAX_VALUE); - final String partId = Long.toString(rowNumber); - - // Update the part id - messagePart.updatePartId(partId); - - return partId; - } - - /** - * Insert a message and its parts into the table - */ - @DoesNotRunOnMainThread - public static void insertNewMessageInTransaction(final DatabaseWrapper dbWrapper, - final MessageData message) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - - // Insert message row - final SQLiteStatement insert = message.getInsertStatement(dbWrapper); - final long rowNumber = insert.executeInsert(); - - Assert.inRange(rowNumber, 0, Long.MAX_VALUE); - final String messageId = Long.toString(rowNumber); - message.updateMessageId(messageId); - // Insert new parts - for (final MessagePartData messagePart : message.getParts()) { - messagePart.updateMessageId(messageId); - insertNewMessagePartInTransaction(dbWrapper, messagePart, message.getConversationId()); - } - } - - /** - * Update a message and add its parts into the table - */ - @DoesNotRunOnMainThread - public static void updateMessageInTransaction(final DatabaseWrapper dbWrapper, - final MessageData message) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - final String messageId = message.getMessageId(); - // Check message still exists (sms sync or delete might have purged it) - final MessageData current = BugleDatabaseOperations.readMessage(dbWrapper, messageId); - if (current != null) { - // Delete existing message parts) - deletePartsForMessage(dbWrapper, message.getMessageId()); - // Insert new parts - for (final MessagePartData messagePart : message.getParts()) { - messagePart.updatePartId(null); - messagePart.updateMessageId(message.getMessageId()); - insertNewMessagePartInTransaction(dbWrapper, messagePart, - message.getConversationId()); - } - // Update message row - final ContentValues values = new ContentValues(); - message.populate(values); - updateMessageRowIfExists(dbWrapper, message.getMessageId(), values); - } - } - - @DoesNotRunOnMainThread - public static void updateMessageAndPartsInTransaction(final DatabaseWrapper dbWrapper, - final MessageData message, final List partsToUpdate) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - final ContentValues values = new ContentValues(); - for (final MessagePartData messagePart : partsToUpdate) { - values.clear(); - messagePart.populate(values); - updatePartRowIfExists(dbWrapper, messagePart.getPartId(), values); - } - values.clear(); - message.populate(values); - updateMessageRowIfExists(dbWrapper, message.getMessageId(), values); - } - - /** - * Delete all parts for a message - */ - static void deletePartsForMessage(final DatabaseWrapper dbWrapper, - final String messageId) { - final int cnt = dbWrapper.delete(DatabaseHelper.PARTS_TABLE, - PartColumns.MESSAGE_ID + " =?", - new String[] { messageId }); - Assert.inRange(cnt, 0, Integer.MAX_VALUE); - } - - /** - * Delete one message and update the conversation (if necessary). - * - * @return number of rows deleted (should be 1 or 0). - */ - @DoesNotRunOnMainThread - public static int deleteMessage(final DatabaseWrapper dbWrapper, final String messageId) { - Assert.isNotMainThread(); - dbWrapper.beginTransaction(); - try { - // Read message to find out which conversation it is in - final MessageData message = BugleDatabaseOperations.readMessage(dbWrapper, messageId); - - int count = 0; - if (message != null) { - final String conversationId = message.getConversationId(); - // Delete message - count = dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE, - MessageColumns._ID + "=?", new String[] { messageId }); - - if (!deleteConversationIfEmptyInTransaction(dbWrapper, conversationId)) { - // TODO: Should we leave the conversation sort timestamp alone? - refreshConversationMetadataInTransaction(dbWrapper, conversationId, - false/* shouldAutoSwitchSelfId */, false/*archived*/); - } - } - dbWrapper.setTransactionSuccessful(); - return count; - } finally { - dbWrapper.endTransaction(); - } - } - - /** - * Deletes the conversation if there are zero non-draft messages left. - *

- * This is necessary because the telephony database has a trigger that deletes threads after - * their last message is deleted. We need to ensure that if a thread goes away, we also delete - * the conversation in Bugle. We don't store draft messages in telephony, so we ignore those - * when querying for the # of messages in the conversation. - * - * @return true if the conversation was deleted - */ - @DoesNotRunOnMainThread - public static boolean deleteConversationIfEmptyInTransaction(final DatabaseWrapper dbWrapper, - final String conversationId) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - Cursor cursor = null; - try { - // TODO: The refreshConversationMetadataInTransaction method below uses this - // same query; maybe they should share this logic? - - // Check to see if there are any (non-draft) messages in the conversation - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - REFRESH_CONVERSATION_MESSAGE_PROJECTION, - MessageColumns.CONVERSATION_ID + "=? AND " + - MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT, - new String[] { conversationId }, null, null, - MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */); - if (cursor.getCount() == 0) { - dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE, - ConversationColumns._ID + "=?", new String[] { conversationId }); - LogUtil.i(TAG, - "BugleDatabaseOperations: Deleted empty conversation " + conversationId); - return true; - } else { - return false; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - private static final String[] REFRESH_CONVERSATION_MESSAGE_PROJECTION = new String[] { - MessageColumns._ID, - MessageColumns.RECEIVED_TIMESTAMP, - MessageColumns.SENDER_PARTICIPANT_ID - }; - - /** - * Update conversation snippet, timestamp and optionally self id to match latest message in - * conversation. - */ - @DoesNotRunOnMainThread - public static void refreshConversationMetadataInTransaction(final DatabaseWrapper dbWrapper, - final String conversationId, final boolean shouldAutoSwitchSelfId, - boolean keepArchived) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - Cursor cursor = null; - try { - // Check to see if there are any (non-draft) messages in the conversation - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - REFRESH_CONVERSATION_MESSAGE_PROJECTION, - MessageColumns.CONVERSATION_ID + "=? AND " + - MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT, - new String[] { conversationId }, null, null, - MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */); - - if (cursor.moveToFirst()) { - // Refresh latest message in conversation - final String latestMessageId = cursor.getString(0); - final long latestMessageTimestamp = cursor.getLong(1); - final String senderParticipantId = cursor.getString(2); - final boolean senderBlocked = isBlockedParticipant(dbWrapper, senderParticipantId); - updateConversationMetadataInTransaction(dbWrapper, conversationId, - latestMessageId, latestMessageTimestamp, senderBlocked || keepArchived, - shouldAutoSwitchSelfId); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** - * When moving/removing an existing message update conversation metadata if necessary - * @param dbWrapper db wrapper - * @param conversationId conversation to modify - * @param messageId message that is leaving the conversation - * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a - * result of this call when we see a new latest message? - * @param keepArchived should we keep the conversation archived despite refresh - */ - @DoesNotRunOnMainThread - public static void maybeRefreshConversationMetadataInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId, final String messageId, - final boolean shouldAutoSwitchSelfId, final boolean keepArchived) { - Assert.isNotMainThread(); - boolean refresh = true; - if (!TextUtils.isEmpty(messageId)) { - refresh = false; - // Look for an existing conversation in the db with this conversation id - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns.LATEST_MESSAGE_ID }, - ConversationColumns._ID + "=?", - new String[] { conversationId }, - null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - refresh = TextUtils.equals(cursor.getString(0), messageId); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - if (refresh) { - // TODO: I think it is okay to delete the conversation if it is empty... - refreshConversationMetadataInTransaction(dbWrapper, conversationId, - shouldAutoSwitchSelfId, keepArchived); - } - } - - - - // SQL statement to query latest message if for particular conversation - private static final String QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL = "SELECT " - + ConversationColumns.LATEST_MESSAGE_ID + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE - + " WHERE " + ConversationColumns._ID + "=? LIMIT 1"; - - /** - * Note this is not thread safe so callers need to make sure they own the wrapper + statements - * while they call this and use the returned value. - */ - @DoesNotRunOnMainThread - public static SQLiteStatement getQueryConversationsLatestMessageStatement( - final DatabaseWrapper db, final String conversationId) { - Assert.isNotMainThread(); - final SQLiteStatement query = db.getStatementInTransaction( - DatabaseWrapper.INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE, - QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL); - query.clearBindings(); - query.bindString(1, conversationId); - return query; - } - - // SQL statement to query latest message if for particular conversation - private static final String QUERY_MESSAGES_LATEST_MESSAGE_SQL = "SELECT " - + MessageColumns._ID + " FROM " + DatabaseHelper.MESSAGES_TABLE - + " WHERE " + MessageColumns.CONVERSATION_ID + "=? ORDER BY " - + MessageColumns.RECEIVED_TIMESTAMP + " DESC LIMIT 1"; - - /** - * Note this is not thread safe so callers need to make sure they own the wrapper + statements - * while they call this and use the returned value. - */ - @DoesNotRunOnMainThread - public static SQLiteStatement getQueryMessagesLatestMessageStatement( - final DatabaseWrapper db, final String conversationId) { - Assert.isNotMainThread(); - final SQLiteStatement query = db.getStatementInTransaction( - DatabaseWrapper.INDEX_QUERY_MESSAGES_LATEST_MESSAGE, - QUERY_MESSAGES_LATEST_MESSAGE_SQL); - query.clearBindings(); - query.bindString(1, conversationId); - return query; - } - - /** - * Update conversation metadata if necessary - * @param dbWrapper db wrapper - * @param conversationId conversation to modify - * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a - * result of this call when we see a new latest message? - * @param keepArchived if the conversation should be kept archived - */ - @DoesNotRunOnMainThread - public static void maybeRefreshConversationMetadataInTransaction( - final DatabaseWrapper dbWrapper, final String conversationId, - final boolean shouldAutoSwitchSelfId, boolean keepArchived) { - Assert.isNotMainThread(); - String currentLatestMessageId = null; - String latestMessageId = null; - try { - final SQLiteStatement currentLatestMessageIdSql = - getQueryConversationsLatestMessageStatement(dbWrapper, conversationId); - currentLatestMessageId = currentLatestMessageIdSql.simpleQueryForString(); - - final SQLiteStatement latestMessageIdSql = - getQueryMessagesLatestMessageStatement(dbWrapper, conversationId); - latestMessageId = latestMessageIdSql.simpleQueryForString(); - } catch (final SQLiteDoneException e) { - LogUtil.e(TAG, "BugleDatabaseOperations: Query for latest message failed", e); - } - - if (TextUtils.isEmpty(currentLatestMessageId) || - !TextUtils.equals(currentLatestMessageId, latestMessageId)) { - refreshConversationMetadataInTransaction(dbWrapper, conversationId, - shouldAutoSwitchSelfId, keepArchived); - } - } - - static boolean getConversationExists(final DatabaseWrapper dbWrapper, - final String conversationId) { - // Look for an existing conversation in the db with this conversation id - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { /* No projection */}, - ConversationColumns._ID + "=?", - new String[] { conversationId }, - null, null, null); - return cursor.getCount() == 1; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** Preserve parts in message but clear the stored draft */ - public static final int UPDATE_MODE_CLEAR_DRAFT = 1; - /** Add the message as a draft */ - public static final int UPDATE_MODE_ADD_DRAFT = 2; - - /** - * Update draft message for specified conversation - * @param dbWrapper local database (wrapped) - * @param conversationId conversation to update - * @param message Optional message to preserve attachments for (either as draft or for - * sending) - * @param updateMode either {@link #UPDATE_MODE_CLEAR_DRAFT} or - * {@link #UPDATE_MODE_ADD_DRAFT} - * @return message id of newly written draft (else null) - */ - @DoesNotRunOnMainThread - public static String updateDraftMessageData(final DatabaseWrapper dbWrapper, - final String conversationId, @Nullable final MessageData message, - final int updateMode) { - Assert.isNotMainThread(); - Assert.notNull(conversationId); - Assert.inRange(updateMode, UPDATE_MODE_CLEAR_DRAFT, UPDATE_MODE_ADD_DRAFT); - String messageId = null; - Cursor cursor = null; - dbWrapper.beginTransaction(); - try { - // Find all draft parts for the current conversation - final SimpleArrayMap currentDraftParts = new SimpleArrayMap<>(); - cursor = dbWrapper.query(DatabaseHelper.DRAFT_PARTS_VIEW, - MessagePartData.getProjection(), - MessageColumns.CONVERSATION_ID + " =?", - new String[] { conversationId }, null, null, null); - while (cursor.moveToNext()) { - final MessagePartData part = MessagePartData.createFromCursor(cursor); - if (part.isAttachment()) { - currentDraftParts.put(part.getContentUri(), part); - } - } - // Optionally, preserve attachments for "message" - final boolean conversationExists = getConversationExists(dbWrapper, conversationId); - if (message != null && conversationExists) { - for (final MessagePartData part : message.getParts()) { - if (part.isAttachment()) { - currentDraftParts.remove(part.getContentUri()); - } - } - } - - // Delete orphan content - for (int index = 0; index < currentDraftParts.size(); index++) { - final MessagePartData part = currentDraftParts.valueAt(index); - part.destroySync(); - } - - // Delete existing draft (cascade deletes parts) - dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE, - MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?", - new String[] { - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT), - conversationId - }); - - // Write new draft - if (updateMode == UPDATE_MODE_ADD_DRAFT && message != null - && message.hasContent() && conversationExists) { - Assert.equals(MessageData.BUGLE_STATUS_OUTGOING_DRAFT, - message.getStatus()); - - // Now add draft to message table - insertNewMessageInTransaction(dbWrapper, message); - messageId = message.getMessageId(); - } - - if (conversationExists) { - updateConversationDraftSnippetAndPreviewInTransaction( - dbWrapper, conversationId, message); - - if (message != null && message.getSelfId() != null) { - updateConversationSelfIdInTransaction(dbWrapper, conversationId, - message.getSelfId()); - } - } - - dbWrapper.setTransactionSuccessful(); - } finally { - dbWrapper.endTransaction(); - if (cursor != null) { - cursor.close(); - } - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, - "Updated draft message " + messageId + " for conversation " + conversationId); - } - return messageId; - } - - /** - * Read the first draft message associated with this conversation. - * If none present create an empty (sms) draft message. - */ - @DoesNotRunOnMainThread - public static MessageData readDraftMessageData(final DatabaseWrapper dbWrapper, - final String conversationId, final String conversationSelfId) { - Assert.isNotMainThread(); - MessageData message = null; - Cursor cursor = null; - dbWrapper.beginTransaction(); - try { - cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), - MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?", - new String[] { - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT), - conversationId - }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - message = new MessageData(); - message.bindDraft(cursor, conversationSelfId); - readMessagePartsData(dbWrapper, message, true); - // Disconnect draft parts from DB - for (final MessagePartData part : message.getParts()) { - part.updatePartId(null); - part.updateMessageId(null); - } - message.updateMessageId(null); - } - dbWrapper.setTransactionSuccessful(); - } finally { - dbWrapper.endTransaction(); - if (cursor != null) { - cursor.close(); - } - } - return message; - } - - // Internal - private static void addParticipantToConversation(final DatabaseWrapper dbWrapper, - final ParticipantData participant, final String conversationId) { - final String participantId = getOrCreateParticipantInTransaction(dbWrapper, participant); - Assert.notNull(participantId); - - // Add the participant to the conversation participants table - final ContentValues values = new ContentValues(); - values.put(ConversationParticipantsColumns.CONVERSATION_ID, conversationId); - values.put(ConversationParticipantsColumns.PARTICIPANT_ID, participantId); - dbWrapper.insert(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE, null, values); - } - - /** - * Get string used as canonical recipient for participant cache for sub id - */ - private static String getCanonicalRecipientFromSubId(final int subId) { - return "SELF(" + subId + ")"; - } - - /** - * Maps from a sub id or phone number to a participant id if there is one. - * - * @return If the participant is available in our cache, or the DB, this returns the - * participant id for the given subid/phone number. Otherwise it returns null. - */ - @VisibleForTesting - private static String getParticipantId(final DatabaseWrapper dbWrapper, - final int subId, final String canonicalRecipient) { - // First check our memory cache for the participant Id - String participantId; - synchronized (sNormalizedPhoneNumberToParticipantIdCache) { - participantId = sNormalizedPhoneNumberToParticipantIdCache.get(canonicalRecipient); - } - - if (participantId != null) { - return participantId; - } - - // This code will only be executed for incremental additions. - Cursor cursor = null; - try { - if (subId != ParticipantData.OTHER_THAN_SELF_SUB_ID) { - // Now look for an existing participant in the db with this sub id. - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - new String[] {ParticipantColumns._ID}, - ParticipantColumns.SUB_ID + "=?", - new String[] { Integer.toString(subId) }, null, null, null); - } else { - // Look for existing participant with this normalized phone number and no subId. - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - new String[] {ParticipantColumns._ID}, - ParticipantColumns.NORMALIZED_DESTINATION + "=? AND " - + ParticipantColumns.SUB_ID + "=?", - new String[] {canonicalRecipient, Integer.toString(subId)}, - null, null, null); - } - - if (cursor.moveToFirst()) { - // TODO Is this assert correct for multi-sim where a new sim was put in? - Assert.isTrue(cursor.getCount() == 1); - - // We found an existing participant in the database - participantId = cursor.getString(0); - - synchronized (sNormalizedPhoneNumberToParticipantIdCache) { - // Add it to the cache for next time - sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient, - participantId); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return participantId; - } - - @DoesNotRunOnMainThread - public static ParticipantData getOrCreateSelf(final DatabaseWrapper dbWrapper, - final int subId) { - Assert.isNotMainThread(); - ParticipantData participant = null; - dbWrapper.beginTransaction(); - try { - final ParticipantData shell = ParticipantData.getSelfParticipant(subId); - final String participantId = getOrCreateParticipantInTransaction(dbWrapper, shell); - participant = getExistingParticipant(dbWrapper, participantId); - dbWrapper.setTransactionSuccessful(); - } finally { - dbWrapper.endTransaction(); - } - return participant; - } - - /** - * Lookup and if necessary create a new participant - * @param dbWrapper Database wrapper - * @param participant Participant to find/create - * @return participantId ParticipantId for existing or newly created participant - */ - @DoesNotRunOnMainThread - public static String getOrCreateParticipantInTransaction(final DatabaseWrapper dbWrapper, - final ParticipantData participant) { - Assert.isNotMainThread(); - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - int subId = ParticipantData.OTHER_THAN_SELF_SUB_ID; - String participantId = null; - String canonicalRecipient = null; - if (participant.isSelf()) { - subId = participant.getSubId(); - canonicalRecipient = getCanonicalRecipientFromSubId(subId); - } else { - canonicalRecipient = participant.getNormalizedDestination(); - } - Assert.notNull(canonicalRecipient); - participantId = getParticipantId(dbWrapper, subId, canonicalRecipient); - - if (participantId != null) { - return participantId; - } - - if (!participant.isContactIdResolved()) { - // Refresh participant's name and avatar with matching contact in CP2. - ParticipantRefresh.refreshParticipant(dbWrapper, participant); - } - - // Insert the participant into the participants table - final ContentValues values = participant.toContentValues(); - final long participantRow = dbWrapper.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, - values); - participantId = Long.toString(participantRow); - Assert.notNull(canonicalRecipient); - - synchronized (sNormalizedPhoneNumberToParticipantIdCache) { - // Now that we've inserted it, add it to our cache - sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient, participantId); - } - - return participantId; - } - - @DoesNotRunOnMainThread - public static void updateDestination(final DatabaseWrapper dbWrapper, - final String destination, final boolean blocked) { - Assert.isNotMainThread(); - final ContentValues values = new ContentValues(); - values.put(ParticipantColumns.BLOCKED, blocked ? 1 : 0); - dbWrapper.update(DatabaseHelper.PARTICIPANTS_TABLE, values, - ParticipantColumns.NORMALIZED_DESTINATION + "=? AND " + - ParticipantColumns.SUB_ID + "=?", - new String[] { destination, Integer.toString( - ParticipantData.OTHER_THAN_SELF_SUB_ID) }); - } - - @DoesNotRunOnMainThread - public static String getConversationFromOtherParticipantDestination( - final DatabaseWrapper db, final String otherDestination) { - Assert.isNotMainThread(); - Cursor cursor = null; - try { - cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns._ID }, - ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + "=?", - new String[] { otherDestination }, null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - return cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - - /** - * Get a list of conversations that contain any of participants specified. - */ - private static HashSet getConversationsForParticipants( - final ArrayList participantIds) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final HashSet conversationIds = new HashSet(); - - final String selection = ConversationParticipantsColumns.PARTICIPANT_ID + "=?"; - for (final String participantId : participantIds) { - final String[] selectionArgs = new String[] { participantId }; - final Cursor cursor = db.query(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE, - ConversationParticipantsQuery.PROJECTION, - selection, selectionArgs, null, null, null); - - if (cursor != null) { - try { - while (cursor.moveToNext()) { - final String conversationId = cursor.getString( - ConversationParticipantsQuery.INDEX_CONVERSATION_ID); - conversationIds.add(conversationId); - } - } finally { - cursor.close(); - } - } - } - - return conversationIds; - } - - /** - * Refresh conversation names/avatars based on a list of participants that are changed. - */ - @DoesNotRunOnMainThread - public static void refreshConversationsForParticipants(final ArrayList participants) { - Assert.isNotMainThread(); - final HashSet conversationIds = getConversationsForParticipants(participants); - if (conversationIds.size() > 0) { - for (final String conversationId : conversationIds) { - refreshConversation(conversationId); - } - - MessagingContentProvider.notifyConversationListChanged(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Number of conversations refreshed:" + conversationIds.size()); - } - } - } - - /** - * Refresh conversation names/avatars based on a changed participant. - */ - @DoesNotRunOnMainThread - public static void refreshConversationsForParticipant(final String participantId) { - Assert.isNotMainThread(); - final ArrayList participantList = new ArrayList(1); - participantList.add(participantId); - refreshConversationsForParticipants(participantList); - } - - /** - * Refresh one conversation. - */ - private static void refreshConversation(final String conversationId) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - db.beginTransaction(); - try { - BugleDatabaseOperations.updateConversationNameAndAvatarInTransaction(db, - conversationId); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - MessagingContentProvider.notifyParticipantsChanged(conversationId); - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyConversationMetadataChanged(conversationId); - } - - @DoesNotRunOnMainThread - public static boolean updateRowIfExists(final DatabaseWrapper db, final String table, - final String rowKey, final String rowId, final ContentValues values) { - Assert.isNotMainThread(); - final StringBuilder sb = new StringBuilder(); - final ArrayList whereValues = new ArrayList(values.size() + 1); - whereValues.add(rowId); - - for (final String key : values.keySet()) { - if (sb.length() > 0) { - sb.append(" OR "); - } - final Object value = values.get(key); - sb.append(key); - if (value != null) { - sb.append(" IS NOT ?"); - whereValues.add(value.toString()); - } else { - sb.append(" IS NOT NULL"); - } - } - - final String whereClause = rowKey + "=?" + " AND (" + sb.toString() + ")"; - final String [] whereValuesArray = whereValues.toArray(new String[whereValues.size()]); - final int count = db.update(table, values, whereClause, whereValuesArray); - if (count > 1) { - LogUtil.w(LogUtil.BUGLE_TAG, "Updated more than 1 row " + count + "; " + table + - " for " + rowKey + " = " + rowId + " (deleted?)"); - } - Assert.inRange(count, 0, 1); - return (count >= 0); - } -} diff --git a/src/com/android/messaging/datamodel/BugleNotifications.java b/src/com/android/messaging/datamodel/BugleNotifications.java deleted file mode 100644 index b796e73..0000000 --- a/src/com/android/messaging/datamodel/BugleNotifications.java +++ /dev/null @@ -1,1221 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.Typeface; -import android.media.AudioManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.SystemClock; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationCompat.WearableExtender; -import android.support.v4.app.NotificationManagerCompat; -import android.support.v4.app.RemoteInput; -import android.support.v4.util.SimpleArrayMap; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.text.style.StyleSpan; -import android.text.style.TextAppearanceSpan; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.MessageNotificationState.BundledMessageNotificationState; -import com.android.messaging.datamodel.MessageNotificationState.ConversationLineInfo; -import com.android.messaging.datamodel.MessageNotificationState.MultiConversationNotificationState; -import com.android.messaging.datamodel.MessageNotificationState.MultiMessageNotificationState; -import com.android.messaging.datamodel.action.MarkAsReadAction; -import com.android.messaging.datamodel.action.MarkAsSeenAction; -import com.android.messaging.datamodel.action.RedownloadMmsAction; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.media.AvatarRequestDescriptor; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.datamodel.media.VideoThumbnailRequest; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ConversationIdSet; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.NotificationPlayer; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PendingIntentConstants; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.RingtoneUtil; -import com.android.messaging.util.ThreadUtil; -import com.android.messaging.util.UriUtil; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -/** - * Handle posting, updating and removing all conversation notifications. - * - * There are currently two main classes of notification and their rules:

- * 1) Messages - {@link MessageNotificationState}. Only one message notification. - * Unread messages across senders and conversations are coalesced.

- * 2) Failed Messages - {@link MessageNotificationState#checkFailedMesages } Only one failed - * message. Multiple failures are coalesced.

- * - * To add a new class of notifications, subclass the NotificationState and add commands which - * create one and pass into general creation function. - * - */ -public class BugleNotifications { - // Logging - public static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG; - - // Constants to use for update. - public static final int UPDATE_NONE = 0; - public static final int UPDATE_MESSAGES = 1; - public static final int UPDATE_ERRORS = 2; - public static final int UPDATE_ALL = UPDATE_MESSAGES + UPDATE_ERRORS; - - // Constants for notification type used for audio and vibration settings. - public static final int LOCAL_SMS_NOTIFICATION = 0; - - private static final String SMS_NOTIFICATION_TAG = ":sms:"; - private static final String SMS_ERROR_NOTIFICATION_TAG = ":error:"; - - private static final String WEARABLE_COMPANION_APP_PACKAGE = "com.google.android.wearable.app"; - - private static final Set sPendingNotifications = - new HashSet(); - - private static int sWearableImageWidth; - private static int sWearableImageHeight; - private static int sIconWidth; - private static int sIconHeight; - - private static boolean sInitialized = false; - - private static final Object mLock = new Object(); - - // sLastMessageDingTime is a map between a conversation id and a time. It's used to keep track - // of the time we last dinged a message for this conversation. When messages are coming in - // at flurry, we don't want to over-ding the user. - private static final SimpleArrayMap sLastMessageDingTime = - new SimpleArrayMap(); - private static int sTimeBetweenDingsMs; - - /** - * This is the volume at which to play the observable-conversation notification sound, - * expressed as a fraction of the system notification volume. - */ - private static final float OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME = 0.25f; - - /** - * Entry point for posting notifications. - * Don't call this on the UI thread. - * @param silent If true, no ring will be played. If false, checks global settings before - * playing a ringtone - * @param coverage Indicates which notification types should be checked. Valid values are - * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL - */ - public static void update(final boolean silent, final int coverage) { - update(silent, null /* conversationId */, coverage); - } - - /** - * Entry point for posting notifications. - * Don't call this on the UI thread. - * @param silent If true, no ring will be played. If false, checks global settings before - * playing a ringtone - * @param conversationId Conversation ID where a new message was received - * @param coverage Indicates which notification types should be checked. Valid values are - * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL - */ - public static void update(final boolean silent, final String conversationId, - final int coverage) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Update: silent = " + silent - + " conversationId = " + conversationId - + " coverage = " + coverage); - } - Assert.isNotMainThread(); - checkInitialized(); - - if (!shouldNotify()) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Notifications disabled"); - } - cancel(PendingIntentConstants.SMS_NOTIFICATION_ID); - return; - } else { - if ((coverage & UPDATE_MESSAGES) != 0) { - createMessageNotification(silent, conversationId); - } - } - if ((coverage & UPDATE_ERRORS) != 0) { - MessageNotificationState.checkFailedMessages(); - } - } - - /** - * Cancel all notifications of a certain type. - * - * @param type Message or error notifications from Constants. - */ - private static synchronized void cancel(final int type) { - cancel(type, null, false); - } - - /** - * Cancel all notifications of a certain type. - * - * @param type Message or error notifications from Constants. - * @param conversationId If set, cancel the notification for this - * conversation only. For message notifications, this only works - * if the notifications are bundled (group children). - * @param isBundledNotification True if this notification is part of a - * notification bundle. This only applies to message notifications, - * which are bundled together with other message notifications. - */ - private static synchronized void cancel(final int type, final String conversationId, - final boolean isBundledNotification) { - final String notificationTag = buildNotificationTag(type, conversationId, - isBundledNotification); - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - - // Find all pending notifications and cancel them. - synchronized (sPendingNotifications) { - final Iterator iter = sPendingNotifications.iterator(); - while (iter.hasNext()) { - final NotificationState notifState = iter.next(); - if (notifState.mType == type) { - notifState.mCanceled = true; - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Canceling pending notification"); - } - iter.remove(); - } - } - } - notificationManager.cancel(notificationTag, type); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Canceled notifications of type " + type); - } - - // Message notifications for multiple conversations can be grouped together (see comment in - // createMessageNotification). We need to do bookkeeping to track the current set of - // notification group children, including removing them when we cancel notifications). - if (type == PendingIntentConstants.SMS_NOTIFICATION_ID) { - final Context context = Factory.get().getApplicationContext(); - final ConversationIdSet groupChildIds = getGroupChildIds(context); - - if (groupChildIds != null && groupChildIds.size() > 0) { - // If a conversation is specified, remove just that notification. Otherwise, - // we're removing the group summary so clear all children. - if (conversationId != null) { - groupChildIds.remove(conversationId); - writeGroupChildIds(context, groupChildIds); - } else { - cancelStaleGroupChildren(groupChildIds, null); - // We'll update the group children preference as we cancel each child, - // so we don't need to do it here. - } - } - } - } - - /** - * Cancels stale notifications from the currently active group of - * notifications. If the {@code state} parameter is an instance of - * {@link MultiConversationNotificationState} it represents a new - * notification group. This method will cancel any notifications that were - * in the old group, but not the new one. If the new notification is not a - * group, then all existing grouped notifications are cancelled. - * - * @param previousGroupChildren Conversation ids for the active notification - * group - * @param state New notification state - */ - private static void cancelStaleGroupChildren(final ConversationIdSet previousGroupChildren, - final NotificationState state) { - final ConversationIdSet newChildren = new ConversationIdSet(); - if (state instanceof MultiConversationNotificationState) { - for (final NotificationState child : - ((MultiConversationNotificationState) state).mChildren) { - if (child.mConversationIds != null) { - newChildren.add(child.mConversationIds.first()); - } - } - } - for (final String childConversationId : previousGroupChildren) { - if (!newChildren.contains(childConversationId)) { - cancel(PendingIntentConstants.SMS_NOTIFICATION_ID, childConversationId, true); - } - } - } - - /** - * Returns {@code true} if incoming notifications should display a - * notification, {@code false} otherwise. - * - * @return true if the notification should occur - */ - private static boolean shouldNotify() { - // If we're not the default sms app, don't put up any notifications. - if (!PhoneUtils.getDefault().isDefaultSmsApp()) { - return false; - } - - // Now check prefs (i.e. settings) to see if the user turned off notifications. - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - final Context context = Factory.get().getApplicationContext(); - final String prefKey = context.getString(R.string.notifications_enabled_pref_key); - final boolean defaultValue = context.getResources().getBoolean( - R.bool.notifications_enabled_pref_default); - return prefs.getBoolean(prefKey, defaultValue); - } - - /** - * Returns {@code true} if incoming notifications for the given {@link NotificationState} - * should vibrate the device, {@code false} otherwise. - * - * @return true if vibration should be used - */ - public static boolean shouldVibrate(final NotificationState state) { - // The notification should vibrate if the global setting is turned on AND - // the per-conversation setting is turned on (default). - if (!state.getNotificationVibrate()) { - return false; - } else { - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - final Context context = Factory.get().getApplicationContext(); - final String prefKey = context.getString(R.string.notification_vibration_pref_key); - final boolean defaultValue = context.getResources().getBoolean( - R.bool.notification_vibration_pref_default); - return prefs.getBoolean(prefKey, defaultValue); - } - } - - private static Uri getNotificationRingtoneUriForConversationId(final String conversationId) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final ConversationListItemData convData = - ConversationListItemData.getExistingConversation(db, conversationId); - return RingtoneUtil.getNotificationRingtoneUri( - convData != null ? convData.getNotificationSoundUri() : null); - } - - /** - * Returns a unique tag to identify a notification. - * - * @param name The tag name (in practice, the type) - * @param conversationId The conversation id (optional) - */ - private static String buildNotificationTag(final String name, - final String conversationId) { - final Context context = Factory.get().getApplicationContext(); - if (conversationId != null) { - return context.getPackageName() + name + ":" + conversationId; - } else { - return context.getPackageName() + name; - } - } - - /** - * Returns a unique tag to identify a notification. - *

- * This delegates to - * {@link #buildNotificationTag(int, String, boolean)} and can be - * used when the notification is never bundled (e.g. error notifications). - */ - static String buildNotificationTag(final int type, final String conversationId) { - return buildNotificationTag(type, conversationId, false /* bundledNotification */); - } - - /** - * Returns a unique tag to identify a notification. - * - * @param type One of the constants in {@link PendingIntentConstants} - * @param conversationId The conversation id (where applicable) - * @param bundledNotification Set to true if this notification will be - * bundled together with other notifications (e.g. on a wearable - * device). - */ - static String buildNotificationTag(final int type, final String conversationId, - final boolean bundledNotification) { - String tag = null; - switch(type) { - case PendingIntentConstants.SMS_NOTIFICATION_ID: - if (bundledNotification) { - tag = buildNotificationTag(SMS_NOTIFICATION_TAG, conversationId); - } else { - tag = buildNotificationTag(SMS_NOTIFICATION_TAG, null); - } - break; - case PendingIntentConstants.MSG_SEND_ERROR: - tag = buildNotificationTag(SMS_ERROR_NOTIFICATION_TAG, null); - break; - } - return tag; - } - - private static void checkInitialized() { - if (!sInitialized) { - final Resources resources = Factory.get().getApplicationContext().getResources(); - sWearableImageWidth = resources.getDimensionPixelSize( - R.dimen.notification_wearable_image_width); - sWearableImageHeight = resources.getDimensionPixelSize( - R.dimen.notification_wearable_image_height); - sIconHeight = (int) resources.getDimension( - android.R.dimen.notification_large_icon_height); - sIconWidth = - (int) resources.getDimension(android.R.dimen.notification_large_icon_width); - - sInitialized = true; - } - } - - private static void processAndSend(final NotificationState state, final boolean silent, - final boolean softSound) { - final Context context = Factory.get().getApplicationContext(); - final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context); - notifBuilder.setCategory(Notification.CATEGORY_MESSAGE); - // TODO: Need to fix this for multi conversation notifications to rate limit dings. - final String conversationId = state.mConversationIds.first(); - - - final Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(state.getRingtoneUri()); - // If the notification's conversation is currently observable (focused or in the - // conversation list), then play a notification beep at a low volume and don't display an - // actual notification. - if (softSound) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "processAndSend: fromConversationId == " + - "sCurrentlyDisplayedConversationId so NOT showing notification," + - " but playing soft sound. conversationId: " + conversationId); - } - playObservableConversationNotificationSound(ringtoneUri); - return; - } - state.mBaseRequestCode = state.mType; - - // Set the delete intent (except for bundled wearable notifications, which are dismissed - // as a group, either from the wearable or when the summary notification is dismissed from - // the host device). - if (!(state instanceof BundledMessageNotificationState)) { - final PendingIntent clearIntent = state.getClearIntent(); - notifBuilder.setDeleteIntent(clearIntent); - } - - updateBuilderAudioVibrate(state, notifBuilder, silent, ringtoneUri, conversationId); - - // Set the content intent - PendingIntent destinationIntent; - if (state.mConversationIds.size() > 1) { - // We have notifications for multiple conversation, go to the conversation list. - destinationIntent = UIIntents.get() - .getPendingIntentForConversationListActivity(context); - } else { - // We have a single conversation, go directly to that conversation. - destinationIntent = UIIntents.get() - .getPendingIntentForConversationActivity(context, - state.mConversationIds.first(), - null /*draft*/); - } - notifBuilder.setContentIntent(destinationIntent); - - // TODO: set based on contact coming from a favorite. - notifBuilder.setPriority(state.getPriority()); - - // Save the state of the notification in-progress so when the avatar is loaded, - // we can continue building the notification. - final NotificationCompat.Style notifStyle = state.build(notifBuilder); - state.mNotificationBuilder = notifBuilder; - state.mNotificationStyle = notifStyle; - if (!state.mPeople.isEmpty()) { - final Bundle people = new Bundle(); - people.putStringArray(NotificationCompat.EXTRA_PEOPLE, - state.mPeople.toArray(new String[state.mPeople.size()])); - notifBuilder.addExtras(people); - } - - if (state.mParticipantAvatarsUris != null) { - final Uri avatarUri = state.mParticipantAvatarsUris.get(0); - final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor(avatarUri, - sIconWidth, sIconHeight, OsUtil.isAtLeastL()); - final MediaRequest imageRequest = descriptor.buildSyncMediaRequest( - context); - - synchronized (sPendingNotifications) { - sPendingNotifications.add(state); - } - - // Synchronously load the avatar. - final ImageResource avatarImage = - MediaResourceManager.get().requestMediaResourceSync(imageRequest); - if (avatarImage != null) { - ImageResource avatarHiRes = null; - try { - if (isWearCompanionAppInstalled()) { - // For Wear users, we need to request a high-res avatar image to use as the - // notification card background. If the sender has a contact photo, we'll - // request the display photo from the Contacts provider. Otherwise, we ask - // the local content provider for a hi-res version of the generic avatar - // (e.g. letter with colored background). - avatarHiRes = requestContactDisplayPhoto(context, - getDisplayPhotoUri(avatarUri)); - if (avatarHiRes == null) { - final AvatarRequestDescriptor hiResDesc = - new AvatarRequestDescriptor(avatarUri, - sWearableImageWidth, - sWearableImageHeight, - false /* cropToCircle */, - true /* isWearBackground */); - avatarHiRes = MediaResourceManager.get().requestMediaResourceSync( - hiResDesc.buildSyncMediaRequest(context)); - } - } - - // We have to make copies of the bitmaps to hand to the NotificationManager - // because the bitmap in the ImageResource is managed and will automatically - // get released. - Bitmap avatarBitmap = Bitmap.createBitmap(avatarImage.getBitmap()); - Bitmap avatarHiResBitmap = (avatarHiRes != null) ? - Bitmap.createBitmap(avatarHiRes.getBitmap()) : null; - sendNotification(state, avatarBitmap, avatarHiResBitmap); - return; - } finally { - avatarImage.release(); - if (avatarHiRes != null) { - avatarHiRes.release(); - } - } - } - } - // We have no avatar. Post the notification anyway. - sendNotification(state, null, null); - } - - /** - * Returns the thumbnailUri from the avatar URI, or null if avatar URI does not have thumbnail. - */ - private static Uri getThumbnailUri(final Uri avatarUri) { - Uri localUri = null; - final String avatarType = AvatarUriUtil.getAvatarType(avatarUri); - if (TextUtils.equals(avatarType, AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI)) { - localUri = AvatarUriUtil.getPrimaryUri(avatarUri); - } else if (UriUtil.isLocalResourceUri(avatarUri)) { - localUri = avatarUri; - } - if (localUri != null && localUri.getAuthority().equals(ContactsContract.AUTHORITY)) { - // Contact photos are of the form: content://com.android.contacts/contacts/123/photo - final List pathParts = localUri.getPathSegments(); - if (pathParts.size() == 3 && - pathParts.get(2).equals(Contacts.Photo.CONTENT_DIRECTORY)) { - return localUri; - } - } - return null; - } - - /** - * Returns the displayPhotoUri from the avatar URI, or null if avatar URI - * does not have a displayPhotoUri. - */ - private static Uri getDisplayPhotoUri(final Uri avatarUri) { - final Uri thumbnailUri = getThumbnailUri(avatarUri); - if (thumbnailUri == null) { - return null; - } - final List originalPaths = thumbnailUri.getPathSegments(); - final int originalPathsSize = originalPaths.size(); - final StringBuilder newPathBuilder = new StringBuilder(); - // Change content://com.android.contacts/contacts("_corp")/123/photo to - // content://com.android.contacts/contacts("_corp")/123/display_photo - for (int i = 0; i < originalPathsSize; i++) { - newPathBuilder.append('/'); - if (i == 2) { - newPathBuilder.append(ContactsContract.Contacts.Photo.DISPLAY_PHOTO); - } else { - newPathBuilder.append(originalPaths.get(i)); - } - } - return thumbnailUri.buildUpon().path(newPathBuilder.toString()).build(); - } - - private static ImageResource requestContactDisplayPhoto(final Context context, - final Uri displayPhotoUri) { - final UriImageRequestDescriptor bgDescriptor = - new UriImageRequestDescriptor(displayPhotoUri, - sWearableImageWidth, - sWearableImageHeight, - false, /* allowCompression */ - true, /* isStatic */ - false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - return MediaResourceManager.get().requestMediaResourceSync( - bgDescriptor.buildSyncMediaRequest(context)); - } - - private static void createMessageNotification(final boolean silent, - final String conversationId) { - final NotificationState state = MessageNotificationState.getNotificationState(); - final boolean softSound = DataModel.get().isNewMessageObservable(conversationId); - if (state == null) { - cancel(PendingIntentConstants.SMS_NOTIFICATION_ID); - if (softSound && !TextUtils.isEmpty(conversationId)) { - final Uri ringtoneUri = getNotificationRingtoneUriForConversationId(conversationId); - playObservableConversationNotificationSound(ringtoneUri); - } - return; - } - processAndSend(state, silent, softSound); - - // The rest of the logic here is for supporting Android Wear devices, specifically for when - // we are notifying about multiple conversations. In that case, the Inbox-style summary - // notification (which we already processed above) appears on the phone (as it always has), - // but wearables show per-conversation notifications, bundled together in a group. - - // It is valid to replace a notification group with another group with fewer conversations, - // or even with one notification for a single conversation. In either case, we need to - // explicitly cancel any children from the old group which are not being notified about now. - final Context context = Factory.get().getApplicationContext(); - final ConversationIdSet oldGroupChildIds = getGroupChildIds(context); - if (oldGroupChildIds != null && oldGroupChildIds.size() > 0) { - cancelStaleGroupChildren(oldGroupChildIds, state); - } - - // Send per-conversation notifications (if there are multiple conversations). - final ConversationIdSet groupChildIds = new ConversationIdSet(); - if (state instanceof MultiConversationNotificationState) { - for (final NotificationState child : - ((MultiConversationNotificationState) state).mChildren) { - processAndSend(child, true /* silent */, softSound); - if (child.mConversationIds != null) { - groupChildIds.add(child.mConversationIds.first()); - } - } - } - - // Record the new set of group children. - writeGroupChildIds(context, groupChildIds); - } - - private static void updateBuilderAudioVibrate(final NotificationState state, - final NotificationCompat.Builder notifBuilder, final boolean silent, - final Uri ringtoneUri, final String conversationId) { - int defaults = Notification.DEFAULT_LIGHTS; - if (!silent) { - final BuglePrefs prefs = Factory.get().getApplicationPrefs(); - final long latestNotificationTimestamp = prefs.getLong( - BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE); - final long latestReceivedTimestamp = state.getLatestReceivedTimestamp(); - prefs.putLong( - BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, - Math.max(latestNotificationTimestamp, latestReceivedTimestamp)); - if (latestReceivedTimestamp > latestNotificationTimestamp) { - synchronized (mLock) { - // Find out the last time we dinged for this conversation - Long lastTime = sLastMessageDingTime.get(conversationId); - if (sTimeBetweenDingsMs == 0) { - sTimeBetweenDingsMs = BugleGservices.get().getInt( - BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS, - BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT) * - 1000; - } - if (lastTime == null - || SystemClock.elapsedRealtime() - lastTime > sTimeBetweenDingsMs) { - sLastMessageDingTime.put(conversationId, SystemClock.elapsedRealtime()); - notifBuilder.setSound(ringtoneUri); - if (shouldVibrate(state)) { - defaults |= Notification.DEFAULT_VIBRATE; - } - } - } - } - } - notifBuilder.setDefaults(defaults); - } - - // TODO: this doesn't seem to be defined in NotificationCompat yet. Temporarily - // define it here until it makes its way from Notification -> NotificationCompat. - /** - * Notification category: incoming direct message (SMS, instant message, etc.). - */ - private static final String CATEGORY_MESSAGE = "msg"; - - private static void sendNotification(final NotificationState notificationState, - final Bitmap avatarIcon, final Bitmap avatarHiRes) { - final Context context = Factory.get().getApplicationContext(); - if (notificationState.mCanceled) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "sendNotification: Notification already cancelled; dropping it"); - } - return; - } - - synchronized (sPendingNotifications) { - if (sPendingNotifications.contains(notificationState)) { - sPendingNotifications.remove(notificationState); - } - } - - notificationState.mNotificationBuilder - .setSmallIcon(notificationState.getIcon()) - .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) - .setColor(context.getResources().getColor(R.color.notification_accent_color)) -// .setPublicVersion(null) // TODO: when/if we ever support different - // text on the lockscreen, instead of "contents hidden" - .setCategory(CATEGORY_MESSAGE); - - if (avatarIcon != null) { - notificationState.mNotificationBuilder.setLargeIcon(avatarIcon); - } - - if (notificationState.mParticipantContactUris != null && - notificationState.mParticipantContactUris.size() > 0) { - for (final Uri contactUri : notificationState.mParticipantContactUris) { - notificationState.mNotificationBuilder.addPerson(contactUri.toString()); - } - } - - final Uri attachmentUri = notificationState.getAttachmentUri(); - final String attachmentType = notificationState.getAttachmentType(); - Bitmap attachmentBitmap = null; - - // For messages with photo/video attachment, request an image to show in the notification. - if (attachmentUri != null && notificationState.mNotificationStyle != null && - (notificationState.mNotificationStyle instanceof - NotificationCompat.BigPictureStyle) && - (ContentType.isImageType(attachmentType) || - ContentType.isVideoType(attachmentType))) { - final boolean isVideo = ContentType.isVideoType(attachmentType); - - MediaRequest imageRequest; - if (isVideo) { - Assert.isTrue(VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()); - final MessagePartVideoThumbnailRequestDescriptor videoDescriptor = - new MessagePartVideoThumbnailRequestDescriptor(attachmentUri); - imageRequest = videoDescriptor.buildSyncMediaRequest(context); - } else { - final UriImageRequestDescriptor imageDescriptor = - new UriImageRequestDescriptor(attachmentUri, - sWearableImageWidth, - sWearableImageHeight, - false /* allowCompression */, - true /* isStatic */, - false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - imageRequest = imageDescriptor.buildSyncMediaRequest(context); - } - final ImageResource imageResource = - MediaResourceManager.get().requestMediaResourceSync(imageRequest); - if (imageResource != null) { - try { - // Copy the bitmap, because the one in the ImageResource is managed by - // MediaResourceManager. - Bitmap imageResourceBitmap = imageResource.getBitmap(); - Config config = imageResourceBitmap.getConfig(); - - // Make sure our bitmap has a valid format. - if (config == null) { - config = Bitmap.Config.ARGB_8888; - } - attachmentBitmap = imageResourceBitmap.copy(config, true); - } finally { - imageResource.release(); - } - } - } - - fireOffNotification(notificationState, attachmentBitmap, avatarIcon, avatarHiRes); - } - - private static void fireOffNotification(final NotificationState notificationState, - final Bitmap attachmentBitmap, final Bitmap avatarBitmap, Bitmap avatarHiResBitmap) { - if (notificationState.mCanceled) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Firing off notification, but notification already canceled"); - } - return; - } - - final Context context = Factory.get().getApplicationContext(); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "MMS picture loaded, bitmap: " + attachmentBitmap); - } - - final NotificationCompat.Builder notifBuilder = notificationState.mNotificationBuilder; - notifBuilder.setStyle(notificationState.mNotificationStyle); - notifBuilder.setColor(context.getResources().getColor(R.color.notification_accent_color)); - - final WearableExtender wearableExtender = new WearableExtender(); - setWearableGroupOptions(notifBuilder, notificationState); - - if (avatarHiResBitmap != null) { - wearableExtender.setBackground(avatarHiResBitmap); - } else if (avatarBitmap != null) { - // Nothing to do here; we already set avatarBitmap as the notification icon - } else { - final Bitmap defaultBackground = BitmapFactory.decodeResource( - context.getResources(), R.drawable.bg_sms); - wearableExtender.setBackground(defaultBackground); - } - - if (notificationState instanceof MultiMessageNotificationState) { - if (attachmentBitmap != null) { - // When we've got a picture attachment, we do some switcheroo trickery. When - // the notification is expanded, we show the picture as a bigPicture. The small - // icon shows the sender's avatar. When that same notification is collapsed, the - // picture is shown in the location where the avatar is normally shown. The lines - // below make all that happen. - - // Here we're taking the picture attachment and making a small, scaled, center - // cropped version of the picture we can stuff into the place where the avatar - // goes when the notification is collapsed. - final Bitmap smallBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap, sIconWidth, - sIconHeight); - ((NotificationCompat.BigPictureStyle) notificationState.mNotificationStyle) - .bigPicture(attachmentBitmap) - .bigLargeIcon(avatarBitmap); - notificationState.mNotificationBuilder.setLargeIcon(smallBitmap); - - // Add a wearable page with no visible card so you can more easily see the photo. - final NotificationCompat.Builder photoPageNotifBuilder = - new NotificationCompat.Builder(Factory.get().getApplicationContext()); - final WearableExtender photoPageWearableExtender = new WearableExtender(); - photoPageWearableExtender.setHintShowBackgroundOnly(true); - if (attachmentBitmap != null) { - final Bitmap wearBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap, - sWearableImageWidth, sWearableImageHeight); - photoPageWearableExtender.setBackground(wearBitmap); - } - photoPageNotifBuilder.extend(photoPageWearableExtender); - wearableExtender.addPage(photoPageNotifBuilder.build()); - } - - maybeAddWearableConversationLog(wearableExtender, - (MultiMessageNotificationState) notificationState); - addDownloadMmsAction(notifBuilder, wearableExtender, notificationState); - addWearableVoiceReplyAction(wearableExtender, notificationState); - } - - // Apply the wearable options and build & post the notification - notifBuilder.extend(wearableExtender); - doNotify(notifBuilder.build(), notificationState); - } - - private static void setWearableGroupOptions(final NotificationCompat.Builder notifBuilder, - final NotificationState notificationState) { - final String groupKey = "groupkey"; - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Group key (for wearables)=" + groupKey); - } - if (notificationState instanceof MultiConversationNotificationState) { - notifBuilder.setGroup(groupKey).setGroupSummary(true); - } else if (notificationState instanceof BundledMessageNotificationState) { - final int order = ((BundledMessageNotificationState) notificationState).mGroupOrder; - // Convert the order to a zero-padded string ("00", "01", "02", etc). - // The Wear library orders notifications within a bundle lexicographically - // by the sort key, hence the need for zeroes to preserve the ordering. - final String sortKey = String.format(Locale.US, "%02d", order); - notifBuilder.setGroup(groupKey).setSortKey(sortKey); - } - } - - private static void maybeAddWearableConversationLog( - final WearableExtender wearableExtender, - final MultiMessageNotificationState notificationState) { - if (!isWearCompanionAppInstalled()) { - return; - } - final String convId = notificationState.mConversationIds.first(); - ConversationLineInfo convInfo = notificationState.mConvList.mConvInfos.get(0); - final Notification page = MessageNotificationState.buildConversationPageForWearable( - convId, - convInfo.mParticipantCount); - if (page != null) { - wearableExtender.addPage(page); - } - } - - private static void addWearableVoiceReplyAction( - final WearableExtender wearableExtender, final NotificationState notificationState) { - if (!(notificationState instanceof MultiMessageNotificationState)) { - return; - } - final MultiMessageNotificationState multiMessageNotificationState = - (MultiMessageNotificationState) notificationState; - final Context context = Factory.get().getApplicationContext(); - - final String conversationId = notificationState.mConversationIds.first(); - final ConversationLineInfo convInfo = - multiMessageNotificationState.mConvList.mConvInfos.get(0); - final String selfId = convInfo.mSelfParticipantId; - - final boolean requiresMms = - MmsSmsUtils.getRequireMmsForEmailAddress( - convInfo.mIncludeEmailAddress, convInfo.mSubId) || - (convInfo.mIsGroup && MmsUtils.groupMmsEnabled(convInfo.mSubId)); - - final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode(); - final PendingIntent replyPendingIntent = UIIntents.get() - .getPendingIntentForSendingMessageToConversation(context, - conversationId, selfId, requiresMms, requestCode); - - final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms : - R.string.notification_reply_via_sms; - - final NotificationCompat.Action.Builder actionBuilder = - new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply, - context.getString(replyLabelRes), replyPendingIntent); - final String[] choices = context.getResources().getStringArray( - R.array.notification_reply_choices); - final RemoteInput remoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT).setLabel( - context.getString(R.string.notification_reply_prompt)). - setChoices(choices) - .build(); - actionBuilder.addRemoteInput(remoteInput); - wearableExtender.addAction(actionBuilder.build()); - } - - private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder, - final WearableExtender wearableExtender, final NotificationState notificationState) { - if (!(notificationState instanceof MultiMessageNotificationState)) { - return; - } - final MultiMessageNotificationState multiMessageNotificationState = - (MultiMessageNotificationState) notificationState; - final ConversationLineInfo convInfo = - multiMessageNotificationState.mConvList.mConvInfos.get(0); - if (!convInfo.getDoesLatestMessageNeedDownload()) { - return; - } - final String messageId = convInfo.getLatestMessageId(); - if (messageId == null) { - // No message Id, no download for you - return; - } - final Context context = Factory.get().getApplicationContext(); - final PendingIntent downloadPendingIntent = - RedownloadMmsAction.getPendingIntentForRedownloadMms(context, messageId); - - final NotificationCompat.Action.Builder actionBuilder = - new NotificationCompat.Action.Builder(R.drawable.ic_file_download_light, - context.getString(R.string.notification_download_mms), - downloadPendingIntent); - final NotificationCompat.Action downloadAction = actionBuilder.build(); - notifBuilder.addAction(downloadAction); - - // Support the action on a wearable device as well - wearableExtender.addAction(downloadAction); - } - - private static synchronized void doNotify(final Notification notification, - final NotificationState notificationState) { - if (notification == null) { - return; - } - final int type = notificationState.mType; - final ConversationIdSet conversationIds = notificationState.mConversationIds; - final boolean isBundledNotification = - (notificationState instanceof BundledMessageNotificationState); - - // Mark the notification as finished - notificationState.mCanceled = true; - - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - // Only need conversationId for tags with a single conversation. - String conversationId = null; - if (conversationIds != null && conversationIds.size() == 1) { - conversationId = conversationIds.first(); - } - final String notificationTag = buildNotificationTag(type, - conversationId, isBundledNotification); - - notification.flags |= Notification.FLAG_AUTO_CANCEL; - notification.defaults |= Notification.DEFAULT_LIGHTS; - - notificationManager.notify(notificationTag, type, notification); - - LogUtil.i(TAG, "Notifying for conversation " + conversationId + "; " - + "tag = " + notificationTag + ", type = " + type); - } - - // This is the message string used in each line of an inboxStyle notification. - // TODO: add attachment type - static CharSequence formatInboxMessage(final String sender, - final CharSequence message, final Uri attachmentUri, final String attachmentType) { - final Context context = Factory.get().getApplicationContext(); - final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan( - context, R.style.NotificationSenderText); - - final TextAppearanceSpan notificationTertiaryText = new TextAppearanceSpan( - context, R.style.NotificationTertiaryText); - - final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); - if (!TextUtils.isEmpty(sender)) { - spannableStringBuilder.append(sender); - spannableStringBuilder.setSpan(notificationSenderSpan, 0, sender.length(), 0); - } - final String separator = context.getString(R.string.notification_separator); - - if (!TextUtils.isEmpty(message)) { - if (spannableStringBuilder.length() > 0) { - spannableStringBuilder.append(separator); - } - final int start = spannableStringBuilder.length(); - spannableStringBuilder.append(message); - spannableStringBuilder.setSpan(notificationTertiaryText, start, - start + message.length(), 0); - } - if (attachmentUri != null) { - if (spannableStringBuilder.length() > 0) { - spannableStringBuilder.append(separator); - } - spannableStringBuilder.append(formatAttachmentTag(null, attachmentType)); - } - return spannableStringBuilder; - } - - protected static CharSequence buildColonSeparatedMessage( - final String title, final CharSequence content, final Uri attachmentUri, - final String attachmentType) { - return buildBoldedMessage(title, content, attachmentUri, attachmentType, - R.string.notification_ticker_separator); - } - - protected static CharSequence buildSpaceSeparatedMessage( - final String title, final CharSequence content, final Uri attachmentUri, - final String attachmentType) { - return buildBoldedMessage(title, content, attachmentUri, attachmentType, - R.string.notification_space_separator); - } - - /** - * buildBoldedMessage - build a formatted message where the title is bold, there's a - * separator, then the message. - */ - private static CharSequence buildBoldedMessage( - final String title, final CharSequence message, final Uri attachmentUri, - final String attachmentType, - final int separatorId) { - final Context context = Factory.get().getApplicationContext(); - final SpannableStringBuilder spanBuilder = new SpannableStringBuilder(); - - // Boldify the title (which is the sender's name) - if (!TextUtils.isEmpty(title)) { - spanBuilder.append(title); - spanBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, title.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (!TextUtils.isEmpty(message)) { - if (spanBuilder.length() > 0) { - spanBuilder.append(context.getString(separatorId)); - } - spanBuilder.append(message); - } - if (attachmentUri != null) { - if (spanBuilder.length() > 0) { - final String separator = context.getString(R.string.notification_separator); - spanBuilder.append(separator); - } - spanBuilder.append(formatAttachmentTag(null, attachmentType)); - } - return spanBuilder; - } - - static CharSequence formatAttachmentTag(final String author, final String attachmentType) { - final Context context = Factory.get().getApplicationContext(); - final TextAppearanceSpan notificationSecondaryText = new TextAppearanceSpan( - context, R.style.NotificationSecondaryText); - final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); - if (!TextUtils.isEmpty(author)) { - final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan( - context, R.style.NotificationSenderText); - spannableStringBuilder.append(author); - spannableStringBuilder.setSpan(notificationSenderSpan, 0, author.length(), 0); - final String separator = context.getString(R.string.notification_separator); - spannableStringBuilder.append(separator); - } - final int start = spannableStringBuilder.length(); - // The default attachment type is an image, since that's what was originally - // supported. When there's no content type, assume it's an image. - int message = R.string.notification_picture; - if (ContentType.isAudioType(attachmentType)) { - message = R.string.notification_audio; - } else if (ContentType.isVideoType(attachmentType)) { - message = R.string.notification_video; - } else if (ContentType.isVCardType(attachmentType)) { - message = R.string.notification_vcard; - } - spannableStringBuilder.append(context.getText(message)); - spannableStringBuilder.setSpan(notificationSecondaryText, start, - spannableStringBuilder.length(), 0); - return spannableStringBuilder; - } - - /** - * Play the observable conversation notification sound (it's the regular notification sound, but - * played at half-volume) - */ - private static void playObservableConversationNotificationSound(final Uri ringtoneUri) { - final Context context = Factory.get().getApplicationContext(); - final AudioManager audioManager = (AudioManager) context - .getSystemService(Context.AUDIO_SERVICE); - final boolean silenced = - audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; - if (silenced) { - return; - } - - final NotificationPlayer player = new NotificationPlayer(LogUtil.BUGLE_TAG); - player.play(ringtoneUri, false, - AudioManager.STREAM_NOTIFICATION, - OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME); - - // Stop the sound after five seconds to handle continuous ringtones - ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() { - @Override - public void run() { - player.stop(); - } - }, 5000); - } - - public static boolean isWearCompanionAppInstalled() { - boolean found = false; - try { - Factory.get().getApplicationContext().getPackageManager() - .getPackageInfo(WEARABLE_COMPANION_APP_PACKAGE, 0); - found = true; - } catch (final NameNotFoundException e) { - // Ignore; found is already false - } - return found; - } - - /** - * When we go to the conversation list, call this to mark all messages as seen. That means - * we won't show a notification again for the same message. - */ - public static void markAllMessagesAsSeen() { - MarkAsSeenAction.markAllAsSeen(); - resetLastMessageDing(null); // reset the ding timeout for all conversations - } - - /** - * When we open a particular conversation, call this to mark all messages as read. - */ - public static void markMessagesAsRead(final String conversationId) { - MarkAsReadAction.markAsRead(conversationId); - resetLastMessageDing(conversationId); - } - - /** - * Returns the conversation ids of all active, grouped notifications, or - * {code null} if no notifications are currently active and grouped. - */ - private static ConversationIdSet getGroupChildIds(final Context context) { - final String prefKey = context.getString(R.string.notifications_group_children_key); - final String groupChildIdsText = BuglePrefs.getApplicationPrefs().getString(prefKey, ""); - if (!TextUtils.isEmpty(groupChildIdsText)) { - return ConversationIdSet.createSet(groupChildIdsText); - } else { - return null; - } - } - - /** - * Records the conversation ids of the currently active grouped notifications. - */ - private static void writeGroupChildIds(final Context context, - final ConversationIdSet childIds) { - final ConversationIdSet oldChildIds = getGroupChildIds(context); - if (childIds.equals(oldChildIds)) { - return; - } - final String prefKey = context.getString(R.string.notifications_group_children_key); - BuglePrefs.getApplicationPrefs().putString(prefKey, childIds.getDelimitedString()); - } - - /** - * Reset the timer for a notification ding on a particular conversation or all conversations. - */ - public static void resetLastMessageDing(final String conversationId) { - synchronized (mLock) { - if (TextUtils.isEmpty(conversationId)) { - // reset all conversation dings - sLastMessageDingTime.clear(); - } else { - sLastMessageDingTime.remove(conversationId); - } - } - } - - public static void notifyEmergencySmsFailed(final String emergencyNumber, - final String conversationId) { - final Context context = Factory.get().getApplicationContext(); - - final CharSequence line1 = MessageNotificationState.applyWarningTextColor(context, - context.getString(R.string.notification_emergency_send_failure_line1, - emergencyNumber)); - final String line2 = context.getString(R.string.notification_emergency_send_failure_line2, - emergencyNumber); - final PendingIntent destinationIntent = UIIntents.get() - .getPendingIntentForConversationActivity(context, conversationId, null /* draft */); - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - builder.setTicker(line1) - .setContentTitle(line1) - .setContentText(line2) - .setStyle(new NotificationCompat.BigTextStyle(builder).bigText(line2)) - .setSmallIcon(R.drawable.ic_failed_light) - .setContentIntent(destinationIntent) - .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure)); - - final String tag = context.getPackageName() + ":emergency_sms_error"; - NotificationManagerCompat.from(context).notify( - tag, - PendingIntentConstants.MSG_SEND_ERROR, - builder.build()); - } -} - diff --git a/src/com/android/messaging/datamodel/BugleRecipientEntry.java b/src/com/android/messaging/datamodel/BugleRecipientEntry.java deleted file mode 100644 index 2a9e5ff..0000000 --- a/src/com/android/messaging/datamodel/BugleRecipientEntry.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.net.Uri; -import android.text.TextUtils; - -import com.android.ex.chips.RecipientEntry; - -/** - * An extension of RecipientEntry for Bugle's use since Bugle uses phone numbers to identify - * participants / recipients instead of contact ids. This allows the user to send to multiple - * phone numbers of the same contact. - */ -public class BugleRecipientEntry extends RecipientEntry { - - protected BugleRecipientEntry(final int entryType, final String displayName, - final String destination, final int destinationType, final String destinationLabel, - final long contactId, final Long directoryId, final long dataId, - final Uri photoThumbnailUri, final boolean isFirstLevel, final boolean isValid, - final String lookupKey) { - super(entryType, displayName, destination, destinationType, destinationLabel, contactId, - directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid, lookupKey); - } - - public static BugleRecipientEntry constructTopLevelEntry(final String displayName, - final int displayNameSource, final String destination, final int destinationType, - final String destinationLabel, final long contactId, final Long directoryId, - final long dataId, final String thumbnailUriAsString, final boolean isValid, - final String lookupKey) { - return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType, - destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null - ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey); - } - - public static BugleRecipientEntry constructSecondLevelEntry(final String displayName, - final int displayNameSource, final String destination, final int destinationType, - final String destinationLabel, final long contactId, final Long directoryId, - final long dataId, final String thumbnailUriAsString, final boolean isValid, - final String lookupKey) { - return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType, - destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null - ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey); - } - - @Override - public boolean isSamePerson(final RecipientEntry entry) { - return getDestination() != null && entry.getDestination() != null && - TextUtils.equals(getDestination(), entry.getDestination()); - } -} diff --git a/src/com/android/messaging/datamodel/ConversationImagePartsView.java b/src/com/android/messaging/datamodel/ConversationImagePartsView.java deleted file mode 100644 index 70ba381..0000000 --- a/src/com/android/messaging/datamodel/ConversationImagePartsView.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.provider.BaseColumns; - -import com.android.ex.photo.provider.PhotoContract.PhotoViewColumns; - -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.util.ContentType; - -/** - * View for the image parts for the conversation. It is used to provide the photoviewer with a - * a data source for all the photos in a conversation, so that the photoviewer can support paging - * through all the photos of the conversation. The columns of the view are a superset of - * {@link com.android.ex.photo.provider.PhotoContract.PhotoViewColumns}. - */ -public class ConversationImagePartsView { - private static final String VIEW_NAME = "conversation_image_parts_view"; - - private static final String CREATE_SQL = "CREATE VIEW " + - VIEW_NAME + " AS SELECT " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID - + " as " + Columns.CONVERSATION_ID + ", " - + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI - + " as " + Columns.URI + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME - + " as " + Columns.SENDER_FULL_NAME + ", " - + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI - + " as " + Columns.CONTENT_URI + ", " - // Use NULL as the thumbnail uri - + " NULL as " + Columns.THUMBNAIL_URI + ", " - + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE - + " as " + Columns.CONTENT_TYPE + ", " - // - // Columns in addition to those specified by PhotoContract - // - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION - + " as " + Columns.DISPLAY_DESTINATION + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP - + " as " + Columns.RECEIVED_TIMESTAMP + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS - + " as " + Columns.STATUS + " " - - + " FROM " + DatabaseHelper.MESSAGES_TABLE + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE - + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID - + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") " - + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON (" - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID - + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")" - - // "content_type like 'image/%'" - + " WHERE " + DatabaseHelper.PARTS_TABLE + "." + PartColumns.CONTENT_TYPE - + " like '" + ContentType.IMAGE_PREFIX + "%'" - - + " ORDER BY " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " ASC, " - + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + " ASC"; - - static class Columns implements BaseColumns { - static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID; - static final String URI = PhotoViewColumns.URI; - static final String SENDER_FULL_NAME = PhotoViewColumns.NAME; - static final String CONTENT_URI = PhotoViewColumns.CONTENT_URI; - static final String THUMBNAIL_URI = PhotoViewColumns.THUMBNAIL_URI; - static final String CONTENT_TYPE = PhotoViewColumns.CONTENT_TYPE; - // Columns in addition to those specified by PhotoContract - static final String DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION; - static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP; - static final String STATUS = MessageColumns.STATUS; - } - - public interface PhotoViewQuery { - public final String[] PROJECTION = { - PhotoViewColumns.URI, - PhotoViewColumns.NAME, - PhotoViewColumns.CONTENT_URI, - PhotoViewColumns.THUMBNAIL_URI, - PhotoViewColumns.CONTENT_TYPE, - // Columns in addition to those specified by PhotoContract - Columns.DISPLAY_DESTINATION, - Columns.RECEIVED_TIMESTAMP, - Columns.STATUS, - }; - - public final int INDEX_URI = 0; - public final int INDEX_SENDER_FULL_NAME = 1; - public final int INDEX_CONTENT_URI = 2; - public final int INDEX_THUMBNAIL_URI = 3; - public final int INDEX_CONTENT_TYPE = 4; - // Columns in addition to those specified by PhotoContract - public final int INDEX_DISPLAY_DESTINATION = 5; - public final int INDEX_RECEIVED_TIMESTAMP = 6; - public final int INDEX_STATUS = 7; - } - - static final String getViewName() { - return VIEW_NAME; - } - - static final String getCreateSql() { - return CREATE_SQL; - } -} diff --git a/src/com/android/messaging/datamodel/CursorQueryData.java b/src/com/android/messaging/datamodel/CursorQueryData.java deleted file mode 100644 index 3e6a656..0000000 --- a/src/com/android/messaging/datamodel/CursorQueryData.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; - -import com.android.messaging.util.Assert; -import com.google.common.annotations.VisibleForTesting; - -/** - * Holds parameters and data (such as content URI) for performing queries on the content provider. - * This class could then be used to perform a query using either a BoundCursorLoader or querying - * on the content resolver directly. - * - * This class is used for cases where the way to load a cursor is not fixed. For example, - * when using ContactUtil to query for phone numbers, the ContactPickerFragment wants to use - * a CursorLoader to asynchronously load the data and tie in nicely with its data binding - * paradigm, whereas ContactRecipientAdapter wants to synchronously perform the query on the - * worker thread. - */ -public class CursorQueryData { - protected final Uri mUri; - protected final String[] mProjection; - protected final String mSelection; - protected final String[] mSelectionArgs; - protected final String mSortOrder; - protected final Context mContext; - - public CursorQueryData(final Context context, final Uri uri, final String[] projection, - final String selection, final String[] selectionArgs, final String sortOrder) { - mContext = context; - mUri = uri; - mProjection = projection; - mSelection = selection; - mSelectionArgs = selectionArgs; - mSortOrder = sortOrder; - } - - public BoundCursorLoader createBoundCursorLoader(final String bindingId) { - return new BoundCursorLoader(bindingId, mContext, mUri, mProjection, mSelection, - mSelectionArgs, mSortOrder); - } - - public Cursor performSynchronousQuery() { - Assert.isNotMainThread(); - if (mUri == null) { - // See {@link #getEmptyQueryData} - return null; - } else { - return mContext.getContentResolver().query(mUri, mProjection, mSelection, - mSelectionArgs, mSortOrder); - } - } - - @VisibleForTesting - public Uri getUri() { - return mUri; - } - - /** - * Representation of an invalid query. {@link #performSynchronousQuery} will return - * a null Cursor. - */ - public static CursorQueryData getEmptyQueryData() { - return new CursorQueryData(null, null, null, null, null, null); - } -} diff --git a/src/com/android/messaging/datamodel/DataModel.java b/src/com/android/messaging/datamodel/DataModel.java deleted file mode 100644 index 936b51c..0000000 --- a/src/com/android/messaging/datamodel/DataModel.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.action.Action; -import com.android.messaging.datamodel.action.ActionService; -import com.android.messaging.datamodel.action.BackgroundWorker; -import com.android.messaging.datamodel.data.BlockedParticipantsData; -import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ContactPickerData; -import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.LaunchConversationData; -import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantListItemData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; -import com.android.messaging.datamodel.data.PeopleOptionsItemData; -import com.android.messaging.datamodel.data.SettingsData; -import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.datamodel.data.VCardContactItemData; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.ConnectivityUtil; - -public abstract class DataModel { - private String mFocusedConversation; - private boolean mConversationListScrolledToNewestConversation; - - public static DataModel get() { - return Factory.get().getDataModel(); - } - - public static final void startActionService(final Action action) { - get().getActionService().startAction(action); - } - - public static final void scheduleAction(final Action action, - final int code, final long delayMs) { - get().getActionService().scheduleAction(action, code, delayMs); - } - - public abstract ConversationListData createConversationListData(final Context context, - final ConversationListDataListener listener, final boolean archivedMode); - - public abstract ConversationData createConversationData(final Context context, - final ConversationDataListener listener, final String conversationId); - - public abstract ContactListItemData createContactListItemData(); - - public abstract ContactPickerData createContactPickerData(final Context context, - final ContactPickerDataListener listener); - - public abstract MediaPickerData createMediaPickerData(final Context context); - - public abstract GalleryGridItemData createGalleryGridItemData(); - - public abstract LaunchConversationData createLaunchConversationData( - LaunchConversationDataListener listener); - - public abstract PeopleOptionsItemData createPeopleOptionsItemData(final Context context); - - public abstract PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId, - final Context context, final PeopleAndOptionsDataListener listener); - - public abstract VCardContactItemData createVCardContactItemData(final Context context, - final MessagePartData data); - - public abstract VCardContactItemData createVCardContactItemData(final Context context, - final Uri vCardUri); - - public abstract ParticipantListItemData createParticipantListItemData( - final ParticipantData participant); - - public abstract BlockedParticipantsData createBlockedParticipantsData(Context context, - BlockedParticipantsDataListener listener); - - public abstract SubscriptionListData createSubscriptonListData(Context context); - - public abstract SettingsData createSettingsData(Context context, SettingsDataListener listener); - - public abstract DraftMessageData createDraftMessageData(String conversationId); - - public abstract ActionService getActionService(); - - public abstract BackgroundWorker getBackgroundWorkerForActionService(); - - @DoesNotRunOnMainThread - public abstract DatabaseWrapper getDatabase(); - - // Allow DataModel to coordinate with activity lifetime events. - public abstract void onActivityResume(); - - abstract void onCreateTables(final SQLiteDatabase db); - - public void setFocusedConversation(final String conversationId) { - mFocusedConversation = conversationId; - } - - public boolean isFocusedConversation(final String conversationId) { - return !TextUtils.isEmpty(mFocusedConversation) - && TextUtils.equals(mFocusedConversation, conversationId); - } - - public void setConversationListScrolledToNewestConversation( - final boolean scrolledToNewestConversation) { - mConversationListScrolledToNewestConversation = scrolledToNewestConversation; - } - - public boolean isConversationListScrolledToNewestConversation() { - return mConversationListScrolledToNewestConversation; - } - - /** - * If a new message is received in the specified conversation, will the user be able to - * observe it in some UI within the app? - * @param conversationId conversation with the new incoming message - */ - public boolean isNewMessageObservable(final String conversationId) { - return isConversationListScrolledToNewestConversation() - || isFocusedConversation(conversationId); - } - - public abstract void onApplicationCreated(); - - public abstract ConnectivityUtil getConnectivityUtil(); - - public abstract SyncManager getSyncManager(); -} diff --git a/src/com/android/messaging/datamodel/DataModelException.java b/src/com/android/messaging/datamodel/DataModelException.java deleted file mode 100644 index 7084438..0000000 --- a/src/com/android/messaging/datamodel/DataModelException.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -public class DataModelException extends Exception { - private static final long serialVersionUID = 1L; - - private static final int FIRST = 100; - - // ERRORS GENERATED INTERNALLY BY DATA MODEL. - - // ERRORS RELATED WITH SMS. - public static final int ERROR_SMS_TEMPORARY_FAILURE = 116; - public static final int ERROR_SMS_PERMANENT_FAILURE = 117; - public static final int ERROR_MMS_TEMPORARY_FAILURE = 118; - public static final int ERROR_MMS_PERMANENT_UNKNOWN_FAILURE = 119; - - // Request expired. - public static final int ERROR_EXPIRED = 120; - // Request canceled by user. - public static final int ERROR_CANCELED = 121; - - public static final int ERROR_MOBILE_DATA_DISABLED = 123; - public static final int ERROR_MMS_SERVICE_BLOCKED = 124; - public static final int ERROR_MMS_INVALID_ADDRESS = 125; - public static final int ERROR_MMS_NETWORK_PROBLEM = 126; - public static final int ERROR_MMS_MESSAGE_NOT_FOUND = 127; - public static final int ERROR_MMS_MESSAGE_FORMAT_CORRUPT = 128; - public static final int ERROR_MMS_CONTENT_NOT_ACCEPTED = 129; - public static final int ERROR_MMS_MESSAGE_NOT_SUPPORTED = 130; - public static final int ERROR_MMS_REPLY_CHARGING_ERROR = 131; - public static final int ERROR_MMS_ADDRESS_HIDING_NOT_SUPPORTED = 132; - public static final int ERROR_MMS_LACK_OF_PREPAID = 133; - public static final int ERROR_MMS_CAN_NOT_PERSIST = 134; - public static final int ERROR_MMS_NO_AVAILABLE_APN = 135; - public static final int ERROR_MMS_INVALID_MESSAGE_TO_SEND = 136; - public static final int ERROR_MMS_INVALID_MESSAGE_RECEIVED = 137; - public static final int ERROR_MMS_NO_CONFIGURATION = 138; - - private static final int LAST = 138; - - private final boolean mIsInjection; - private final int mErrorCode; - private final String mMessage; - private final long mBackoff; - - public DataModelException(final int errorCode, final Exception innerException, - final long backoff, final boolean injection, final String message) { - // Since some of the exceptions passed in may not be serializable, only record message - // instead of setting inner exception for Exception class. Otherwise, we will get - // serialization issues when we pass ServerRequestException as intent extra later. - if (errorCode < FIRST || errorCode > LAST) { - throw new IllegalArgumentException("error code out of range: " + errorCode); - } - mIsInjection = injection; - mErrorCode = errorCode; - if (innerException != null) { - mMessage = innerException.getMessage() + " -- " + - (mIsInjection ? "[INJECTED] -- " : "") + message; - } else { - mMessage = (mIsInjection ? "[INJECTED] -- " : "") + message; - } - - mBackoff = backoff; - } - - public DataModelException(final int errorCode) { - this(errorCode, null, 0, false, null); - } - - public DataModelException(final int errorCode, final Exception innerException) { - this(errorCode, innerException, 0, false, null); - } - - public DataModelException(final int errorCode, final String message) { - this(errorCode, null, 0, false, message); - } - - @Override - public String getMessage() { - return mMessage; - } - - public int getErrorCode() { - return mErrorCode; - } -} diff --git a/src/com/android/messaging/datamodel/DataModelImpl.java b/src/com/android/messaging/datamodel/DataModelImpl.java deleted file mode 100644 index 6ab3f00..0000000 --- a/src/com/android/messaging/datamodel/DataModelImpl.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.telephony.SubscriptionManager; - -import com.android.messaging.datamodel.action.ActionService; -import com.android.messaging.datamodel.action.BackgroundWorker; -import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction; -import com.android.messaging.datamodel.action.ProcessPendingMessagesAction; -import com.android.messaging.datamodel.data.BlockedParticipantsData; -import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ContactPickerData; -import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.LaunchConversationData; -import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantListItemData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; -import com.android.messaging.datamodel.data.PeopleOptionsItemData; -import com.android.messaging.datamodel.data.SettingsData; -import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.datamodel.data.VCardContactItemData; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.ConnectivityUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -public class DataModelImpl extends DataModel { - private final Context mContext; - private final ActionService mActionService; - private final BackgroundWorker mDataModelWorker; - private final DatabaseHelper mDatabaseHelper; - private final ConnectivityUtil mConnectivityUtil; - private final SyncManager mSyncManager; - - public DataModelImpl(final Context context) { - super(); - mContext = context; - mActionService = new ActionService(); - mDataModelWorker = new BackgroundWorker(); - mDatabaseHelper = DatabaseHelper.getInstance(context); - mConnectivityUtil = new ConnectivityUtil(context); - mSyncManager = new SyncManager(); - } - - @Override - public ConversationListData createConversationListData(final Context context, - final ConversationListDataListener listener, final boolean archivedMode) { - return new ConversationListData(context, listener, archivedMode); - } - - @Override - public ConversationData createConversationData(final Context context, - final ConversationDataListener listener, final String conversationId) { - return new ConversationData(context, listener, conversationId); - } - - @Override - public ContactListItemData createContactListItemData() { - return new ContactListItemData(); - } - - @Override - public ContactPickerData createContactPickerData(final Context context, - final ContactPickerDataListener listener) { - return new ContactPickerData(context, listener); - } - - @Override - public BlockedParticipantsData createBlockedParticipantsData( - final Context context, final BlockedParticipantsDataListener listener) { - return new BlockedParticipantsData(context, listener); - } - - @Override - public MediaPickerData createMediaPickerData(final Context context) { - return new MediaPickerData(context); - } - - @Override - public GalleryGridItemData createGalleryGridItemData() { - return new GalleryGridItemData(); - } - - @Override - public LaunchConversationData createLaunchConversationData( - final LaunchConversationDataListener listener) { - return new LaunchConversationData(listener); - } - - @Override - public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) { - return new PeopleOptionsItemData(context); - } - - @Override - public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId, - final Context context, final PeopleAndOptionsDataListener listener) { - return new PeopleAndOptionsData(conversationId, context, listener); - } - - @Override - public VCardContactItemData createVCardContactItemData(final Context context, - final MessagePartData data) { - return new VCardContactItemData(context, data); - } - - @Override - public VCardContactItemData createVCardContactItemData(final Context context, - final Uri vCardUri) { - return new VCardContactItemData(context, vCardUri); - } - - @Override - public ParticipantListItemData createParticipantListItemData( - final ParticipantData participant) { - return new ParticipantListItemData(participant); - } - - @Override - public SubscriptionListData createSubscriptonListData(Context context) { - return new SubscriptionListData(context); - } - - @Override - public SettingsData createSettingsData(Context context, SettingsDataListener listener) { - return new SettingsData(context, listener); - } - - @Override - public DraftMessageData createDraftMessageData(String conversationId) { - return new DraftMessageData(conversationId); - } - - @Override - public ActionService getActionService() { - // We need to allow access to this on the UI thread since it's used to start actions. - return mActionService; - } - - @Override - public BackgroundWorker getBackgroundWorkerForActionService() { - return mDataModelWorker; - } - - @Override - @DoesNotRunOnMainThread - public DatabaseWrapper getDatabase() { - // We prevent the main UI thread from accessing the database since we have to allow - // public access to this class to enable sub-packages to access data. - Assert.isNotMainThread(); - return mDatabaseHelper.getDatabase(); - } - - @Override - public ConnectivityUtil getConnectivityUtil() { - return mConnectivityUtil; - } - - @Override - public SyncManager getSyncManager() { - return mSyncManager; - } - - @Override - void onCreateTables(final SQLiteDatabase db) { - LogUtil.w(LogUtil.BUGLE_TAG, "Rebuilt databases: reseting related state"); - // Clear other things that implicitly reference the DB - SyncManager.resetLastSyncTimestamps(); - } - - @Override - public void onActivityResume() { - // Perform an incremental sync and register for changes if necessary - mSyncManager.updateSyncObserver(mContext); - - // Trigger a participant refresh if needed, we should only need to refresh if there is - // contact change while the activity was paused. - ParticipantRefresh.refreshParticipantsIfNeeded(); - } - - @Override - public void onApplicationCreated() { - FixupMessageStatusOnStartupAction.fixupMessageStatus(); - ProcessPendingMessagesAction.processFirstPendingMessage(); - SyncManager.immediateSync(); - - if (OsUtil.isAtLeastL_MR1()) { - // Start listening for subscription change events for refreshing self participants. - PhoneUtils.getDefault().toLMr1().registerOnSubscriptionsChangedListener( - new SubscriptionManager.OnSubscriptionsChangedListener() { - @Override - public void onSubscriptionsChanged() { - // TODO: This dynamically changes the mms config that app is - // currently using. It may cause inconsistency in some cases. We need - // to check the usage of mms config and handle the dynamic change - // gracefully - MmsConfig.loadAsync(); - ParticipantRefresh.refreshSelfParticipants(); - } - }); - } - } -} diff --git a/src/com/android/messaging/datamodel/DatabaseHelper.java b/src/com/android/messaging/datamodel/DatabaseHelper.java deleted file mode 100644 index f16bb3c..0000000 --- a/src/com/android/messaging/datamodel/DatabaseHelper.java +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.provider.BaseColumns; - -import com.android.messaging.BugleApplication; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -/** - * TODO: Open Issues: - * - Should we be storing the draft messages in the regular messages table or should we have a - * separate table for drafts to keep the normal messages query as simple as possible? - */ - -/** - * Allows access to the SQL database. This is package private. - */ -public class DatabaseHelper extends SQLiteOpenHelper { - public static final String DATABASE_NAME = "bugle_db"; - - private static final int getDatabaseVersion(final Context context) { - return Integer.parseInt(context.getResources().getString(R.string.database_version)); - } - - /** Table containing names of all other tables and views */ - private static final String MASTER_TABLE = "sqlite_master"; - /** Column containing the name of the tables and views */ - private static final String[] MASTER_COLUMNS = new String[] { "name", }; - - // Table names - public static final String CONVERSATIONS_TABLE = "conversations"; - public static final String MESSAGES_TABLE = "messages"; - public static final String PARTS_TABLE = "parts"; - public static final String PARTICIPANTS_TABLE = "participants"; - public static final String CONVERSATION_PARTICIPANTS_TABLE = "conversation_participants"; - - // Views - static final String DRAFT_PARTS_VIEW = "draft_parts_view"; - - // Conversations table schema - public static class ConversationColumns implements BaseColumns { - /* SMS/MMS Thread ID from the system provider */ - public static final String SMS_THREAD_ID = "sms_thread_id"; - - /* Display name for the conversation */ - public static final String NAME = "name"; - - /* Latest Message ID for the read status to display in conversation list */ - public static final String LATEST_MESSAGE_ID = "latest_message_id"; - - /* Latest text snippet for display in conversation list */ - public static final String SNIPPET_TEXT = "snippet_text"; - - /* Latest text subject for display in conversation list, empty string if none exists */ - public static final String SUBJECT_TEXT = "subject_text"; - - /* Preview Uri */ - public static final String PREVIEW_URI = "preview_uri"; - - /* The preview uri's content type */ - public static final String PREVIEW_CONTENT_TYPE = "preview_content_type"; - - /* If we should display the current draft snippet/preview pair or snippet/preview pair */ - public static final String SHOW_DRAFT = "show_draft"; - - /* Latest draft text subject for display in conversation list, empty string if none exists*/ - public static final String DRAFT_SUBJECT_TEXT = "draft_subject_text"; - - /* Latest draft text snippet for display, empty string if none exists */ - public static final String DRAFT_SNIPPET_TEXT = "draft_snippet_text"; - - /* Draft Preview Uri, empty string if none exists */ - public static final String DRAFT_PREVIEW_URI = "draft_preview_uri"; - - /* The preview uri's content type */ - public static final String DRAFT_PREVIEW_CONTENT_TYPE = "draft_preview_content_type"; - - /* If this conversation is archived */ - public static final String ARCHIVE_STATUS = "archive_status"; - - /* Timestamp for sorting purposes */ - public static final String SORT_TIMESTAMP = "sort_timestamp"; - - /* Last read message timestamp */ - public static final String LAST_READ_TIMESTAMP = "last_read_timestamp"; - - /* Avatar for the conversation. Could be for group of individual */ - public static final String ICON = "icon"; - - /* Participant contact ID if this conversation has a single participant. -1 otherwise */ - public static final String PARTICIPANT_CONTACT_ID = "participant_contact_id"; - - /* Participant lookup key if this conversation has a single participant. null otherwise */ - public static final String PARTICIPANT_LOOKUP_KEY = "participant_lookup_key"; - - /* - * Participant's normalized destination if this conversation has a single participant. - * null otherwise. - */ - public static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION = - "participant_normalized_destination"; - - /* Default self participant for the conversation */ - public static final String CURRENT_SELF_ID = "current_self_id"; - - /* Participant count not including self (so will be 1 for 1:1 or bigger for group) */ - public static final String PARTICIPANT_COUNT = "participant_count"; - - /* Should notifications be enabled for this conversation? */ - public static final String NOTIFICATION_ENABLED = "notification_enabled"; - - /* Notification sound used for the conversation */ - public static final String NOTIFICATION_SOUND_URI = "notification_sound_uri"; - - /* Should vibrations be enabled for the conversation's notification? */ - public static final String NOTIFICATION_VIBRATION = "notification_vibration"; - - /* Conversation recipients include email address */ - public static final String INCLUDE_EMAIL_ADDRESS = "include_email_addr"; - - // Record the last received sms's service center info if it indicates that the reply path - // is present (TP-Reply-Path), so that we could use it for the subsequent message to send. - // Refer to TS 23.040 D.6 and SmsMessageSender.java in Android Messaging app. - public static final String SMS_SERVICE_CENTER = "sms_service_center"; - } - - // Conversation table SQL - private static final String CREATE_CONVERSATIONS_TABLE_SQL = - "CREATE TABLE " + CONVERSATIONS_TABLE + "(" - + ConversationColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - // TODO : Int? Required not default? - + ConversationColumns.SMS_THREAD_ID + " INT DEFAULT(0), " - + ConversationColumns.NAME + " TEXT, " - + ConversationColumns.LATEST_MESSAGE_ID + " INT, " - + ConversationColumns.SNIPPET_TEXT + " TEXT, " - + ConversationColumns.SUBJECT_TEXT + " TEXT, " - + ConversationColumns.PREVIEW_URI + " TEXT, " - + ConversationColumns.PREVIEW_CONTENT_TYPE + " TEXT, " - + ConversationColumns.SHOW_DRAFT + " INT DEFAULT(0), " - + ConversationColumns.DRAFT_SNIPPET_TEXT + " TEXT, " - + ConversationColumns.DRAFT_SUBJECT_TEXT + " TEXT, " - + ConversationColumns.DRAFT_PREVIEW_URI + " TEXT, " - + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, " - + ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), " - + ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), " - + ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), " - + ConversationColumns.ICON + " TEXT, " - + ConversationColumns.PARTICIPANT_CONTACT_ID + " INT DEFAULT ( " - + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), " - + ConversationColumns.PARTICIPANT_LOOKUP_KEY + " TEXT, " - + ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + " TEXT, " - + ConversationColumns.CURRENT_SELF_ID + " TEXT, " - + ConversationColumns.PARTICIPANT_COUNT + " INT DEFAULT(0), " - + ConversationColumns.NOTIFICATION_ENABLED + " INT DEFAULT(1), " - + ConversationColumns.NOTIFICATION_SOUND_URI + " TEXT, " - + ConversationColumns.NOTIFICATION_VIBRATION + " INT DEFAULT(1), " - + ConversationColumns.INCLUDE_EMAIL_ADDRESS + " INT DEFAULT(0), " - + ConversationColumns.SMS_SERVICE_CENTER + " TEXT " - + ");"; - - private static final String CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL = - "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SMS_THREAD_ID - + " ON " + CONVERSATIONS_TABLE - + "(" + ConversationColumns.SMS_THREAD_ID + ")"; - - private static final String CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL = - "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.ARCHIVE_STATUS - + " ON " + CONVERSATIONS_TABLE - + "(" + ConversationColumns.ARCHIVE_STATUS + ")"; - - private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL = - "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP - + " ON " + CONVERSATIONS_TABLE - + "(" + ConversationColumns.SORT_TIMESTAMP + ")"; - - // Messages table schema - public static class MessageColumns implements BaseColumns { - /* conversation id that this message belongs to */ - public static final String CONVERSATION_ID = "conversation_id"; - - /* participant which send this message */ - public static final String SENDER_PARTICIPANT_ID = "sender_id"; - - /* This is bugle's internal status for the message */ - public static final String STATUS = "message_status"; - - /* Type of message: SMS, MMS or MMS notification */ - public static final String PROTOCOL = "message_protocol"; - - /* This is the time that the sender sent the message */ - public static final String SENT_TIMESTAMP = "sent_timestamp"; - - /* Time that we received the message on this device */ - public static final String RECEIVED_TIMESTAMP = "received_timestamp"; - - /* When the message has been seen by a user in a notification */ - public static final String SEEN = "seen"; - - /* When the message has been read by a user */ - public static final String READ = "read"; - - /* participant representing the sim which processed this message */ - public static final String SELF_PARTICIPANT_ID = "self_id"; - - /* - * Time when a retry is initiated. This is used to compute the retry window - * when we retry sending/downloading a message. - */ - public static final String RETRY_START_TIMESTAMP = "retry_start_timestamp"; - - // Columns which map to the SMS provider - - /* Message ID from the platform provider */ - public static final String SMS_MESSAGE_URI = "sms_message_uri"; - - /* The message priority for MMS message */ - public static final String SMS_PRIORITY = "sms_priority"; - - /* The message size for MMS message */ - public static final String SMS_MESSAGE_SIZE = "sms_message_size"; - - /* The subject for MMS message */ - public static final String MMS_SUBJECT = "mms_subject"; - - /* Transaction id for MMS notificaiton */ - public static final String MMS_TRANSACTION_ID = "mms_transaction_id"; - - /* Content location for MMS notificaiton */ - public static final String MMS_CONTENT_LOCATION = "mms_content_location"; - - /* The expiry time (ms) for MMS message */ - public static final String MMS_EXPIRY = "mms_expiry"; - - /* The detailed status (RESPONSE_STATUS or RETRIEVE_STATUS) for MMS message */ - public static final String RAW_TELEPHONY_STATUS = "raw_status"; - } - - // Messages table SQL - private static final String CREATE_MESSAGES_TABLE_SQL = - "CREATE TABLE " + MESSAGES_TABLE + " (" - + MessageColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + MessageColumns.CONVERSATION_ID + " INT, " - + MessageColumns.SENDER_PARTICIPANT_ID + " INT, " - + MessageColumns.SENT_TIMESTAMP + " INT DEFAULT(0), " - + MessageColumns.RECEIVED_TIMESTAMP + " INT DEFAULT(0), " - + MessageColumns.PROTOCOL + " INT DEFAULT(0), " - + MessageColumns.STATUS + " INT DEFAULT(0), " - + MessageColumns.SEEN + " INT DEFAULT(0), " - + MessageColumns.READ + " INT DEFAULT(0), " - + MessageColumns.SMS_MESSAGE_URI + " TEXT, " - + MessageColumns.SMS_PRIORITY + " INT DEFAULT(0), " - + MessageColumns.SMS_MESSAGE_SIZE + " INT DEFAULT(0), " - + MessageColumns.MMS_SUBJECT + " TEXT, " - + MessageColumns.MMS_TRANSACTION_ID + " TEXT, " - + MessageColumns.MMS_CONTENT_LOCATION + " TEXT, " - + MessageColumns.MMS_EXPIRY + " INT DEFAULT(0), " - + MessageColumns.RAW_TELEPHONY_STATUS + " INT DEFAULT(0), " - + MessageColumns.SELF_PARTICIPANT_ID + " INT, " - + MessageColumns.RETRY_START_TIMESTAMP + " INT DEFAULT(0), " - + "FOREIGN KEY (" + MessageColumns.CONVERSATION_ID + ") REFERENCES " - + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE " - + "FOREIGN KEY (" + MessageColumns.SENDER_PARTICIPANT_ID + ") REFERENCES " - + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL " - + "FOREIGN KEY (" + MessageColumns.SELF_PARTICIPANT_ID + ") REFERENCES " - + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL " - + ");"; - - // Primary sort index for messages table : by conversation id, status, received timestamp. - private static final String MESSAGES_TABLE_SORT_INDEX_SQL = - "CREATE INDEX index_" + MESSAGES_TABLE + "_sort ON " + MESSAGES_TABLE + "(" - + MessageColumns.CONVERSATION_ID + ", " - + MessageColumns.STATUS + ", " - + MessageColumns.RECEIVED_TIMESTAMP + ")"; - - private static final String MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL = - "CREATE INDEX index_" + MESSAGES_TABLE + "_status_seen ON " + MESSAGES_TABLE + "(" - + MessageColumns.STATUS + ", " - + MessageColumns.SEEN + ")"; - - // Parts table schema - // A part may contain text or a media url, but not both. - public static class PartColumns implements BaseColumns { - /* message id that this part belongs to */ - public static final String MESSAGE_ID = "message_id"; - - /* conversation id that this part belongs to */ - public static final String CONVERSATION_ID = "conversation_id"; - - /* text for this part */ - public static final String TEXT = "text"; - - /* content uri for this part */ - public static final String CONTENT_URI = "uri"; - - /* content type for this part */ - public static final String CONTENT_TYPE = "content_type"; - - /* cached width for this part (for layout while loading) */ - public static final String WIDTH = "width"; - - /* cached height for this part (for layout while loading) */ - public static final String HEIGHT = "height"; - - /* de-normalized copy of timestamp from the messages table. This is populated - * via an insert trigger on the parts table. - */ - public static final String TIMESTAMP = "timestamp"; - } - - // Message part table SQL - private static final String CREATE_PARTS_TABLE_SQL = - "CREATE TABLE " + PARTS_TABLE + "(" - + PartColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + PartColumns.MESSAGE_ID + " INT," - + PartColumns.TEXT + " TEXT," - + PartColumns.CONTENT_URI + " TEXT," - + PartColumns.CONTENT_TYPE + " TEXT," - + PartColumns.WIDTH + " INT DEFAULT(" - + MessagingContentProvider.UNSPECIFIED_SIZE + ")," - + PartColumns.HEIGHT + " INT DEFAULT(" - + MessagingContentProvider.UNSPECIFIED_SIZE + ")," - + PartColumns.TIMESTAMP + " INT, " - + PartColumns.CONVERSATION_ID + " INT NOT NULL," - + "FOREIGN KEY (" + PartColumns.MESSAGE_ID + ") REFERENCES " - + MESSAGES_TABLE + "(" + MessageColumns._ID + ") ON DELETE CASCADE " - + "FOREIGN KEY (" + PartColumns.CONVERSATION_ID + ") REFERENCES " - + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE " - + ");"; - - public static final String CREATE_PARTS_TRIGGER_SQL = - "CREATE TRIGGER " + PARTS_TABLE + "_TRIGGER" + " AFTER INSERT ON " + PARTS_TABLE - + " FOR EACH ROW " - + " BEGIN UPDATE " + PARTS_TABLE - + " SET " + PartColumns.TIMESTAMP + "=" - + " (SELECT received_timestamp FROM " + MESSAGES_TABLE + " WHERE " + MESSAGES_TABLE - + "." + MessageColumns._ID + "=" + "NEW." + PartColumns.MESSAGE_ID + ")" - + " WHERE " + PARTS_TABLE + "." + PartColumns._ID + "=" + "NEW." + PartColumns._ID - + "; END"; - - public static final String CREATE_MESSAGES_TRIGGER_SQL = - "CREATE TRIGGER " + MESSAGES_TABLE + "_TRIGGER" + " AFTER UPDATE OF " - + MessageColumns.RECEIVED_TIMESTAMP + " ON " + MESSAGES_TABLE - + " FOR EACH ROW BEGIN UPDATE " + PARTS_TABLE + " SET " + PartColumns.TIMESTAMP - + " = NEW." + MessageColumns.RECEIVED_TIMESTAMP + " WHERE " + PARTS_TABLE + "." - + PartColumns.MESSAGE_ID + " = NEW." + MessageColumns._ID - + "; END;"; - - // Primary sort index for parts table : by message_id - private static final String PARTS_TABLE_MESSAGE_INDEX_SQL = - "CREATE INDEX index_" + PARTS_TABLE + "_message_id ON " + PARTS_TABLE + "(" - + PartColumns.MESSAGE_ID + ")"; - - // Participants table schema - public static class ParticipantColumns implements BaseColumns { - /* The subscription id for the sim associated with this self participant. - * Introduced in L. For earlier versions will always be default_sub_id (-1). - * For multi sim devices (or cases where the sim was changed) single device - * may have several different sub_id values */ - public static final String SUB_ID = "sub_id"; - - /* The slot of the active SIM (inserted in the device) for this self-participant. If the - * self-participant doesn't correspond to any active SIM, this will be - * {@link android.telephony.SubscriptionManager#INVALID_SLOT_ID}. - * The column is ignored for all non-self participants. - */ - public static final String SIM_SLOT_ID = "sim_slot_id"; - - /* The phone number stored in a standard E164 format if possible. This is unique for a - * given participant. We can't handle multiple participants with the same phone number - * since we don't know which of them a message comes from. This can also be an email - * address, in which case this is the same as the displayed address */ - public static final String NORMALIZED_DESTINATION = "normalized_destination"; - - /* The phone number as originally supplied and used for dialing. Not necessarily in E164 - * format or unique */ - public static final String SEND_DESTINATION = "send_destination"; - - /* The user-friendly formatting of the phone number according to the region setting of - * the device when the row was added. */ - public static final String DISPLAY_DESTINATION = "display_destination"; - - /* A string with this participant's full name or a pretty printed phone number */ - public static final String FULL_NAME = "full_name"; - - /* A string with just this participant's first name */ - public static final String FIRST_NAME = "first_name"; - - /* A local URI to an asset for the icon for this participant */ - public static final String PROFILE_PHOTO_URI = "profile_photo_uri"; - - /* Contact id for matching local contact for this participant */ - public static final String CONTACT_ID = "contact_id"; - - /* String that contains hints on how to find contact information in a contact lookup */ - public static final String LOOKUP_KEY = "lookup_key"; - - /* If this participant is blocked */ - public static final String BLOCKED = "blocked"; - - /* The color of the subscription (FOR SELF PARTICIPANTS ONLY) */ - public static final String SUBSCRIPTION_COLOR = "subscription_color"; - - /* The name of the subscription (FOR SELF PARTICIPANTS ONLY) */ - public static final String SUBSCRIPTION_NAME = "subscription_name"; - - /* The exact destination stored in Contacts for this participant */ - public static final String CONTACT_DESTINATION = "contact_destination"; - } - - // Participants table SQL - private static final String CREATE_PARTICIPANTS_TABLE_SQL = - "CREATE TABLE " + PARTICIPANTS_TABLE + "(" - + ParticipantColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + ParticipantColumns.SUB_ID + " INT DEFAULT(" - + ParticipantData.OTHER_THAN_SELF_SUB_ID + ")," - + ParticipantColumns.SIM_SLOT_ID + " INT DEFAULT(" - + ParticipantData.INVALID_SLOT_ID + ")," - + ParticipantColumns.NORMALIZED_DESTINATION + " TEXT," - + ParticipantColumns.SEND_DESTINATION + " TEXT," - + ParticipantColumns.DISPLAY_DESTINATION + " TEXT," - + ParticipantColumns.FULL_NAME + " TEXT," - + ParticipantColumns.FIRST_NAME + " TEXT," - + ParticipantColumns.PROFILE_PHOTO_URI + " TEXT, " - + ParticipantColumns.CONTACT_ID + " INT DEFAULT( " - + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), " - + ParticipantColumns.LOOKUP_KEY + " STRING, " - + ParticipantColumns.BLOCKED + " INT DEFAULT(0), " - + ParticipantColumns.SUBSCRIPTION_NAME + " TEXT, " - + ParticipantColumns.SUBSCRIPTION_COLOR + " INT DEFAULT(0), " - + ParticipantColumns.CONTACT_DESTINATION + " TEXT, " - + "UNIQUE (" + ParticipantColumns.NORMALIZED_DESTINATION + ", " - + ParticipantColumns.SUB_ID + ") ON CONFLICT FAIL" + ");"; - - private static final String CREATE_SELF_PARTICIPANT_SQL = - "INSERT INTO " + PARTICIPANTS_TABLE - + " ( " + ParticipantColumns.SUB_ID + " ) VALUES ( %s )"; - - static String getCreateSelfParticipantSql(int subId) { - return String.format(CREATE_SELF_PARTICIPANT_SQL, subId); - } - - // Conversation Participants table schema - contains a list of participants excluding the user - // in a given conversation. - public static class ConversationParticipantsColumns implements BaseColumns { - /* participant id of someone in this conversation */ - public static final String PARTICIPANT_ID = "participant_id"; - - /* conversation id that this participant belongs to */ - public static final String CONVERSATION_ID = "conversation_id"; - } - - // Conversation Participants table SQL - private static final String CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL = - "CREATE TABLE " + CONVERSATION_PARTICIPANTS_TABLE + "(" - + ConversationParticipantsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + ConversationParticipantsColumns.CONVERSATION_ID + " INT," - + ConversationParticipantsColumns.PARTICIPANT_ID + " INT," - + "UNIQUE (" + ConversationParticipantsColumns.CONVERSATION_ID + "," - + ConversationParticipantsColumns.PARTICIPANT_ID + ") ON CONFLICT FAIL, " - + "FOREIGN KEY (" + ConversationParticipantsColumns.CONVERSATION_ID + ") " - + "REFERENCES " + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ")" - + " ON DELETE CASCADE " - + "FOREIGN KEY (" + ConversationParticipantsColumns.PARTICIPANT_ID + ")" - + " REFERENCES " + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + "));"; - - // Primary access pattern for conversation participants is to look them up for a specific - // conversation. - private static final String CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL = - "CREATE INDEX index_" + CONVERSATION_PARTICIPANTS_TABLE + "_" - + ConversationParticipantsColumns.CONVERSATION_ID - + " ON " + CONVERSATION_PARTICIPANTS_TABLE - + "(" + ConversationParticipantsColumns.CONVERSATION_ID + ")"; - - // View for getting parts which are for draft messages. - static final String DRAFT_PARTS_VIEW_SQL = "CREATE VIEW " + - DRAFT_PARTS_VIEW + " AS SELECT " - + PARTS_TABLE + '.' + PartColumns._ID - + " as " + PartColumns._ID + ", " - + PARTS_TABLE + '.' + PartColumns.MESSAGE_ID - + " as " + PartColumns.MESSAGE_ID + ", " - + PARTS_TABLE + '.' + PartColumns.TEXT - + " as " + PartColumns.TEXT + ", " - + PARTS_TABLE + '.' + PartColumns.CONTENT_URI - + " as " + PartColumns.CONTENT_URI + ", " - + PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE - + " as " + PartColumns.CONTENT_TYPE + ", " - + PARTS_TABLE + '.' + PartColumns.WIDTH - + " as " + PartColumns.WIDTH + ", " - + PARTS_TABLE + '.' + PartColumns.HEIGHT - + " as " + PartColumns.HEIGHT + ", " - + MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID - + " as " + MessageColumns.CONVERSATION_ID + " " - + " FROM " + MESSAGES_TABLE + " LEFT JOIN " + PARTS_TABLE + " ON (" - + MESSAGES_TABLE + "." + MessageColumns._ID - + "=" + PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ")" - // Exclude draft messages from main view - + " WHERE " + MESSAGES_TABLE + "." + MessageColumns.STATUS - + " = " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT; - - // List of all our SQL tables - private static final String[] CREATE_TABLE_SQLS = new String[] { - CREATE_CONVERSATIONS_TABLE_SQL, - CREATE_MESSAGES_TABLE_SQL, - CREATE_PARTS_TABLE_SQL, - CREATE_PARTICIPANTS_TABLE_SQL, - CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL, - }; - - // List of all our indices - private static final String[] CREATE_INDEX_SQLS = new String[] { - CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL, - CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL, - CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL, - MESSAGES_TABLE_SORT_INDEX_SQL, - MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL, - PARTS_TABLE_MESSAGE_INDEX_SQL, - CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL, - }; - - // List of all our SQL triggers - private static final String[] CREATE_TRIGGER_SQLS = new String[] { - CREATE_PARTS_TRIGGER_SQL, - CREATE_MESSAGES_TRIGGER_SQL, - }; - - // List of all our views - private static final String[] CREATE_VIEW_SQLS = new String[] { - ConversationListItemData.getConversationListViewSql(), - ConversationImagePartsView.getCreateSql(), - DRAFT_PARTS_VIEW_SQL, - }; - - private static final Object sLock = new Object(); - private final Context mApplicationContext; - private static DatabaseHelper sHelperInstance; // Protected by sLock. - - private final Object mDatabaseWrapperLock = new Object(); - private DatabaseWrapper mDatabaseWrapper; // Protected by mDatabaseWrapperLock. - private final DatabaseUpgradeHelper mUpgradeHelper = new DatabaseUpgradeHelper(); - - /** - * Get a (singleton) instance of {@link DatabaseHelper}, creating one if there isn't one yet. - * This is the only public method for getting a new instance of the class. - * @param context Should be the application context (or something that will live for the - * lifetime of the application). - * @return The current (or a new) DatabaseHelper instance. - */ - public static DatabaseHelper getInstance(final Context context) { - synchronized (sLock) { - if (sHelperInstance == null) { - sHelperInstance = new DatabaseHelper(context); - } - return sHelperInstance; - } - } - - /** - * Private constructor, used from {@link #getInstance()}. - * @param context Should be the application context (or something that will live for the - * lifetime of the application). - */ - private DatabaseHelper(final Context context) { - super(context, DATABASE_NAME, null, getDatabaseVersion(context), null); - mApplicationContext = context; - } - - /** - * Test method that always instantiates a new DatabaseHelper instance. This should - * be used ONLY by the tests and never by the real application. - * @param context Test context. - * @return Brand new DatabaseHelper instance. - */ - @VisibleForTesting - static DatabaseHelper getNewInstanceForTest(final Context context) { - Assert.isEngBuild(); - Assert.isTrue(BugleApplication.isRunningTests()); - return new DatabaseHelper(context); - } - - /** - * Get the (singleton) instance of @{link DatabaseWrapper}. - *

The database is always opened as a writeable database. - * @return The current (or a new) DatabaseWrapper instance. - */ - @DoesNotRunOnMainThread - DatabaseWrapper getDatabase() { - // We prevent the main UI thread from accessing the database here since we have to allow - // public access to this class to enable sub-packages to access data. - Assert.isNotMainThread(); - - synchronized (mDatabaseWrapperLock) { - if (mDatabaseWrapper == null) { - mDatabaseWrapper = new DatabaseWrapper(mApplicationContext, getWritableDatabase()); - } - return mDatabaseWrapper; - } - } - - @Override - public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - mUpgradeHelper.onDowngrade(db, oldVersion, newVersion); - } - - /** - * Drops and recreates all tables. - */ - public static void rebuildTables(final SQLiteDatabase db) { - // Drop tables first, then views, and indices. - dropAllTables(db); - dropAllViews(db); - dropAllIndexes(db); - dropAllTriggers(db); - - // Recreate the whole database. - createDatabase(db); - } - - /** - * Drop and rebuild a given view. - */ - static void rebuildView(final SQLiteDatabase db, final String viewName, - final String createViewSql) { - dropView(db, viewName, true /* throwOnFailure */); - db.execSQL(createViewSql); - } - - private static void dropView(final SQLiteDatabase db, final String viewName, - final boolean throwOnFailure) { - final String dropPrefix = "DROP VIEW IF EXISTS "; - try { - db.execSQL(dropPrefix + viewName); - } catch (final SQLException ex) { - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop view " + viewName + " " - + ex); - } - - if (throwOnFailure) { - throw ex; - } - } - } - - /** - * Drops all user-defined tables from the given database. - */ - private static void dropAllTables(final SQLiteDatabase db) { - final Cursor tableCursor = - db.query(MASTER_TABLE, MASTER_COLUMNS, "type='table'", null, null, null, null); - if (tableCursor != null) { - try { - final String dropPrefix = "DROP TABLE IF EXISTS "; - while (tableCursor.moveToNext()) { - final String tableName = tableCursor.getString(0); - - // Skip special tables - if (tableName.startsWith("android_") || tableName.startsWith("sqlite_")) { - continue; - } - try { - db.execSQL(dropPrefix + tableName); - } catch (final SQLException ex) { - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop table " + tableName + " " - + ex); - } - } - } - } finally { - tableCursor.close(); - } - } - } - - /** - * Drops all user-defined triggers from the given database. - */ - private static void dropAllTriggers(final SQLiteDatabase db) { - final Cursor triggerCursor = - db.query(MASTER_TABLE, MASTER_COLUMNS, "type='trigger'", null, null, null, null); - if (triggerCursor != null) { - try { - final String dropPrefix = "DROP TRIGGER IF EXISTS "; - while (triggerCursor.moveToNext()) { - final String triggerName = triggerCursor.getString(0); - - // Skip special tables - if (triggerName.startsWith("android_") || triggerName.startsWith("sqlite_")) { - continue; - } - try { - db.execSQL(dropPrefix + triggerName); - } catch (final SQLException ex) { - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop trigger " + triggerName + - " " + ex); - } - } - } - } finally { - triggerCursor.close(); - } - } - } - - /** - * Drops all user-defined views from the given database. - */ - private static void dropAllViews(final SQLiteDatabase db) { - final Cursor viewCursor = - db.query(MASTER_TABLE, MASTER_COLUMNS, "type='view'", null, null, null, null); - if (viewCursor != null) { - try { - while (viewCursor.moveToNext()) { - final String viewName = viewCursor.getString(0); - dropView(db, viewName, false /* throwOnFailure */); - } - } finally { - viewCursor.close(); - } - } - } - - /** - * Drops all user-defined views from the given database. - */ - private static void dropAllIndexes(final SQLiteDatabase db) { - final Cursor indexCursor = - db.query(MASTER_TABLE, MASTER_COLUMNS, "type='index'", null, null, null, null); - if (indexCursor != null) { - try { - final String dropPrefix = "DROP INDEX IF EXISTS "; - while (indexCursor.moveToNext()) { - final String indexName = indexCursor.getString(0); - try { - db.execSQL(dropPrefix + indexName); - } catch (final SQLException ex) { - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop index " + indexName + " " - + ex); - } - } - } - } finally { - indexCursor.close(); - } - } - } - - private static void createDatabase(final SQLiteDatabase db) { - for (final String sql : CREATE_TABLE_SQLS) { - db.execSQL(sql); - } - - for (final String sql : CREATE_INDEX_SQLS) { - db.execSQL(sql); - } - - for (final String sql : CREATE_VIEW_SQLS) { - db.execSQL(sql); - } - - for (final String sql : CREATE_TRIGGER_SQLS) { - db.execSQL(sql); - } - - // Enable foreign key constraints - db.execSQL("PRAGMA foreign_keys=ON;"); - - // Add the default self participant. The default self will be assigned a proper slot id - // during participant refresh. - db.execSQL(getCreateSelfParticipantSql(ParticipantData.DEFAULT_SELF_SUB_ID)); - - DataModel.get().onCreateTables(db); - } - - @Override - public void onCreate(SQLiteDatabase db) { - createDatabase(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - mUpgradeHelper.doOnUpgrade(db, oldVersion, newVersion); - } -} diff --git a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java deleted file mode 100644 index d112533..0000000 --- a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.database.sqlite.SQLiteDatabase; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -public class DatabaseUpgradeHelper { - private static final String TAG = LogUtil.BUGLE_DATABASE_TAG; - - public void doOnUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - Assert.isTrue(newVersion >= oldVersion); - if (oldVersion == newVersion) { - return; - } - - LogUtil.i(TAG, "Database upgrade started from version " + oldVersion + " to " + newVersion); - - // Add future upgrade code here - } - - public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - DatabaseHelper.rebuildTables(db); - LogUtil.e(TAG, "Database downgrade requested for version " + - oldVersion + " version " + newVersion + ", forcing db rebuild!"); - } -} diff --git a/src/com/android/messaging/datamodel/DatabaseWrapper.java b/src/com/android/messaging/datamodel/DatabaseWrapper.java deleted file mode 100644 index ca7a331..0000000 --- a/src/com/android/messaging/datamodel/DatabaseWrapper.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteFullException; -import android.database.sqlite.SQLiteQueryBuilder; -import android.database.sqlite.SQLiteStatement; -import android.util.SparseArray; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; - -import java.util.Locale; -import java.util.Stack; -import java.util.regex.Pattern; - -public class DatabaseWrapper { - private static final String TAG = LogUtil.BUGLE_DATABASE_TAG; - - private final SQLiteDatabase mDatabase; - private final Context mContext; - private final boolean mLog; - /** - * Set mExplainQueryPlanRegexp (via {@link BugleGservicesKeys#EXPLAIN_QUERY_PLAN_REGEXP} - * to regex matching queries to see query plans. For example, ".*" to show all query plans. - */ - // See - private final String mExplainQueryPlanRegexp; - private static final int sTimingThreshold = 50; // in milliseconds - - public static final int INDEX_INSERT_MESSAGE_PART = 0; - public static final int INDEX_INSERT_MESSAGE = 1; - public static final int INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE = 2; - public static final int INDEX_QUERY_MESSAGES_LATEST_MESSAGE = 3; - - private final SparseArray mCompiledStatements; - - static class TransactionData { - long time; - boolean transactionSuccessful; - } - - // track transaction on a per thread basis - private static ThreadLocal> sTransactionDepth = - new ThreadLocal>() { - @Override - public Stack initialValue() { - return new Stack(); - } - }; - - private static String[] sFormatStrings = new String[] { - "took %d ms to %s", - " took %d ms to %s", - " took %d ms to %s", - }; - - DatabaseWrapper(final Context context, final SQLiteDatabase db) { - mLog = LogUtil.isLoggable(LogUtil.BUGLE_DATABASE_PERF_TAG, LogUtil.VERBOSE); - mExplainQueryPlanRegexp = Factory.get().getBugleGservices().getString( - BugleGservicesKeys.EXPLAIN_QUERY_PLAN_REGEXP, null); - mDatabase = db; - mContext = context; - mCompiledStatements = new SparseArray(); - } - - public SQLiteStatement getStatementInTransaction(final int index, final String statement) { - // Use transaction to serialize access to statements - Assert.isTrue(mDatabase.inTransaction()); - SQLiteStatement compiled = mCompiledStatements.get(index); - if (compiled == null) { - compiled = mDatabase.compileStatement(statement); - Assert.isTrue(compiled.toString().contains(statement.trim())); - mCompiledStatements.put(index, compiled); - } - return compiled; - } - - private void maybePlayDebugNoise() { - DebugUtils.maybePlayDebugNoise(mContext, DebugUtils.DEBUG_SOUND_DB_OP); - } - - private static void printTiming(final long t1, final String msg) { - final int transactionDepth = sTransactionDepth.get().size(); - final long t2 = System.currentTimeMillis(); - final long delta = t2 - t1; - if (delta > sTimingThreshold) { - LogUtil.v(LogUtil.BUGLE_DATABASE_PERF_TAG, String.format(Locale.US, - sFormatStrings[Math.min(sFormatStrings.length - 1, transactionDepth)], - delta, - msg)); - } - } - - public Context getContext() { - return mContext; - } - - public void beginTransaction() { - final long t1 = System.currentTimeMillis(); - - // push the current time onto the transaction stack - final TransactionData f = new TransactionData(); - f.time = t1; - sTransactionDepth.get().push(f); - - mDatabase.beginTransaction(); - } - - public void setTransactionSuccessful() { - final TransactionData f = sTransactionDepth.get().peek(); - f.transactionSuccessful = true; - mDatabase.setTransactionSuccessful(); - } - - public void endTransaction() { - long t1 = 0; - long transactionStartTime = 0; - final TransactionData f = sTransactionDepth.get().pop(); - if (f.transactionSuccessful == false) { - LogUtil.w(TAG, "endTransaction without setting successful"); - for (final StackTraceElement st : (new Exception()).getStackTrace()) { - LogUtil.w(TAG, " " + st.toString()); - } - } - if (mLog) { - transactionStartTime = f.time; - t1 = System.currentTimeMillis(); - } - try { - mDatabase.endTransaction(); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to endTransaction", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, - ">>> endTransaction (total for this transaction: %d)", - (System.currentTimeMillis() - transactionStartTime))); - } - } - - public void yieldTransaction() { - long yieldStartTime = 0; - if (mLog) { - yieldStartTime = System.currentTimeMillis(); - } - final boolean wasYielded = mDatabase.yieldIfContendedSafely(); - if (wasYielded && mLog) { - printTiming(yieldStartTime, "yieldTransaction"); - } - } - - public void insertWithOnConflict(final String searchTable, final String nullColumnHack, - final ContentValues initialValues, final int conflictAlgorithm) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - try { - mDatabase.insertWithOnConflict(searchTable, nullColumnHack, initialValues, - conflictAlgorithm); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to insertWithOnConflict", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, - "insertWithOnConflict with ", searchTable)); - } - } - - private void explainQueryPlan(final SQLiteQueryBuilder qb, final SQLiteDatabase db, - final String[] projection, final String selection, - @SuppressWarnings("unused") - final String[] queryArgs, - final String groupBy, - @SuppressWarnings("unused") - final String having, - final String sortOrder, final String limit) { - final String queryString = qb.buildQuery( - projection, - selection, - groupBy, - null/*having*/, - sortOrder, - limit); - explainQueryPlan(db, queryString, queryArgs); - } - - private void explainQueryPlan(final SQLiteDatabase db, final String sql, - final String[] queryArgs) { - if (!Pattern.matches(mExplainQueryPlanRegexp, sql)) { - return; - } - final Cursor planCursor = db.rawQuery("explain query plan " + sql, queryArgs); - try { - if (planCursor != null && planCursor.moveToFirst()) { - final int detailColumn = planCursor.getColumnIndex("detail"); - final StringBuilder sb = new StringBuilder(); - do { - sb.append(planCursor.getString(detailColumn)); - sb.append("\n"); - } while (planCursor.moveToNext()); - if (sb.length() > 0) { - sb.setLength(sb.length() - 1); - } - LogUtil.v(TAG, "for query " + sql + "\nplan is: " - + sb.toString()); - } - } catch (final Exception e) { - LogUtil.w(TAG, "Query plan failed ", e); - } finally { - if (planCursor != null) { - planCursor.close(); - } - } - } - - public Cursor query(final String searchTable, final String[] projection, - final String selection, final String[] selectionArgs, final String groupBy, - final String having, final String orderBy, final String limit) { - if (mExplainQueryPlanRegexp != null) { - final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(searchTable); - explainQueryPlan(qb, mDatabase, projection, selection, selectionArgs, - groupBy, having, orderBy, limit); - } - - maybePlayDebugNoise(); - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - final Cursor cursor = mDatabase.query(searchTable, projection, selection, selectionArgs, - groupBy, having, orderBy, limit); - if (mLog) { - printTiming( - t1, - String.format(Locale.US, "query %s with %s ==> %d", - searchTable, selection, cursor.getCount())); - } - return cursor; - } - - public Cursor query(final String searchTable, final String[] columns, - final String selection, final String[] selectionArgs, final String groupBy, - final String having, final String orderBy) { - return query( - searchTable, columns, selection, selectionArgs, - groupBy, having, orderBy, null); - } - - public Cursor query(final SQLiteQueryBuilder qb, - final String[] projection, final String selection, final String[] queryArgs, - final String groupBy, final String having, final String sortOrder, final String limit) { - if (mExplainQueryPlanRegexp != null) { - explainQueryPlan(qb, mDatabase, projection, selection, queryArgs, - groupBy, having, sortOrder, limit); - } - maybePlayDebugNoise(); - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - final Cursor cursor = qb.query(mDatabase, projection, selection, queryArgs, groupBy, - having, sortOrder, limit); - if (mLog) { - printTiming( - t1, - String.format(Locale.US, "query %s with %s ==> %d", - qb.getTables(), selection, cursor.getCount())); - } - return cursor; - } - - public long queryNumEntries(final String table, final String selection, - final String[] selectionArgs) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - final long retval = - DatabaseUtils.queryNumEntries(mDatabase, table, selection, selectionArgs); - if (mLog){ - printTiming( - t1, - String.format(Locale.US, "queryNumEntries %s with %s ==> %d", table, - selection, retval)); - } - return retval; - } - - public Cursor rawQuery(final String sql, final String[] args) { - if (mExplainQueryPlanRegexp != null) { - explainQueryPlan(mDatabase, sql, args); - } - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - final Cursor cursor = mDatabase.rawQuery(sql, args); - if (mLog) { - printTiming( - t1, - String.format(Locale.US, "rawQuery %s ==> %d", sql, cursor.getCount())); - } - return cursor; - } - - public int update(final String table, final ContentValues values, - final String selection, final String[] selectionArgs) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - int count = 0; - try { - count = mDatabase.update(table, values, selection, selectionArgs); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to update", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, "update %s with %s ==> %d", - table, selection, count)); - } - return count; - } - - public int delete(final String table, final String whereClause, final String[] whereArgs) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - int count = 0; - try { - count = mDatabase.delete(table, whereClause, whereArgs); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to delete", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, - String.format(Locale.US, "delete from %s with %s ==> %d", table, - whereClause, count)); - } - return count; - } - - public long insert(final String table, final String nullColumnHack, - final ContentValues values) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - long rowId = -1; - try { - rowId = mDatabase.insert(table, nullColumnHack, values); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to insert", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, "insert to %s", table)); - } - return rowId; - } - - public long replace(final String table, final String nullColumnHack, - final ContentValues values) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - long rowId = -1; - try { - rowId = mDatabase.replace(table, nullColumnHack, values); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to replace", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, "replace to %s", table)); - } - return rowId; - } - - public void setLocale(final Locale locale) { - mDatabase.setLocale(locale); - } - - public void execSQL(final String sql, final String[] bindArgs) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - try { - mDatabase.execSQL(sql, bindArgs); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to execSQL", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - - if (mLog) { - printTiming(t1, String.format(Locale.US, "execSQL %s", sql)); - } - } - - public void execSQL(final String sql) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - try { - mDatabase.execSQL(sql); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to execSQL", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - - if (mLog) { - printTiming(t1, String.format(Locale.US, "execSQL %s", sql)); - } - } - - public int execSQLUpdateDelete(final String sql) { - long t1 = 0; - if (mLog) { - t1 = System.currentTimeMillis(); - } - maybePlayDebugNoise(); - final SQLiteStatement statement = mDatabase.compileStatement(sql); - int rowsUpdated = 0; - try { - rowsUpdated = statement.executeUpdateDelete(); - } catch (SQLiteFullException ex) { - LogUtil.e(TAG, "Database full, unable to execSQLUpdateDelete", ex); - UiUtils.showToastAtBottom(R.string.db_full); - } - if (mLog) { - printTiming(t1, String.format(Locale.US, "execSQLUpdateDelete %s", sql)); - } - return rowsUpdated; - } - - public SQLiteDatabase getDatabase() { - return mDatabase; - } -} diff --git a/src/com/android/messaging/datamodel/FileProvider.java b/src/com/android/messaging/datamodel/FileProvider.java deleted file mode 100644 index ee332cd..0000000 --- a/src/com/android/messaging/datamodel/FileProvider.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Random; - -/** - * A very simple content provider that can serve files. - */ -public abstract class FileProvider extends ContentProvider { - // Object to generate random id for temp images. - private static final Random RANDOM_ID = new Random(); - - abstract File getFile(final String path, final String extension); - - private static final String FILE_EXTENSION_PARAM_KEY = "ext"; - - /** - * Check if filename conforms to requirement for our provider - * @param fileId filename (optionally starting with path character - * @return true if filename consists only of digits - */ - protected static boolean isValidFileId(final String fileId) { - // Ignore initial "/" - for (int index = (fileId.startsWith("/") ? 1 : 0); index < fileId.length(); index++) { - final Character c = fileId.charAt(index); - if (!Character.isDigit(c)) { - return false; - } - } - return true; - } - - /** - * Create a temp file (to allow writing to that one particular file) - * @param file the file to create - * @return true if file successfully created - */ - protected static boolean ensureFileExists(final File file) { - try { - final File parentDir = file.getParentFile(); - if (parentDir.exists() || parentDir.mkdirs()) { - return file.createNewFile(); - } - } catch (final IOException e) { - // fail on exceptions creating the file - } - return false; - } - - /** - * Build uri for a new temporary file (creating file) - * @param authority authority with which to populate uri - * @param extension optional file extension - * @return unique uri that can be used to write temporary files - */ - protected static Uri buildFileUri(final String authority, final String extension) { - final long fileId = Math.abs(RANDOM_ID.nextLong()); - final Uri.Builder builder = (new Uri.Builder()).authority(authority).scheme( - ContentResolver.SCHEME_CONTENT); - builder.appendPath(String.valueOf(fileId)); - if (!TextUtils.isEmpty(extension)) { - builder.appendQueryParameter(FILE_EXTENSION_PARAM_KEY, extension); - } - return builder.build(); - } - - @Override - public boolean onCreate() { - return true; - } - - @Override - public int delete(final Uri uri, final String selection, final String[] selectionArgs) { - final String fileId = uri.getPath(); - if (isValidFileId(fileId)) { - final File file = getFile(fileId, getExtensionFromUri(uri)); - return file.delete() ? 1 : 0; - } - return 0; - } - - @Override - public ParcelFileDescriptor openFile(final Uri uri, final String fileMode) - throws FileNotFoundException { - final String fileId = uri.getPath(); - if (isValidFileId(fileId)) { - final File file = getFile(fileId, getExtensionFromUri(uri)); - final int mode = - (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY : - ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE); - return ParcelFileDescriptor.open(file, mode); - } - return null; - } - - protected static String getExtensionFromUri(final Uri uri) { - return uri.getQueryParameter(FILE_EXTENSION_PARAM_KEY); - } - - @Override - public Cursor query(final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - // Don't support queries. - return null; - } - - @Override - public Uri insert(final Uri uri, final ContentValues values) { - // Don't support inserts. - return null; - } - - @Override - public int update(final Uri uri, final ContentValues values, final String selection, - final String[] selectionArgs) { - // Don't support updates. - return 0; - } - - @Override - public String getType(final Uri uri) { - // No need for mime types. - return null; - } -} diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java deleted file mode 100644 index 62483a0..0000000 --- a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.database.Cursor; -import android.database.MatrixCursor; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v4.util.SimpleArrayMap; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * A cursor builder that takes the frequent contacts cursor and aggregate it with the all contacts - * cursor to fill in contact details such as phone numbers and strip away invalid contacts. - * - * Because the frequent contact list depends on the loading of two cursors, it needs to temporarily - * store the cursor that it receives with setFrequents() and setAllContacts() calls. Because it - * doesn't know which one will be finished first, it always checks whether both cursors are ready - * to pull data from and construct the aggregate cursor when it's ready to do so. Note that - * this cursor builder doesn't assume ownership of the cursors passed in - it merely references - * them and always does a isClosed() check before consuming them. The ownership still belongs to - * the loader framework and the cursor may be closed when the UI is torn down. - */ -public class FrequentContactsCursorBuilder { - private Cursor mAllContactsCursor; - private Cursor mFrequentContactsCursor; - - /** - * Sets the frequent contacts cursor as soon as it is loaded, or null if it's reset. - * @return this builder instance for chained operations - */ - public FrequentContactsCursorBuilder setFrequents(final Cursor frequentContactsCursor) { - mFrequentContactsCursor = frequentContactsCursor; - return this; - } - - /** - * Sets the all contacts cursor as soon as it is loaded, or null if it's reset. - * @return this builder instance for chained operations - */ - public FrequentContactsCursorBuilder setAllContacts(final Cursor allContactsCursor) { - mAllContactsCursor = allContactsCursor; - return this; - } - - /** - * Reset this builder. Must be called when the consumer resets its data. - */ - public void resetBuilder() { - mAllContactsCursor = null; - mFrequentContactsCursor = null; - } - - /** - * Attempt to build the cursor records from the frequent and all contacts cursor if they - * are both ready to be consumed. - * @return the frequent contact cursor if built successfully, or null if it can't be built yet. - */ - public Cursor build() { - if (mFrequentContactsCursor != null && mAllContactsCursor != null) { - Assert.isTrue(!mFrequentContactsCursor.isClosed()); - Assert.isTrue(!mAllContactsCursor.isClosed()); - - // Frequent contacts cursor has one record per contact, plus it doesn't contain info - // such as phone number and type. In order for the records to be usable by Bugle, we - // would like to populate it with information from the all contacts cursor. - final MatrixCursor retCursor = new MatrixCursor(ContactUtil.PhoneQuery.PROJECTION); - - // First, go through the frequents cursor and take note of all lookup keys and their - // corresponding rank in the frequents list. - final SimpleArrayMap lookupKeyToRankMap = - new SimpleArrayMap(); - int oldPosition = mFrequentContactsCursor.getPosition(); - int rank = 0; - mFrequentContactsCursor.moveToPosition(-1); - while (mFrequentContactsCursor.moveToNext()) { - final String lookupKey = mFrequentContactsCursor.getString( - ContactUtil.INDEX_LOOKUP_KEY_FREQUENT); - lookupKeyToRankMap.put(lookupKey, rank++); - } - mFrequentContactsCursor.moveToPosition(oldPosition); - - // Second, go through the all contacts cursor once and retrieve all information - // (multiple phone numbers etc.) and store that in an array list. Since the all - // contacts list only contains phone contacts, this step will ensure that we filter - // out any invalid/email contacts in the frequents list. - final ArrayList rows = - new ArrayList(mFrequentContactsCursor.getCount()); - oldPosition = mAllContactsCursor.getPosition(); - mAllContactsCursor.moveToPosition(-1); - while (mAllContactsCursor.moveToNext()) { - final String lookupKey = mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY); - if (lookupKeyToRankMap.containsKey(lookupKey)) { - final Object[] row = new Object[ContactUtil.PhoneQuery.PROJECTION.length]; - row[ContactUtil.INDEX_DATA_ID] = - mAllContactsCursor.getLong(ContactUtil.INDEX_DATA_ID); - row[ContactUtil.INDEX_CONTACT_ID] = - mAllContactsCursor.getLong(ContactUtil.INDEX_CONTACT_ID); - row[ContactUtil.INDEX_LOOKUP_KEY] = - mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY); - row[ContactUtil.INDEX_DISPLAY_NAME] = - mAllContactsCursor.getString(ContactUtil.INDEX_DISPLAY_NAME); - row[ContactUtil.INDEX_PHOTO_URI] = - mAllContactsCursor.getString(ContactUtil.INDEX_PHOTO_URI); - row[ContactUtil.INDEX_PHONE_EMAIL] = - mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL); - row[ContactUtil.INDEX_PHONE_EMAIL_TYPE] = - mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE); - row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] = - mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL); - rows.add(row); - } - } - mAllContactsCursor.moveToPosition(oldPosition); - - // Now we have a list of rows containing frequent contacts in alphabetical order. - // Therefore, sort all the rows according to their actual ranks in the frequents list. - Collections.sort(rows, new Comparator() { - @Override - public int compare(final Object[] lhs, final Object[] rhs) { - final String lookupKeyLhs = (String) lhs[ContactUtil.INDEX_LOOKUP_KEY]; - final String lookupKeyRhs = (String) rhs[ContactUtil.INDEX_LOOKUP_KEY]; - Assert.isTrue(lookupKeyToRankMap.containsKey(lookupKeyLhs) && - lookupKeyToRankMap.containsKey(lookupKeyRhs)); - final int rankLhs = lookupKeyToRankMap.get(lookupKeyLhs); - final int rankRhs = lookupKeyToRankMap.get(lookupKeyRhs); - if (rankLhs < rankRhs) { - return -1; - } else if (rankLhs > rankRhs) { - return 1; - } else { - // Same rank, so it's two contact records for the same contact. - // Perform secondary sorting on the phone type. Always place - // mobile before everything else. - final int phoneTypeLhs = (int) lhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE]; - final int phoneTypeRhs = (int) rhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE]; - if (phoneTypeLhs == Phone.TYPE_MOBILE && - phoneTypeRhs == Phone.TYPE_MOBILE) { - return 0; - } else if (phoneTypeLhs == Phone.TYPE_MOBILE) { - return -1; - } else if (phoneTypeRhs == Phone.TYPE_MOBILE) { - return 1; - } else { - // Use the default sort order, i.e. sort by phoneType value. - return phoneTypeLhs < phoneTypeRhs ? -1 : - (phoneTypeLhs == phoneTypeRhs ? 0 : 1); - } - } - } - }); - - // Finally, add all the rows to this cursor. - for (final Object[] row : rows) { - retCursor.addRow(row); - } - return retCursor; - } - return null; - } -} diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java b/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java deleted file mode 100644 index d1759ad..0000000 --- a/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; - -import com.android.messaging.util.FallbackStrategies; -import com.android.messaging.util.FallbackStrategies.Strategy; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -/** - * Helper for querying frequent (and/or starred) contacts. - */ -public class FrequentContactsCursorQueryData extends CursorQueryData { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static class FrequentContactsCursorLoader extends BoundCursorLoader { - private final Uri mOriginalUri; - - FrequentContactsCursorLoader(String bindingId, Context context, Uri uri, - String[] projection, String selection, String[] selectionArgs, String sortOrder) { - super(bindingId, context, uri, projection, selection, selectionArgs, sortOrder); - mOriginalUri = uri; - } - - @Override - public Cursor loadInBackground() { - return FallbackStrategies - .startWith(new PrimaryStrequentContactsQueryStrategy()) - .thenTry(new FrequentOnlyContactsQueryStrategy()) - .thenTry(new PhoneOnlyStrequentContactsQueryStrategy()) - .execute(null); - } - - private abstract class StrequentContactsQueryStrategy implements Strategy { - @Override - public Cursor execute(Void params) throws Exception { - final Uri uri = getUri(); - if (uri != null) { - setUri(uri); - } - return FrequentContactsCursorLoader.super.loadInBackground(); - } - protected abstract Uri getUri(); - } - - private class PrimaryStrequentContactsQueryStrategy extends StrequentContactsQueryStrategy { - @Override - protected Uri getUri() { - // Use the original URI requested. - return mOriginalUri; - } - } - - private class FrequentOnlyContactsQueryStrategy extends StrequentContactsQueryStrategy { - @Override - protected Uri getUri() { - // Some phones have a buggy implementation of the Contacts provider which crashes - // when we query for strequent (starred+frequent) contacts (b/17991485). - // If this happens, switch to just querying for frequent contacts. - return Contacts.CONTENT_FREQUENT_URI; - } - } - - private class PhoneOnlyStrequentContactsQueryStrategy extends - StrequentContactsQueryStrategy { - @Override - protected Uri getUri() { - // Some 3rd party ROMs have content provider - // implementation where invalid SQL queries are returned for regular strequent - // queries. Using strequent_phone_only query as a fallback to display only phone - // contacts. This is the last-ditch effort; if this fails, we will display an - // empty frequent list (b/18354836). - final String strequentQueryParam = OsUtil.isAtLeastL() ? - ContactsContract.STREQUENT_PHONE_ONLY : "strequent_phone_only"; - // TODO: Handle enterprise contacts post M once contacts provider supports it - return Contacts.CONTENT_STREQUENT_URI.buildUpon() - .appendQueryParameter(strequentQueryParam, "true").build(); - } - } - } - - public FrequentContactsCursorQueryData(Context context, String[] projection, - String selection, String[] selectionArgs, String sortOrder) { - // TODO: Handle enterprise contacts post M once contacts provider supports it - super(context, Contacts.CONTENT_STREQUENT_URI, projection, selection, selectionArgs, - sortOrder); - } - - @Override - public BoundCursorLoader createBoundCursorLoader(String bindingId) { - return new FrequentContactsCursorLoader(bindingId, mContext, mUri, mProjection, mSelection, - mSelectionArgs, mSortOrder); - } -} diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java deleted file mode 100644 index 28ec303..0000000 --- a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.net.Uri; -import android.provider.MediaStore.Files; -import android.provider.MediaStore.Files.FileColumns; -import android.provider.MediaStore.Images.Media; - -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.google.common.base.Joiner; - -/** - * A BoundCursorLoader that reads local media on the device. - */ -public class GalleryBoundCursorLoader extends BoundCursorLoader { - public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external"; - private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL); - private static final String SORT_ORDER = Media.DATE_MODIFIED + " DESC"; - private static final String IMAGE_SELECTION = createSelection( - MessagePartData.ACCEPTABLE_IMAGE_TYPES, - new Integer[] { FileColumns.MEDIA_TYPE_IMAGE }); - - public GalleryBoundCursorLoader(final String bindingId, final Context context) { - super(bindingId, context, STORAGE_URI, GalleryGridItemData.IMAGE_PROJECTION, - IMAGE_SELECTION, null, SORT_ORDER); - } - - private static String createSelection(final String[] mimeTypes, Integer[] mediaTypes) { - return Media.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND " - + FileColumns.MEDIA_TYPE + " IN (" + Joiner.on(',').join(mediaTypes) + ")"; - } -} diff --git a/src/com/android/messaging/datamodel/MediaScratchFileProvider.java b/src/com/android/messaging/datamodel/MediaScratchFileProvider.java deleted file mode 100644 index 29ae4f4..0000000 --- a/src/com/android/messaging/datamodel/MediaScratchFileProvider.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MatrixCursor.RowBuilder; -import android.net.Uri; -import android.provider.OpenableColumns; -import android.support.v4.util.SimpleArrayMap; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.util.List; - -/** - * A very simple content provider that can serve media files from our cache directory. - */ -public class MediaScratchFileProvider extends FileProvider { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final SimpleArrayMap sUriToDisplayNameMap = - new SimpleArrayMap(); - - @VisibleForTesting - public static final String AUTHORITY = - "com.android.messaging.datamodel.MediaScratchFileProvider"; - private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace"; - - public static boolean isMediaScratchSpaceUri(final Uri uri) { - if (uri == null) { - return false; - } - - final List segments = uri.getPathSegments(); - return (TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_CONTENT) && - TextUtils.equals(uri.getAuthority(), AUTHORITY) && - segments.size() == 1 && FileProvider.isValidFileId(segments.get(0))); - } - - /** - * Returns a uri that can be used to access a raw mms file. - * - * @return the URI for an raw mms file - */ - public static Uri buildMediaScratchSpaceUri(final String extension) { - final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension); - final File file = getFileWithExtension(uri.getPath(), extension); - if (!ensureFileExists(file)) { - LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath()); - } - return uri; - } - - public static File getFileFromUri(final Uri uri) { - Assert.equals(AUTHORITY, uri.getAuthority()); - return getFileWithExtension(uri.getPath(), getExtensionFromUri(uri)); - } - - public static Uri.Builder getUriBuilder() { - return (new Uri.Builder()).authority(AUTHORITY).scheme(ContentResolver.SCHEME_CONTENT); - } - - @Override - File getFile(final String path, final String extension) { - return getFileWithExtension(path, extension); - } - - private static File getFileWithExtension(final String path, final String extension) { - final Context context = Factory.get().getApplicationContext(); - return new File(getDirectory(context), - TextUtils.isEmpty(extension) ? path : path + "." + extension); - } - - private static File getDirectory(final Context context) { - return new File(context.getCacheDir(), MEDIA_SCRATCH_SPACE_DIR); - } - - @Override - public Cursor query(final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - if (projection != null && projection.length > 0 && - TextUtils.equals(projection[0], OpenableColumns.DISPLAY_NAME) && - isMediaScratchSpaceUri(uri)) { - // Retrieve the display name associated with a temp file. This is used by the Contacts - // ImportVCardActivity to retrieve the name of the contact(s) being imported. - String displayName; - synchronized (sUriToDisplayNameMap) { - displayName = sUriToDisplayNameMap.get(uri); - } - if (!TextUtils.isEmpty(displayName)) { - MatrixCursor cursor = - new MatrixCursor(new String[] { OpenableColumns.DISPLAY_NAME }); - RowBuilder row = cursor.newRow(); - row.add(displayName); - return cursor; - } - } - return null; - } - - public static void addUriToDisplayNameEntry(final Uri scratchFileUri, - final String displayName) { - if (TextUtils.isEmpty(displayName)) { - return; - } - synchronized (sUriToDisplayNameMap) { - sUriToDisplayNameMap.put(scratchFileUri, displayName); - } - } -} diff --git a/src/com/android/messaging/datamodel/MemoryCacheManager.java b/src/com/android/messaging/datamodel/MemoryCacheManager.java deleted file mode 100644 index 0968cff..0000000 --- a/src/com/android/messaging/datamodel/MemoryCacheManager.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import com.android.messaging.Factory; - -import java.util.HashSet; - -/** - * Utility abstraction which allows MemoryCaches in an application to register and then when there - * is memory pressure provide a callback to reclaim the memory in the caches. - */ -public class MemoryCacheManager { - private final HashSet mMemoryCaches = new HashSet(); - private final Object mMemoryCacheLock = new Object(); - - public static MemoryCacheManager get() { - return Factory.get().getMemoryCacheManager(); - } - - /** - * Extend this interface to provide a reclaim method on a memory cache. - */ - public interface MemoryCache { - void reclaim(); - } - - /** - * Register the memory cache with the application. - */ - public void registerMemoryCache(final MemoryCache cache) { - synchronized (mMemoryCacheLock) { - mMemoryCaches.add(cache); - } - } - - /** - * Unregister the memory cache with the application. - */ - public void unregisterMemoryCache(final MemoryCache cache) { - synchronized (mMemoryCacheLock) { - mMemoryCaches.remove(cache); - } - } - - /** - * Reclaim memory in all the memory caches in the application. - */ - @SuppressWarnings("unchecked") - public void reclaimMemory() { - // We're creating a cache copy in the lock to ensure we're not working on a concurrently - // modified set, then reclaim outside of the lock to minimize the time within the lock. - final HashSet shallowCopy; - synchronized (mMemoryCacheLock) { - shallowCopy = (HashSet) mMemoryCaches.clone(); - } - for (final MemoryCache cache : shallowCopy) { - cache.reclaim(); - } - } -} diff --git a/src/com/android/messaging/datamodel/MessageNotificationState.java b/src/com/android/messaging/datamodel/MessageNotificationState.java deleted file mode 100644 index 0bd4aaa..0000000 --- a/src/com/android/messaging/datamodel/MessageNotificationState.java +++ /dev/null @@ -1,1342 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Typeface; -import android.net.Uri; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationCompat.Builder; -import android.support.v4.app.NotificationCompat.WearableExtender; -import android.support.v4.app.NotificationManagerCompat; -import android.text.Html; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.TextAppearanceSpan; -import android.text.style.URLSpan; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.ConversationParticipantsData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.media.VideoThumbnailRequest; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ConversationIdSet; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PendingIntentConstants; -import com.android.messaging.util.UriUtil; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Notification building class for conversation messages. - * - * Message Notifications are built in several stages with several utility classes. - * 1) Perform a database query and fill a data structure with information on messages and - * conversations which need to be notified. - * 2) Based on the data structure choose an appropriate NotificationState subclass to - * represent all the notifications. - * -- For one or more messages in one conversation: MultiMessageNotificationState. - * -- For multiple messages in multiple conversations: MultiConversationNotificationState - * - * A three level structure is used to coalesce the data from the database. From bottom to top: - * 1) NotificationLineInfo - A single message that needs to be notified. - * 2) ConversationLineInfo - A list of NotificationLineInfo in a single conversation. - * 3) ConversationInfoList - A list of ConversationLineInfo and the total number of messages. - * - * The createConversationInfoList function performs the query and creates the data structure. - */ -public abstract class MessageNotificationState extends NotificationState { - // Logging - static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG; - private static final int MAX_MESSAGES_IN_WEARABLE_PAGE = 20; - - private static final int MAX_CHARACTERS_IN_GROUP_NAME = 30; - - private static final int REPLY_INTENT_REQUEST_CODE_OFFSET = 0; - private static final int NUM_EXTRA_REQUEST_CODES_NEEDED = 1; - protected String mTickerSender = null; - protected CharSequence mTickerText = null; - protected String mTitle = null; - protected CharSequence mContent = null; - protected Uri mAttachmentUri = null; - protected String mAttachmentType = null; - protected boolean mTickerNoContent; - - @Override - protected Uri getAttachmentUri() { - return mAttachmentUri; - } - - @Override - protected String getAttachmentType() { - return mAttachmentType; - } - - @Override - public int getIcon() { - return R.drawable.ic_sms_light; - } - - @Override - public int getPriority() { - // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker - // isn't displayed. - return Notification.PRIORITY_HIGH; - } - - /** - * Base class for single notification events for messages. Multiple of these - * may be grouped into a single conversation. - */ - static class NotificationLineInfo { - - final int mNotificationType; - - NotificationLineInfo() { - mNotificationType = BugleNotifications.LOCAL_SMS_NOTIFICATION; - } - - NotificationLineInfo(final int notificationType) { - mNotificationType = notificationType; - } - } - - /** - * Information on a single chat message which should be shown in a notification. - */ - static class MessageLineInfo extends NotificationLineInfo { - final CharSequence mText; - Uri mAttachmentUri; - String mAttachmentType; - final String mAuthorFullName; - final String mAuthorFirstName; - boolean mIsManualDownloadNeeded; - final String mMessageId; - - MessageLineInfo(final boolean isGroup, final String authorFullName, - final String authorFirstName, final CharSequence text, final Uri attachmentUrl, - final String attachmentType, final boolean isManualDownloadNeeded, - final String messageId) { - super(BugleNotifications.LOCAL_SMS_NOTIFICATION); - mAuthorFullName = authorFullName; - mAuthorFirstName = authorFirstName; - mText = text; - mAttachmentUri = attachmentUrl; - mAttachmentType = attachmentType; - mIsManualDownloadNeeded = isManualDownloadNeeded; - mMessageId = messageId; - } - } - - /** - * Information on all the notification messages within a single conversation. - */ - static class ConversationLineInfo { - // Conversation id of the latest message in the notification for this merged conversation. - final String mConversationId; - - // True if this represents a group conversation. - final boolean mIsGroup; - - // Name of the group conversation if available. - final String mGroupConversationName; - - // True if this conversation's recipients includes one or more email address(es) - // (see ConversationColumns.INCLUDE_EMAIL_ADDRESS) - final boolean mIncludeEmailAddress; - - // Timestamp of the latest message - final long mReceivedTimestamp; - - // Self participant id. - final String mSelfParticipantId; - - // List of individual line notifications to be parsed later. - final List mLineInfos; - - // Total number of messages. Might be different that mLineInfos.size() as the number of - // line infos is capped. - int mTotalMessageCount; - - // Custom ringtone if set - final String mRingtoneUri; - - // Should notification be enabled for this conversation? - final boolean mNotificationEnabled; - - // Should notifications vibrate for this conversation? - final boolean mNotificationVibrate; - - // Avatar uri of sender - final Uri mAvatarUri; - - // Contact uri of sender - final Uri mContactUri; - - // Subscription id. - final int mSubId; - - // Number of participants - final int mParticipantCount; - - public ConversationLineInfo(final String conversationId, - final boolean isGroup, - final String groupConversationName, - final boolean includeEmailAddress, - final long receivedTimestamp, - final String selfParticipantId, - final String ringtoneUri, - final boolean notificationEnabled, - final boolean notificationVibrate, - final Uri avatarUri, - final Uri contactUri, - final int subId, - final int participantCount) { - mConversationId = conversationId; - mIsGroup = isGroup; - mGroupConversationName = groupConversationName; - mIncludeEmailAddress = includeEmailAddress; - mReceivedTimestamp = receivedTimestamp; - mSelfParticipantId = selfParticipantId; - mLineInfos = new ArrayList(); - mTotalMessageCount = 0; - mRingtoneUri = ringtoneUri; - mAvatarUri = avatarUri; - mContactUri = contactUri; - mNotificationEnabled = notificationEnabled; - mNotificationVibrate = notificationVibrate; - mSubId = subId; - mParticipantCount = participantCount; - } - - public int getLatestMessageNotificationType() { - final MessageLineInfo messageLineInfo = getLatestMessageLineInfo(); - if (messageLineInfo == null) { - return BugleNotifications.LOCAL_SMS_NOTIFICATION; - } - return messageLineInfo.mNotificationType; - } - - public String getLatestMessageId() { - final MessageLineInfo messageLineInfo = getLatestMessageLineInfo(); - if (messageLineInfo == null) { - return null; - } - return messageLineInfo.mMessageId; - } - - public boolean getDoesLatestMessageNeedDownload() { - final MessageLineInfo messageLineInfo = getLatestMessageLineInfo(); - if (messageLineInfo == null) { - return false; - } - return messageLineInfo.mIsManualDownloadNeeded; - } - - private MessageLineInfo getLatestMessageLineInfo() { - // The latest message is stored at index zero of the message line infos. - if (mLineInfos.size() > 0 && mLineInfos.get(0) instanceof MessageLineInfo) { - return (MessageLineInfo) mLineInfos.get(0); - } - return null; - } - } - - /** - * Information on all the notification messages across all conversations. - */ - public static class ConversationInfoList { - final int mMessageCount; - final List mConvInfos; - public ConversationInfoList(final int count, final List infos) { - mMessageCount = count; - mConvInfos = infos; - } - } - - final ConversationInfoList mConvList; - private long mLatestReceivedTimestamp; - - private static ConversationIdSet makeConversationIdSet(final ConversationInfoList convList) { - ConversationIdSet set = null; - if (convList != null && convList.mConvInfos != null && convList.mConvInfos.size() > 0) { - set = new ConversationIdSet(); - for (final ConversationLineInfo info : convList.mConvInfos) { - set.add(info.mConversationId); - } - } - return set; - } - - protected MessageNotificationState(final ConversationInfoList convList) { - super(makeConversationIdSet(convList)); - mConvList = convList; - mType = PendingIntentConstants.SMS_NOTIFICATION_ID; - mLatestReceivedTimestamp = Long.MIN_VALUE; - if (convList != null) { - for (final ConversationLineInfo info : convList.mConvInfos) { - mLatestReceivedTimestamp = Math.max(mLatestReceivedTimestamp, - info.mReceivedTimestamp); - } - } - } - - @Override - public long getLatestReceivedTimestamp() { - return mLatestReceivedTimestamp; - } - - @Override - public int getNumRequestCodesNeeded() { - // Get additional request codes for the Reply PendingIntent (wearables only) - // and the DND PendingIntent. - return super.getNumRequestCodesNeeded() + NUM_EXTRA_REQUEST_CODES_NEEDED; - } - - private int getBaseExtraRequestCode() { - return mBaseRequestCode + super.getNumRequestCodesNeeded(); - } - - public int getReplyIntentRequestCode() { - return getBaseExtraRequestCode() + REPLY_INTENT_REQUEST_CODE_OFFSET; - } - - @Override - public PendingIntent getClearIntent() { - return UIIntents.get().getPendingIntentForClearingNotifications( - Factory.get().getApplicationContext(), - BugleNotifications.UPDATE_MESSAGES, - mConversationIds, - getClearIntentRequestCode()); - } - - /** - * Notification for multiple messages in at least 2 different conversations. - */ - public static class MultiConversationNotificationState extends MessageNotificationState { - - public final List - mChildren = new ArrayList(); - - public MultiConversationNotificationState( - final ConversationInfoList convList, final MessageNotificationState state) { - super(convList); - mAttachmentUri = null; - mAttachmentType = null; - - // Pull the ticker title/text from the single notification - mTickerSender = state.getTitle(); - mTitle = Factory.get().getApplicationContext().getResources().getQuantityString( - R.plurals.notification_new_messages, - convList.mMessageCount, convList.mMessageCount); - mTickerText = state.mContent; - - // Create child notifications for each conversation, - // which will be displayed (only) on a wearable device. - for (int i = 0; i < convList.mConvInfos.size(); i++) { - final ConversationLineInfo convInfo = convList.mConvInfos.get(i); - if (!(convInfo.mLineInfos.get(0) instanceof MessageLineInfo)) { - continue; - } - setPeopleForConversation(convInfo.mConversationId); - final ConversationInfoList list = new ConversationInfoList( - convInfo.mTotalMessageCount, Lists.newArrayList(convInfo)); - mChildren.add(new BundledMessageNotificationState(list, i)); - } - } - - @Override - public int getIcon() { - return R.drawable.ic_sms_multi_light; - } - - @Override - protected NotificationCompat.Style build(final Builder builder) { - builder.setContentTitle(mTitle); - NotificationCompat.InboxStyle inboxStyle = null; - inboxStyle = new NotificationCompat.InboxStyle(builder); - - final Context context = Factory.get().getApplicationContext(); - // enumeration_comma is defined as ", " - final String separator = context.getString(R.string.enumeration_comma); - final StringBuilder senders = new StringBuilder(); - long when = 0; - for (int i = 0; i < mConvList.mConvInfos.size(); i++) { - final ConversationLineInfo convInfo = mConvList.mConvInfos.get(i); - if (convInfo.mReceivedTimestamp > when) { - when = convInfo.mReceivedTimestamp; - } - String sender; - CharSequence text; - final NotificationLineInfo lineInfo = convInfo.mLineInfos.get(0); - final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfo; - if (convInfo.mIsGroup) { - sender = (convInfo.mGroupConversationName.length() > - MAX_CHARACTERS_IN_GROUP_NAME) ? - truncateGroupMessageName(convInfo.mGroupConversationName) - : convInfo.mGroupConversationName; - } else { - sender = messageLineInfo.mAuthorFullName; - } - text = messageLineInfo.mText; - mAttachmentUri = messageLineInfo.mAttachmentUri; - mAttachmentType = messageLineInfo.mAttachmentType; - - inboxStyle.addLine(BugleNotifications.formatInboxMessage( - sender, text, mAttachmentUri, mAttachmentType)); - if (sender != null) { - if (senders.length() > 0) { - senders.append(separator); - } - senders.append(sender); - } - } - // for collapsed state - mContent = senders; - builder.setContentText(senders) - .setTicker(getTicker()) - .setWhen(when); - - return inboxStyle; - } - } - - /** - * Truncate group conversation name to be displayed in the notifications. This either truncates - * the entire group name or finds the last comma in the available length and truncates the name - * at that point - */ - private static String truncateGroupMessageName(final String conversationName) { - int endIndex = MAX_CHARACTERS_IN_GROUP_NAME; - for (int i = MAX_CHARACTERS_IN_GROUP_NAME; i >= 0; i--) { - // The dividing marker should stay consistent with ConversationListItemData.DIVIDER_TEXT - if (conversationName.charAt(i) == ',') { - endIndex = i; - break; - } - } - return conversationName.substring(0, endIndex) + '\u2026'; - } - - /** - * Notification for multiple messages in a single conversation. Also used if there is a single - * message in a single conversation. - */ - public static class MultiMessageNotificationState extends MessageNotificationState { - - public MultiMessageNotificationState(final ConversationInfoList convList) { - super(convList); - // This conversation has been accepted. - final ConversationLineInfo convInfo = convList.mConvInfos.get(0); - setAvatarUrlsForConversation(convInfo.mConversationId); - setPeopleForConversation(convInfo.mConversationId); - - final Context context = Factory.get().getApplicationContext(); - MessageLineInfo messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0); - // attached photo - mAttachmentUri = messageInfo.mAttachmentUri; - mAttachmentType = messageInfo.mAttachmentType; - mContent = messageInfo.mText; - - if (mAttachmentUri != null) { - // The default attachment type is an image, since that's what was originally - // supported. When there's no content type, assume it's an image. - int message = R.string.notification_picture; - if (ContentType.isAudioType(mAttachmentType)) { - message = R.string.notification_audio; - } else if (ContentType.isVideoType(mAttachmentType)) { - message = R.string.notification_video; - } else if (ContentType.isVCardType(mAttachmentType)) { - message = R.string.notification_vcard; - } - final String attachment = context.getString(message); - final SpannableStringBuilder spanBuilder = new SpannableStringBuilder(); - if (!TextUtils.isEmpty(mContent)) { - spanBuilder.append(mContent).append(System.getProperty("line.separator")); - } - final int start = spanBuilder.length(); - spanBuilder.append(attachment); - spanBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, spanBuilder.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - mContent = spanBuilder; - } - if (convInfo.mIsGroup) { - // When the message is part of a group, the sender's first name - // is prepended to the message, but not for the ticker message. - mTickerText = mContent; - mTickerSender = messageInfo.mAuthorFullName; - // append the bold name to the front of the message - mContent = BugleNotifications.buildSpaceSeparatedMessage( - messageInfo.mAuthorFullName, mContent, mAttachmentUri, - mAttachmentType); - mTitle = convInfo.mGroupConversationName; - } else { - // No matter how many messages there are, since this is a 1:1, just - // get the author full name from the first one. - messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0); - mTitle = messageInfo.mAuthorFullName; - } - } - - @Override - protected NotificationCompat.Style build(final Builder builder) { - builder.setContentTitle(mTitle) - .setTicker(getTicker()); - - NotificationCompat.Style notifStyle = null; - final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0); - final List lineInfos = convInfo.mLineInfos; - final int messageCount = lineInfos.size(); - // At this point, all the messages come from the same conversation. We need to load - // the sender's avatar and then finish building the notification on a callback. - - builder.setContentText(mContent); // for collapsed state - - if (messageCount == 1) { - final boolean shouldShowImage = ContentType.isImageType(mAttachmentType) - || (ContentType.isVideoType(mAttachmentType) - && VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()); - if (mAttachmentUri != null && shouldShowImage) { - // Show "Picture" as the content - final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0); - String authorFirstName = messageLineInfo.mAuthorFirstName; - - // For the collapsed state, just show "picture" unless this is a - // group conversation. If it's a group, show the sender name and - // "picture". - final CharSequence tickerTag = - BugleNotifications.formatAttachmentTag(authorFirstName, - mAttachmentType); - // For 1:1 notifications don't show first name in the notification, but - // do show it in the ticker text - CharSequence pictureTag = tickerTag; - if (!convInfo.mIsGroup) { - authorFirstName = null; - pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName, - mAttachmentType); - } - builder.setContentText(pictureTag); - builder.setTicker(tickerTag); - - notifStyle = new NotificationCompat.BigPictureStyle(builder) - .setSummaryText(BugleNotifications.formatInboxMessage( - authorFirstName, - null, null, - null)); // expanded state, just show sender - } else { - notifStyle = new NotificationCompat.BigTextStyle(builder) - .bigText(mContent); - } - } else { - // We've got multiple messages for the same sender. - // Starting with the oldest new message, display the full text of each message. - // Begin a line for each subsequent message. - final SpannableStringBuilder buf = new SpannableStringBuilder(); - - for (int i = lineInfos.size() - 1; i >= 0; --i) { - final NotificationLineInfo info = lineInfos.get(i); - final MessageLineInfo messageLineInfo = (MessageLineInfo) info; - mAttachmentUri = messageLineInfo.mAttachmentUri; - mAttachmentType = messageLineInfo.mAttachmentType; - CharSequence text = messageLineInfo.mText; - if (!TextUtils.isEmpty(text) || mAttachmentUri != null) { - if (convInfo.mIsGroup) { - // append the bold name to the front of the message - text = BugleNotifications.buildSpaceSeparatedMessage( - messageLineInfo.mAuthorFullName, text, mAttachmentUri, - mAttachmentType); - } else { - text = BugleNotifications.buildSpaceSeparatedMessage( - null, text, mAttachmentUri, mAttachmentType); - } - buf.append(text); - if (i > 0) { - buf.append('\n'); - } - } - } - - // Show a single notification -- big style with the text of all the messages - notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf); - } - builder.setWhen(convInfo.mReceivedTimestamp); - return notifStyle; - } - - } - - private static boolean firstNameUsedMoreThanOnce( - final HashMap map, final String firstName) { - if (map == null) { - return false; - } - if (firstName == null) { - return false; - } - final Integer count = map.get(firstName); - if (count != null) { - return count > 1; - } else { - return false; - } - } - - private static HashMap scanFirstNames(final String conversationId) { - final Context context = Factory.get().getApplicationContext(); - final Uri uri = - MessagingContentProvider.buildConversationParticipantsUri(conversationId); - final Cursor participantsCursor = context.getContentResolver().query( - uri, ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); - final ConversationParticipantsData participantsData = new ConversationParticipantsData(); - participantsData.bind(participantsCursor); - final Iterator iter = participantsData.iterator(); - - final HashMap firstNames = new HashMap(); - boolean seenSelf = false; - while (iter.hasNext()) { - final ParticipantData participant = iter.next(); - // Make sure we only add the self participant once - if (participant.isSelf()) { - if (seenSelf) { - continue; - } else { - seenSelf = true; - } - } - - final String firstName = participant.getFirstName(); - if (firstName == null) { - continue; - } - - final int currentCount = firstNames.containsKey(firstName) - ? firstNames.get(firstName) - : 0; - firstNames.put(firstName, currentCount + 1); - } - return firstNames; - } - - // Essentially, we're building a list of the past 20 messages for this conversation to display - // on the wearable. - public static Notification buildConversationPageForWearable(final String conversationId, - int participantCount) { - final Context context = Factory.get().getApplicationContext(); - - // Limit the number of messages to show. We just want enough to provide context for the - // notification. Fetch one more than we need, so we can tell if there are more messages - // before the one we're showing. - // TODO: in the query, a multipart message will contain a row for each part. - // We might need a smarter GROUP_BY. On the other hand, we might want to show each of the - // parts as separate messages on the wearable. - final int limit = MAX_MESSAGES_IN_WEARABLE_PAGE + 1; - - final List messages = Lists.newArrayList(); - boolean hasSeenMessagesBeforeNotification = false; - Cursor convMessageCursor = null; - try { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String[] queryArgs = { conversationId }; - final String convPageSql = ConversationMessageData.getWearableQuerySql() + " LIMIT " + - limit; - convMessageCursor = db.rawQuery( - convPageSql, - queryArgs); - - if (convMessageCursor == null || !convMessageCursor.moveToFirst()) { - return null; - } - final ConversationMessageData convMessageData = - new ConversationMessageData(); - - final HashMap firstNames = scanFirstNames(conversationId); - do { - convMessageData.bind(convMessageCursor); - - final String authorFullName = convMessageData.getSenderFullName(); - final String authorFirstName = convMessageData.getSenderFirstName(); - String text = convMessageData.getText(); - - final boolean isSmsPushNotification = convMessageData.getIsMmsNotification(); - - // if auto-download was off to show a message to tap to download the message. We - // might need to get that working again. - if (isSmsPushNotification && text != null) { - text = convertHtmlAndStripUrls(text).toString(); - } - // Skip messages without any content - if (TextUtils.isEmpty(text) && !convMessageData.hasAttachments()) { - continue; - } - // Track whether there are messages prior to the one(s) shown in the notification. - if (convMessageData.getIsSeen()) { - hasSeenMessagesBeforeNotification = true; - } - - final boolean usedMoreThanOnce = firstNameUsedMoreThanOnce( - firstNames, authorFirstName); - String displayName = usedMoreThanOnce ? authorFullName : authorFirstName; - if (TextUtils.isEmpty(displayName)) { - if (convMessageData.getIsIncoming()) { - displayName = convMessageData.getSenderDisplayDestination(); - if (TextUtils.isEmpty(displayName)) { - displayName = context.getString(R.string.unknown_sender); - } - } else { - displayName = context.getString(R.string.unknown_self_participant); - } - } - - Uri attachmentUri = null; - String attachmentType = null; - final List attachments = convMessageData.getAttachments(); - for (final MessagePartData messagePartData : attachments) { - // Look for the first attachment that's not the text piece. - if (!messagePartData.isText()) { - attachmentUri = messagePartData.getContentUri(); - attachmentType = messagePartData.getContentType(); - break; - } - } - - final CharSequence message = BugleNotifications.buildSpaceSeparatedMessage( - displayName, text, attachmentUri, attachmentType); - messages.add(message); - - } while (convMessageCursor.moveToNext()); - } finally { - if (convMessageCursor != null) { - convMessageCursor.close(); - } - } - - // If there is no conversation history prior to what is already visible in the main - // notification, there's no need to include the conversation log, too. - final int maxMessagesInNotification = getMaxMessagesInConversationNotification(); - if (!hasSeenMessagesBeforeNotification && messages.size() <= maxMessagesInNotification) { - return null; - } - - final SpannableStringBuilder bigText = new SpannableStringBuilder(); - // There is at least 1 message prior to the first one that we're going to show. - // Indicate this by inserting an ellipsis at the beginning of the conversation log. - if (convMessageCursor.getCount() == limit) { - bigText.append(context.getString(R.string.ellipsis) + "\n\n"); - if (messages.size() > MAX_MESSAGES_IN_WEARABLE_PAGE) { - messages.remove(messages.size() - 1); - } - } - // Messages are sorted in descending timestamp order, so iterate backwards - // to get them back in ascending order for display purposes. - for (int i = messages.size() - 1; i >= 0; --i) { - bigText.append(messages.get(i)); - if (i > 0) { - bigText.append("\n\n"); - } - } - ++participantCount; // Add in myself - - if (participantCount > 2) { - final SpannableString statusText = new SpannableString( - context.getResources().getQuantityString(R.plurals.wearable_participant_count, - participantCount, participantCount)); - statusText.setSpan(new ForegroundColorSpan(context.getResources().getColor( - R.color.wearable_notification_participants_count)), 0, statusText.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - bigText.append("\n\n").append(statusText); - } - - final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context); - final NotificationCompat.Style notifStyle = - new NotificationCompat.BigTextStyle(notifBuilder).bigText(bigText); - notifBuilder.setStyle(notifStyle); - - final WearableExtender wearableExtender = new WearableExtender(); - wearableExtender.setStartScrollBottom(true); - notifBuilder.extend(wearableExtender); - - return notifBuilder.build(); - } - - /** - * Notification for one or more messages in a single conversation, which is bundled together - * with notifications for other conversations on a wearable device. - */ - public static class BundledMessageNotificationState extends MultiMessageNotificationState { - public int mGroupOrder; - public BundledMessageNotificationState(final ConversationInfoList convList, - final int groupOrder) { - super(convList); - mGroupOrder = groupOrder; - } - } - - /** - * Performs a query on the database. - */ - private static ConversationInfoList createConversationInfoList() { - // Map key is conversation id. We use LinkedHashMap to ensure that entries are iterated in - // the same order they were originally added. We scan unseen messages from newest to oldest, - // so the corresponding conversations are added in that order, too. - final Map convLineInfos = new LinkedHashMap<>(); - int messageCount = 0; - - Cursor convMessageCursor = null; - try { - final Context context = Factory.get().getApplicationContext(); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - convMessageCursor = db.rawQuery( - ConversationMessageData.getNotificationQuerySql(), - null); - - if (convMessageCursor != null && convMessageCursor.moveToFirst()) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "MessageNotificationState: Found unseen message notifications."); - } - final ConversationMessageData convMessageData = - new ConversationMessageData(); - - HashMap firstNames = null; - String conversationIdForFirstNames = null; - String groupConversationName = null; - final int maxMessages = getMaxMessagesInConversationNotification(); - - do { - convMessageData.bind(convMessageCursor); - - // First figure out if this is a valid message. - String authorFullName = convMessageData.getSenderFullName(); - String authorFirstName = convMessageData.getSenderFirstName(); - final String messageText = convMessageData.getText(); - - final String convId = convMessageData.getConversationId(); - final String messageId = convMessageData.getMessageId(); - - CharSequence text = messageText; - final boolean isManualDownloadNeeded = convMessageData.getIsMmsNotification(); - if (isManualDownloadNeeded) { - // Don't try and convert the text from html if it's sms and not a sms push - // notification. - Assert.equals(MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD, - convMessageData.getStatus()); - text = context.getResources().getString( - R.string.message_title_manual_download); - } - ConversationLineInfo currConvInfo = convLineInfos.get(convId); - if (currConvInfo == null) { - final ConversationListItemData convData = - ConversationListItemData.getExistingConversation(db, convId); - if (!convData.getNotificationEnabled()) { - // Skip conversations that have notifications disabled. - continue; - } - final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db, - convData.getSelfId()); - groupConversationName = convData.getName(); - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - convMessageData.getSenderProfilePhotoUri(), - convMessageData.getSenderFullName(), - convMessageData.getSenderNormalizedDestination(), - convMessageData.getSenderContactLookupKey()); - currConvInfo = new ConversationLineInfo(convId, - convData.getIsGroup(), - groupConversationName, - convData.getIncludeEmailAddress(), - convMessageData.getReceivedTimeStamp(), - convData.getSelfId(), - convData.getNotificationSoundUri(), - convData.getNotificationEnabled(), - convData.getNotifiationVibrate(), - avatarUri, - convMessageData.getSenderContactLookupUri(), - subId, - convData.getParticipantCount()); - convLineInfos.put(convId, currConvInfo); - } - // Prepare the message line - if (currConvInfo.mTotalMessageCount < maxMessages) { - if (currConvInfo.mIsGroup) { - if (authorFirstName == null) { - // authorFullName might be null as well. In that case, we won't - // show an author. That is better than showing all the group - // names again on the 2nd line. - authorFirstName = authorFullName; - } - } else { - // don't recompute this if we don't need to - if (!TextUtils.equals(conversationIdForFirstNames, convId)) { - firstNames = scanFirstNames(convId); - conversationIdForFirstNames = convId; - } - if (firstNames != null) { - final Integer count = firstNames.get(authorFirstName); - if (count != null && count > 1) { - authorFirstName = authorFullName; - } - } - - if (authorFullName == null) { - authorFullName = groupConversationName; - } - if (authorFirstName == null) { - authorFirstName = groupConversationName; - } - } - final String subjectText = MmsUtils.cleanseMmsSubject( - context.getResources(), - convMessageData.getMmsSubject()); - if (!TextUtils.isEmpty(subjectText)) { - final String subjectLabel = - context.getString(R.string.subject_label); - final SpannableStringBuilder spanBuilder = - new SpannableStringBuilder(); - - spanBuilder.append(context.getString(R.string.notification_subject, - subjectLabel, subjectText)); - spanBuilder.setSpan(new TextAppearanceSpan( - context, R.style.NotificationSubjectText), 0, - subjectLabel.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - if (!TextUtils.isEmpty(text)) { - // Now add the actual message text below the subject header. - spanBuilder.append(System.getProperty("line.separator") + text); - } - text = spanBuilder; - } - // If we've got attachments, find the best one. If one of the messages is - // a photo, save the url so we'll display a big picture notification. - // Otherwise, show the first one we find. - Uri attachmentUri = null; - String attachmentType = null; - final MessagePartData messagePartData = - getMostInterestingAttachment(convMessageData); - if (messagePartData != null) { - attachmentUri = messagePartData.getContentUri(); - attachmentType = messagePartData.getContentType(); - } - currConvInfo.mLineInfos.add(new MessageLineInfo(currConvInfo.mIsGroup, - authorFullName, authorFirstName, text, - attachmentUri, attachmentType, isManualDownloadNeeded, messageId)); - } - messageCount++; - currConvInfo.mTotalMessageCount++; - } while (convMessageCursor.moveToNext()); - } - } finally { - if (convMessageCursor != null) { - convMessageCursor.close(); - } - } - if (convLineInfos.isEmpty()) { - return null; - } else { - return new ConversationInfoList(messageCount, - Lists.newLinkedList(convLineInfos.values())); - } - } - - /** - * Scans all the attachments for a message and returns the most interesting one that we'll - * show in a notification. By order of importance, in case there are multiple attachments: - * 1- an image (because we can show the image as a BigPictureNotification) - * 2- a video (because we can show a video frame as a BigPictureNotification) - * 3- a vcard - * 4- an audio attachment - * @return MessagePartData for the most interesting part. Can be null. - */ - private static MessagePartData getMostInterestingAttachment( - final ConversationMessageData convMessageData) { - final List attachments = convMessageData.getAttachments(); - - MessagePartData imagePart = null; - MessagePartData audioPart = null; - MessagePartData vcardPart = null; - MessagePartData videoPart = null; - - // 99.99% of the time there will be 0 or 1 part, since receiving slideshows is so - // uncommon. - - // Remember the first of each type of part. - for (final MessagePartData messagePartData : attachments) { - if (messagePartData.isImage() && imagePart == null) { - imagePart = messagePartData; - } - if (messagePartData.isVideo() && videoPart == null) { - videoPart = messagePartData; - } - if (messagePartData.isVCard() && vcardPart == null) { - vcardPart = messagePartData; - } - if (messagePartData.isAudio() && audioPart == null) { - audioPart = messagePartData; - } - } - if (imagePart != null) { - return imagePart; - } else if (videoPart != null) { - return videoPart; - } else if (audioPart != null) { - return audioPart; - } else if (vcardPart != null) { - return vcardPart; - } - return null; - } - - private static int getMaxMessagesInConversationNotification() { - if (!BugleNotifications.isWearCompanionAppInstalled()) { - return BugleGservices.get().getInt( - BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION, - BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT); - } - return BugleGservices.get().getInt( - BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE, - BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT); - } - - /** - * Scans the database for messages that need to go into notifications. Creates the appropriate - * MessageNotificationState depending on if there are multiple senders, or - * messages from one sender. - * @return NotificationState for the notification created. - */ - public static NotificationState getNotificationState() { - MessageNotificationState state = null; - final ConversationInfoList convList = createConversationInfoList(); - - if (convList == null || convList.mConvInfos.size() == 0) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "MessageNotificationState: No unseen notifications"); - } - } else { - final ConversationLineInfo convInfo = convList.mConvInfos.get(0); - state = new MultiMessageNotificationState(convList); - - if (convList.mConvInfos.size() > 1) { - // We've got notifications across multiple conversations. Pass in the notification - // we just built of the most recent notification so we can use that to show the - // user the new message in the ticker. - state = new MultiConversationNotificationState(convList, state); - } else { - // For now, only show avatars for notifications for a single conversation. - if (convInfo.mAvatarUri != null) { - if (state.mParticipantAvatarsUris == null) { - state.mParticipantAvatarsUris = new ArrayList(1); - } - state.mParticipantAvatarsUris.add(convInfo.mAvatarUri); - } - if (convInfo.mContactUri != null) { - if (state.mParticipantContactUris == null) { - state.mParticipantContactUris = new ArrayList(1); - } - state.mParticipantContactUris.add(convInfo.mContactUri); - } - } - } - if (state != null && LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "MessageNotificationState: Notification state created" - + ", title = " + LogUtil.sanitizePII(state.mTitle) - + ", content = " + LogUtil.sanitizePII(state.mContent.toString())); - } - return state; - } - - protected String getTitle() { - return mTitle; - } - - @Override - public int getLatestMessageNotificationType() { - // This function is called to determine whether the most recent notification applies - // to an sms conversation or a hangout conversation. We have different ringtone/vibrate - // settings for both types of conversations. - if (mConvList.mConvInfos.size() > 0) { - final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0); - return convInfo.getLatestMessageNotificationType(); - } - return BugleNotifications.LOCAL_SMS_NOTIFICATION; - } - - @Override - public String getRingtoneUri() { - if (mConvList.mConvInfos.size() > 0) { - return mConvList.mConvInfos.get(0).mRingtoneUri; - } - return null; - } - - @Override - public boolean getNotificationVibrate() { - if (mConvList.mConvInfos.size() > 0) { - return mConvList.mConvInfos.get(0).mNotificationVibrate; - } - return false; - } - - protected CharSequence getTicker() { - return BugleNotifications.buildColonSeparatedMessage( - mTickerSender != null ? mTickerSender : mTitle, - mTickerText != null ? mTickerText : (mTickerNoContent ? null : mContent), null, - null); - } - - private static CharSequence convertHtmlAndStripUrls(final String s) { - final Spanned text = Html.fromHtml(s); - if (text instanceof Spannable) { - stripUrls((Spannable) text); - } - return text; - } - - // Since we don't want to show URLs in notifications, a function - // to remove them in place. - private static void stripUrls(final Spannable text) { - final URLSpan[] spans = text.getSpans(0, text.length(), URLSpan.class); - for (final URLSpan span : spans) { - text.removeSpan(span); - } - } - - /* - private static void updateAlertStatusMessages(final long thresholdDeltaMs) { - // TODO may need this when supporting error notifications - final EsDatabaseHelper helper = EsDatabaseHelper.getDatabaseHelper(); - final ContentValues values = new ContentValues(); - final long nowMicros = System.currentTimeMillis() * 1000; - values.put(MessageColumns.ALERT_STATUS, "1"); - final String selection = - MessageColumns.ALERT_STATUS + "=0 AND (" + - MessageColumns.STATUS + "=" + EsProvider.MESSAGE_STATUS_FAILED_TO_SEND + " OR (" + - MessageColumns.STATUS + "!=" + EsProvider.MESSAGE_STATUS_ON_SERVER + " AND " + - MessageColumns.TIMESTAMP + "+" + thresholdDeltaMs*1000 + "<" + nowMicros + ")) "; - - final int updateCount = helper.getWritableDatabaseWrapper().update( - EsProvider.MESSAGES_TABLE, - values, - selection, - null); - if (updateCount > 0) { - EsConversationsData.notifyConversationsChanged(); - } - }*/ - - static CharSequence applyWarningTextColor(final Context context, - final CharSequence text) { - if (text == null) { - return null; - } - final SpannableStringBuilder spanBuilder = new SpannableStringBuilder(); - spanBuilder.append(text); - spanBuilder.setSpan(new ForegroundColorSpan(context.getResources().getColor( - R.color.notification_warning_color)), 0, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return spanBuilder; - } - - /** - * Check for failed messages and post notifications as needed. - * TODO: Rewrite this as a NotificationState. - */ - public static void checkFailedMessages() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final Cursor messageDataCursor = db.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), - FailedMessageQuery.FAILED_MESSAGES_WHERE_CLAUSE, - null /*selectionArgs*/, - null /*groupBy*/, - null /*having*/, - FailedMessageQuery.FAILED_ORDER_BY); - - try { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(context); - if (messageDataCursor != null) { - final MessageData messageData = new MessageData(); - - final HashSet conversationsWithFailedMessages = new HashSet(); - - // track row ids in case we want to display something that requires this - // information - final ArrayList failedMessages = new ArrayList(); - - int cursorPosition = -1; - final long when = 0; - - messageDataCursor.moveToPosition(-1); - while (messageDataCursor.moveToNext()) { - messageData.bind(messageDataCursor); - - final String conversationId = messageData.getConversationId(); - if (DataModel.get().isNewMessageObservable(conversationId)) { - // Don't post a system notification for an observable conversation - // because we already show an angry red annotation in the conversation - // itself or in the conversation preview snippet. - continue; - } - - cursorPosition = messageDataCursor.getPosition(); - failedMessages.add(cursorPosition); - conversationsWithFailedMessages.add(conversationId); - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Found " + failedMessages.size() + " failed messages"); - } - if (failedMessages.size() > 0) { - final NotificationCompat.Builder builder = - new NotificationCompat.Builder(context); - - CharSequence line1; - CharSequence line2; - final boolean isRichContent = false; - ConversationIdSet conversationIds = null; - PendingIntent destinationIntent; - if (failedMessages.size() == 1) { - messageDataCursor.moveToPosition(cursorPosition); - messageData.bind(messageDataCursor); - final String conversationId = messageData.getConversationId(); - - // We have a single conversation, go directly to that conversation. - destinationIntent = UIIntents.get() - .getPendingIntentForConversationActivity(context, - conversationId, - null /*draft*/); - - conversationIds = ConversationIdSet.createSet(conversationId); - - final String failedMessgeSnippet = messageData.getMessageText(); - int failureStringId; - if (messageData.getStatus() == - MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) { - failureStringId = - R.string.notification_download_failures_line1_singular; - } else { - failureStringId = R.string.notification_send_failures_line1_singular; - } - line1 = resources.getString(failureStringId); - line2 = failedMessgeSnippet; - // Set rich text for non-SMS messages or MMS push notification messages - // which we generate locally with rich text - // TODO- fix this -// if (messageData.isMmsInd()) { -// isRichContent = true; -// } - } else { - // We have notifications for multiple conversation, go to the conversation - // list. - destinationIntent = UIIntents.get() - .getPendingIntentForConversationListActivity(context); - - int line1StringId; - int line2PluralsId; - if (messageData.getStatus() == - MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) { - line1StringId = - R.string.notification_download_failures_line1_plural; - line2PluralsId = R.plurals.notification_download_failures; - } else { - line1StringId = R.string.notification_send_failures_line1_plural; - line2PluralsId = R.plurals.notification_send_failures; - } - line1 = resources.getString(line1StringId); - line2 = resources.getQuantityString( - line2PluralsId, - conversationsWithFailedMessages.size(), - failedMessages.size(), - conversationsWithFailedMessages.size()); - } - line1 = applyWarningTextColor(context, line1); - line2 = applyWarningTextColor(context, line2); - - final PendingIntent pendingIntentForDelete = - UIIntents.get().getPendingIntentForClearingNotifications( - context, - BugleNotifications.UPDATE_ERRORS, - conversationIds, - 0); - - builder - .setContentTitle(line1) - .setTicker(line1) - .setWhen(when > 0 ? when : System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_failed_light) - .setDeleteIntent(pendingIntentForDelete) - .setContentIntent(destinationIntent) - .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure)); - if (isRichContent && !TextUtils.isEmpty(line2)) { - final NotificationCompat.InboxStyle inboxStyle = - new NotificationCompat.InboxStyle(builder); - if (line2 != null) { - inboxStyle.addLine(Html.fromHtml(line2.toString())); - } - builder.setStyle(inboxStyle); - } else { - builder.setContentText(line2); - } - - if (builder != null) { - notificationManager.notify( - BugleNotifications.buildNotificationTag( - PendingIntentConstants.MSG_SEND_ERROR, null), - PendingIntentConstants.MSG_SEND_ERROR, - builder.build()); - } - } else { - notificationManager.cancel( - BugleNotifications.buildNotificationTag( - PendingIntentConstants.MSG_SEND_ERROR, null), - PendingIntentConstants.MSG_SEND_ERROR); - } - } - } finally { - if (messageDataCursor != null) { - messageDataCursor.close(); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/MessageTextStats.java b/src/com/android/messaging/datamodel/MessageTextStats.java deleted file mode 100644 index 2bd24ff..0000000 --- a/src/com/android/messaging/datamodel/MessageTextStats.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.telephony.SmsMessage; - -import com.android.messaging.sms.MmsConfig; - -public class MessageTextStats { - private boolean mMessageLengthRequiresMms; - private int mMessageCount; - private int mCodePointsRemainingInCurrentMessage; - - public MessageTextStats() { - mCodePointsRemainingInCurrentMessage = Integer.MAX_VALUE; - } - - public int getNumMessagesToBeSent() { - return mMessageCount; - } - - public int getCodePointsRemainingInCurrentMessage() { - return mCodePointsRemainingInCurrentMessage; - } - - public boolean getMessageLengthRequiresMms() { - return mMessageLengthRequiresMms; - } - - public void updateMessageTextStats(final int selfSubId, final String messageText) { - final int[] params = SmsMessage.calculateLength(messageText, false); - /* SmsMessage.calculateLength returns an int[4] with: - * int[0] being the number of SMS's required, - * int[1] the number of code points used, - * int[2] is the number of code points remaining until the next message. - * int[3] is the encoding type that should be used for the message. - */ - mMessageCount = params[0]; - mCodePointsRemainingInCurrentMessage = params[2]; - - final MmsConfig mmsConfig = MmsConfig.get(selfSubId); - if (!mmsConfig.getMultipartSmsEnabled() && - !mmsConfig.getSendMultipartSmsAsSeparateMessages()) { - // The provider doesn't support multi-part sms's and we should use MMS to - // send multi-part sms, so as soon as the user types - // an sms longer than one segment, we have to turn the message into an mms. - mMessageLengthRequiresMms = mMessageCount > 1; - } else { - final int threshold = mmsConfig.getSmsToMmsTextThreshold(); - mMessageLengthRequiresMms = threshold > 0 && mMessageCount > threshold; - } - // Some carriers require any SMS message longer than 80 to be sent as MMS - // see b/12122333 - int smsToMmsLengthThreshold = mmsConfig.getSmsToMmsTextLengthThreshold(); - if (smsToMmsLengthThreshold > 0) { - final int usedInCurrentMessage = params[1]; - /* - * A little hacky way to find out if we should count characters in double bytes. - * SmsMessage.calculateLength counts message code units based on the characters - * in input. If all of them are ascii, the max length is - * SmsMessage.MAX_USER_DATA_SEPTETS (160). If any of them are double-byte, like - * Korean or Chinese, the max length is SmsMessage.MAX_USER_DATA_BYTES (140) bytes - * (70 code units). - * Here we check if the total code units we can use is smaller than 140. If so, - * we know we should count threshold in double-byte, so divide the threshold by 2. - * In this way, we will count Korean text correctly with regard to the length threshold. - */ - if (usedInCurrentMessage + mCodePointsRemainingInCurrentMessage - < SmsMessage.MAX_USER_DATA_BYTES) { - smsToMmsLengthThreshold /= 2; - } - if (usedInCurrentMessage > smsToMmsLengthThreshold) { - mMessageLengthRequiresMms = true; - } - } - } - -} diff --git a/src/com/android/messaging/datamodel/MessagingContentProvider.java b/src/com/android/messaging/datamodel/MessagingContentProvider.java deleted file mode 100644 index 7688abd..0000000 --- a/src/com/android/messaging/datamodel/MessagingContentProvider.java +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; - -import com.android.messaging.BugleApplication; -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.widget.BugleWidgetProvider; -import com.android.messaging.widget.WidgetConversationProvider; -import com.google.common.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.PrintWriter; - -/** - * A centralized provider for Uris exposed by Bugle. - * */ -public class MessagingContentProvider extends ContentProvider { - private static final String TAG = LogUtil.BUGLE_TAG; - - @VisibleForTesting - public static final String AUTHORITY = - "com.android.messaging.datamodel.MessagingContentProvider"; - private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/'; - - // Conversations query - private static final String CONVERSATIONS_QUERY = "conversations"; - - public static final Uri CONVERSATIONS_URI = Uri.parse(CONTENT_AUTHORITY + CONVERSATIONS_QUERY); - static final Uri PARTS_URI = Uri.parse(CONTENT_AUTHORITY + DatabaseHelper.PARTS_TABLE); - - // Messages query - private static final String MESSAGES_QUERY = "messages"; - - static final Uri MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY + MESSAGES_QUERY); - - public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY + - MESSAGES_QUERY + "/conversation"); - - // Conversation participants query - private static final String PARTICIPANTS_QUERY = "participants"; - - static class ConversationParticipantsQueryColumns extends ParticipantColumns { - static final String CONVERSATION_ID = ConversationParticipantsColumns.CONVERSATION_ID; - } - - static final Uri CONVERSATION_PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY + - PARTICIPANTS_QUERY + "/conversation"); - - public static final Uri PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY + PARTICIPANTS_QUERY); - - // Conversation images query - private static final String CONVERSATION_IMAGES_QUERY = "conversation_images"; - - public static final Uri CONVERSATION_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY + - CONVERSATION_IMAGES_QUERY); - - private static final String DRAFT_IMAGES_QUERY = "draft_images"; - - public static final Uri DRAFT_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY + - DRAFT_IMAGES_QUERY); - - /** - * Notifies that all data exposed by the provider needs to be refreshed. - *

- * IMPORTANT! You probably shouldn't be calling this. Prefer to notify more specific - * uri's instead. Currently only sync uses this, because sync can potentially update many - * different tables at once. - */ - public static void notifyEverythingChanged() { - final Uri uri = Uri.parse(CONTENT_AUTHORITY); - final Context context = Factory.get().getApplicationContext(); - final ContentResolver cr = context.getContentResolver(); - cr.notifyChange(uri, null); - - // Notify any conversations widgets the conversation list has changed. - BugleWidgetProvider.notifyConversationListChanged(context); - - // Notify all conversation widgets to update. - WidgetConversationProvider.notifyMessagesChanged(context, null /*conversationId*/); - } - - /** - * Build a participant uri from the conversation id. - */ - public static Uri buildConversationParticipantsUri(final String conversationId) { - final Uri.Builder builder = CONVERSATION_PARTICIPANTS_URI.buildUpon(); - builder.appendPath(conversationId); - return builder.build(); - } - - public static void notifyParticipantsChanged(final String conversationId) { - final Uri uri = buildConversationParticipantsUri(conversationId); - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - cr.notifyChange(uri, null); - } - - public static void notifyAllMessagesChanged() { - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - cr.notifyChange(CONVERSATION_MESSAGES_URI, null); - } - - public static void notifyAllParticipantsChanged() { - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - cr.notifyChange(CONVERSATION_PARTICIPANTS_URI, null); - } - - // Default value for unknown dimension of image - public static final int UNSPECIFIED_SIZE = -1; - - // Internal - private static final int CONVERSATIONS_QUERY_CODE = 10; - - private static final int CONVERSATION_QUERY_CODE = 20; - private static final int CONVERSATION_MESSAGES_QUERY_CODE = 30; - private static final int CONVERSATION_PARTICIPANTS_QUERY_CODE = 40; - private static final int CONVERSATION_IMAGES_QUERY_CODE = 50; - private static final int DRAFT_IMAGES_QUERY_CODE = 60; - private static final int PARTICIPANTS_QUERY_CODE = 70; - - // TODO: Move to a better structured URI namespace. - private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); - static { - sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY, CONVERSATIONS_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY + "/*", CONVERSATION_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*", - CONVERSATION_MESSAGES_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*", - CONVERSATION_PARTICIPANTS_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, CONVERSATION_IMAGES_QUERY + "/*", - CONVERSATION_IMAGES_QUERY_CODE); - sURIMatcher.addURI(AUTHORITY, DRAFT_IMAGES_QUERY + "/*", - DRAFT_IMAGES_QUERY_CODE); - } - - /** - * Build a messages uri from the conversation id. - */ - public static Uri buildConversationMessagesUri(final String conversationId) { - final Uri.Builder builder = CONVERSATION_MESSAGES_URI.buildUpon(); - builder.appendPath(conversationId); - return builder.build(); - } - - public static void notifyMessagesChanged(final String conversationId) { - final Uri uri = buildConversationMessagesUri(conversationId); - final Context context = Factory.get().getApplicationContext(); - final ContentResolver cr = context.getContentResolver(); - cr.notifyChange(uri, null); - notifyConversationListChanged(); - - // Notify the widget the messages changed - WidgetConversationProvider.notifyMessagesChanged(context, conversationId); - } - - /** - * Build a conversation metadata uri from a conversation id. - */ - public static Uri buildConversationMetadataUri(final String conversationId) { - final Uri.Builder builder = CONVERSATIONS_URI.buildUpon(); - builder.appendPath(conversationId); - return builder.build(); - } - - public static void notifyConversationMetadataChanged(final String conversationId) { - final Uri uri = buildConversationMetadataUri(conversationId); - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - cr.notifyChange(uri, null); - notifyConversationListChanged(); - } - - public static void notifyPartsChanged() { - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - cr.notifyChange(PARTS_URI, null); - } - - public static void notifyConversationListChanged() { - final Context context = Factory.get().getApplicationContext(); - final ContentResolver cr = context.getContentResolver(); - cr.notifyChange(CONVERSATIONS_URI, null); - - // Notify the widget the conversation list changed - BugleWidgetProvider.notifyConversationListChanged(context); - } - - /** - * Build a conversation images uri from a conversation id. - */ - public static Uri buildConversationImagesUri(final String conversationId) { - final Uri.Builder builder = CONVERSATION_IMAGES_URI.buildUpon(); - builder.appendPath(conversationId); - return builder.build(); - } - - /** - * Build a draft images uri from a conversation id. - */ - public static Uri buildDraftImagesUri(final String conversationId) { - final Uri.Builder builder = DRAFT_IMAGES_URI.buildUpon(); - builder.appendPath(conversationId); - return builder.build(); - } - - private DatabaseHelper mDatabaseHelper; - private DatabaseWrapper mDatabaseWrapper; - - public MessagingContentProvider() { - super(); - } - - @VisibleForTesting - public void setDatabaseForTest(final DatabaseWrapper db) { - Assert.isTrue(BugleApplication.isRunningTests()); - mDatabaseWrapper = db; - } - - private DatabaseWrapper getDatabaseWrapper() { - if (mDatabaseWrapper == null) { - mDatabaseWrapper = mDatabaseHelper.getDatabase(); - } - return mDatabaseWrapper; - } - - @Override - public Cursor query(final Uri uri, final String[] projection, String selection, - final String[] selectionArgs, String sortOrder) { - - // Processes other than self are allowed to temporarily access the media - // scratch space; we grant uri read access on a case-by-case basis. Dialer app and - // contacts app would doQuery() on the vCard uri before trying to open the inputStream. - // There's nothing that we need to return for this uri so just No-Op. - //if (isMediaScratchSpaceUri(uri)) { - // return null; - //} - - final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - - String[] queryArgs = selectionArgs; - final int match = sURIMatcher.match(uri); - String groupBy = null; - String limit = null; - switch (match) { - case CONVERSATIONS_QUERY_CODE: - queryBuilder.setTables(ConversationListItemData.getConversationListView()); - // Hide empty conversations (ones with 0 sort_timestamp) - queryBuilder.appendWhere(ConversationColumns.SORT_TIMESTAMP + " > 0 "); - break; - case CONVERSATION_QUERY_CODE: - queryBuilder.setTables(ConversationListItemData.getConversationListView()); - if (uri.getPathSegments().size() == 2) { - queryBuilder.appendWhere(ConversationColumns._ID + "=?"); - // Get the conversation id from the uri - queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1)); - } else { - throw new IllegalArgumentException("Malformed URI " + uri); - } - break; - case CONVERSATION_PARTICIPANTS_QUERY_CODE: - queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE); - if (uri.getPathSegments().size() == 3 && - TextUtils.equals(uri.getPathSegments().get(1), "conversation")) { - queryBuilder.appendWhere(ParticipantColumns._ID + " IN ( " + "SELECT " - + ConversationParticipantsColumns.PARTICIPANT_ID + " AS " - + ParticipantColumns._ID - + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE - + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID - + " =? UNION SELECT " + ParticipantColumns._ID + " FROM " - + DatabaseHelper.PARTICIPANTS_TABLE + " WHERE " - + ParticipantColumns.SUB_ID + " != " - + ParticipantData.OTHER_THAN_SELF_SUB_ID + " )"); - // Get the conversation id from the uri - queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(2)); - } else { - throw new IllegalArgumentException("Malformed URI " + uri); - } - break; - case PARTICIPANTS_QUERY_CODE: - queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE); - if (uri.getPathSegments().size() != 1) { - throw new IllegalArgumentException("Malformed URI " + uri); - } - break; - case CONVERSATION_MESSAGES_QUERY_CODE: - if (uri.getPathSegments().size() == 3 && - TextUtils.equals(uri.getPathSegments().get(1), "conversation")) { - // Get the conversation id from the uri - final String conversationId = uri.getPathSegments().get(2); - - // We need to handle this query differently, instead of falling through to the - // generic query call at the bottom. For performance reasons, the conversation - // messages query is executed as a raw query. It is invalid to specify - // selection/sorting for this query. - - if (selection == null && selectionArgs == null && sortOrder == null) { - return queryConversationMessages(conversationId, uri); - } else { - throw new IllegalArgumentException( - "Cannot set selection or sort order with this query"); - } - } else { - throw new IllegalArgumentException("Malformed URI " + uri); - } - case CONVERSATION_IMAGES_QUERY_CODE: - queryBuilder.setTables(ConversationImagePartsView.getViewName()); - if (uri.getPathSegments().size() == 2) { - // Exclude draft. - queryBuilder.appendWhere( - ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " + - ConversationImagePartsView.Columns.STATUS + "<>" + - MessageData.BUGLE_STATUS_OUTGOING_DRAFT); - // Get the conversation id from the uri - queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1)); - } else { - throw new IllegalArgumentException("Malformed URI " + uri); - } - break; - case DRAFT_IMAGES_QUERY_CODE: - queryBuilder.setTables(ConversationImagePartsView.getViewName()); - if (uri.getPathSegments().size() == 2) { - // Draft only. - queryBuilder.appendWhere( - ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " + - ConversationImagePartsView.Columns.STATUS + "=" + - MessageData.BUGLE_STATUS_OUTGOING_DRAFT); - // Get the conversation id from the uri - queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1)); - } else { - throw new IllegalArgumentException("Malformed URI " + uri); - } - break; - default: { - throw new IllegalArgumentException("Unknown URI " + uri); - } - } - - final Cursor cursor = getDatabaseWrapper().query(queryBuilder, projection, selection, - queryArgs, groupBy, null, sortOrder, limit); - cursor.setNotificationUri(getContext().getContentResolver(), uri); - return cursor; - } - - private Cursor queryConversationMessages(final String conversationId, final Uri notifyUri) { - final String[] queryArgs = { conversationId }; - final Cursor cursor = getDatabaseWrapper().rawQuery( - ConversationMessageData.getConversationMessagesQuerySql(), queryArgs); - cursor.setNotificationUri(getContext().getContentResolver(), notifyUri); - return cursor; - } - - @Override - public String getType(final Uri uri) { - final StringBuilder sb = new - StringBuilder("vnd.android.cursor.dir/vnd.android.messaging."); - - switch (sURIMatcher.match(uri)) { - case CONVERSATIONS_QUERY_CODE: { - sb.append(CONVERSATIONS_QUERY); - break; - } - default: { - throw new IllegalArgumentException("Unknown URI: " + uri); - } - } - return sb.toString(); - } - - protected DatabaseHelper getDatabase() { - return DatabaseHelper.getInstance(getContext()); - } - - @Override - public ParcelFileDescriptor openFile(final Uri uri, final String fileMode) - throws FileNotFoundException { - throw new IllegalArgumentException("openFile not supported: " + uri); - } - - @Override - public Uri insert(final Uri uri, final ContentValues values) { - throw new IllegalStateException("Insert not supported " + uri); - } - - @Override - public int delete(final Uri uri, final String selection, final String[] selectionArgs) { - throw new IllegalArgumentException("Delete not supported: " + uri); - } - - @Override - public int update(final Uri uri, final ContentValues values, final String selection, - final String[] selectionArgs) { - throw new IllegalArgumentException("Update not supported: " + uri); - } - - /** - * Prepends new arguments to the existing argument list. - * - * @param oldArgList The current list of arguments. May be {@code null} - * @param args The new arguments to prepend - * @return A new argument list with the given arguments prepended - */ - private String[] prependArgs(final String[] oldArgList, final String... args) { - if (args == null || args.length == 0) { - return oldArgList; - } - final int oldArgCount = (oldArgList == null ? 0 : oldArgList.length); - final int newArgCount = args.length; - - final String[] newArgs = new String[oldArgCount + newArgCount]; - System.arraycopy(args, 0, newArgs, 0, newArgCount); - if (oldArgCount > 0) { - System.arraycopy(oldArgList, 0, newArgs, newArgCount, oldArgCount); - } - return newArgs; - } - /** - * {@inheritDoc} - */ - @Override - public void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) { - // First dump out the default SMS app package name - String defaultSmsApp = PhoneUtils.getDefault().getDefaultSmsApp(); - if (TextUtils.isEmpty(defaultSmsApp)) { - if (OsUtil.isAtLeastKLP()) { - defaultSmsApp = "None"; - } else { - defaultSmsApp = "None (pre-Kitkat)"; - } - } - writer.println("Default SMS app: " + defaultSmsApp); - // Now dump logs - LogUtil.dump(writer); - } - - @Override - public boolean onCreate() { - // This is going to wind up calling into createDatabase() below. - mDatabaseHelper = (DatabaseHelper) getDatabase(); - // We cannot initialize mDatabaseWrapper yet as the Factory may not be initialized - return true; - } -} diff --git a/src/com/android/messaging/datamodel/MmsFileProvider.java b/src/com/android/messaging/datamodel/MmsFileProvider.java deleted file mode 100644 index 0022630..0000000 --- a/src/com/android/messaging/datamodel/MmsFileProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.Factory; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; - -/** - * A very simple content provider that can serve mms files from our cache directory. - */ -public class MmsFileProvider extends FileProvider { - private static final String TAG = LogUtil.BUGLE_TAG; - - @VisibleForTesting - static final String AUTHORITY = "com.android.messaging.datamodel.MmsFileProvider"; - private static final String RAW_MMS_DIR = "rawmms"; - - /** - * Returns a uri that can be used to access a raw mms file. - * - * @return the URI for an raw mms file - */ - public static Uri buildRawMmsUri() { - final Uri uri = FileProvider.buildFileUri(AUTHORITY, null); - final File file = getFile(uri.getPath()); - if (!ensureFileExists(file)) { - LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath()); - } - return uri; - } - - @Override - File getFile(final String path, final String extension) { - return getFile(path); - } - - public static File getFile(final Uri uri) { - return getFile(uri.getPath()); - } - - private static File getFile(final String path) { - final Context context = Factory.get().getApplicationContext(); - return new File(getDirectory(context), path + ".dat"); - } - - private static File getDirectory(final Context context) { - return new File(context.getCacheDir(), RAW_MMS_DIR); - } -} diff --git a/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java b/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java deleted file mode 100644 index 791ff34..0000000 --- a/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.app.IntentService; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.RemoteInput; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.android.messaging.datamodel.action.InsertNewMessageAction; -import com.android.messaging.datamodel.action.UpdateMessageNotificationAction; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.conversationlist.ConversationListActivity; -import com.android.messaging.util.LogUtil; - -/** - * Respond to a special intent and send an SMS message without the user's intervention, unless - * the intent extra "showUI" is true. - */ -public class NoConfirmationSmsSendService extends IntentService { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final String EXTRA_SUBSCRIPTION = "subscription"; - public static final String EXTRA_SELF_ID = "self_id"; - - public NoConfirmationSmsSendService() { - // Class name will be the thread name. - super(NoConfirmationSmsSendService.class.getName()); - - // Intent should be redelivered if the process gets killed before completing the job. - setIntentRedelivery(true); - } - - @Override - protected void onHandleIntent(final Intent intent) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent"); - } - - final String action = intent.getAction(); - if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(action)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent wrong action: " + - action); - } - return; - } - final Bundle extras = intent.getExtras(); - if (extras == null) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Called to send SMS but no extras"); - } - return; - } - - // Get all possible extras from intent - final String conversationId = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - final String selfId = intent.getStringExtra(EXTRA_SELF_ID); - final boolean requiresMms = intent.getBooleanExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, - false); - final String message = getText(intent, Intent.EXTRA_TEXT); - final String subject = getText(intent, Intent.EXTRA_SUBJECT); - final int subId = extras.getInt(EXTRA_SUBSCRIPTION, ParticipantData.DEFAULT_SELF_SUB_ID); - - final Uri intentUri = intent.getData(); - final String recipients = intentUri != null ? MmsUtils.getSmsRecipients(intentUri) : null; - - if (TextUtils.isEmpty(recipients) && TextUtils.isEmpty(conversationId)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Both conversationId and recipient(s) cannot be empty"); - } - return; - } - - if (extras.getBoolean("showUI", false)) { - startActivity(new Intent(this, ConversationListActivity.class)); - } else { - if (TextUtils.isEmpty(message)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Message cannot be empty"); - } - return; - } - - // TODO: it's possible that a long message would require sending it via mms, - // but we're not testing for that here and we're sending the message as an sms. - - if (TextUtils.isEmpty(conversationId)) { - InsertNewMessageAction.insertNewMessage(subId, recipients, message, subject); - } else { - MessageData messageData = null; - if (requiresMms) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Auto-sending MMS message in conversation: " + - conversationId); - } - messageData = MessageData.createDraftMmsMessage(conversationId, selfId, message, - subject); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Auto-sending SMS message in conversation: " + - conversationId); - } - messageData = MessageData.createDraftSmsMessage(conversationId, selfId, - message); - } - InsertNewMessageAction.insertNewMessage(messageData); - } - UpdateMessageNotificationAction.updateMessageNotification(); - } - } - - private String getText(final Intent intent, final String textType) { - final String message = intent.getStringExtra(textType); - if (message == null) { - final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) { - final CharSequence extra = remoteInput.getCharSequence(textType); - if (extra != null) { - return extra.toString(); - } - } - } - return message; - } - -} diff --git a/src/com/android/messaging/datamodel/NotificationState.java b/src/com/android/messaging/datamodel/NotificationState.java deleted file mode 100644 index d589874..0000000 --- a/src/com/android/messaging/datamodel/NotificationState.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.app.PendingIntent; -import android.net.Uri; -import android.support.v4.app.NotificationCompat; - -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.ConversationIdSet; - -import java.util.ArrayList; -import java.util.HashSet; - -/** - * Base class for representing notifications. The main reason for this class is that in order to - * show pictures or avatars they might need to be loaded in the background. This class and - * subclasses can do the main work to get the notification ready and then wait until any images - * that are needed are ready before posting. - * - * The creation of a notification is split into two parts. The NotificationState ctor should - * setup the basic information including the mContentIntent. A Notification Builder is created in - * RealTimeChatNotifications and passed to the build() method of each notification where the - * Notification is fully specified. - * - * TODO: There is still some duplication and inconsistency in the utility functions and - * placement of different building blocks across notification types (e.g. summary text for accounts) - */ -public abstract class NotificationState { - private static final int CONTENT_INTENT_REQUEST_CODE_OFFSET = 0; - private static final int CLEAR_INTENT_REQUEST_CODE_OFFSET = 1; - private static final int NUM_REQUEST_CODES_NEEDED = 2; - - public interface FailedMessageQuery { - static final String FAILED_MESSAGES_WHERE_CLAUSE = - "((" + MessageColumns.STATUS + " = " + - MessageData.BUGLE_STATUS_OUTGOING_FAILED + " OR " + - MessageColumns.STATUS + " = " + - MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED + ") AND " + - DatabaseHelper.MessageColumns.SEEN + " = 0)"; - - static final String FAILED_ORDER_BY = DatabaseHelper.MessageColumns.CONVERSATION_ID + ", " + - DatabaseHelper.MessageColumns.SENT_TIMESTAMP + " asc"; - } - - public final ConversationIdSet mConversationIds; - public final HashSet mPeople; - - public NotificationCompat.Style mNotificationStyle; - public NotificationCompat.Builder mNotificationBuilder; - public boolean mCanceled; - public int mType; - public int mBaseRequestCode; - public ArrayList mParticipantAvatarsUris = null; - public ArrayList mParticipantContactUris = null; - - NotificationState(final ConversationIdSet conversationIds) { - mConversationIds = conversationIds; - mPeople = new HashSet(); - } - - /** - * The intent to be triggered when the notification is dismissed. - */ - public abstract PendingIntent getClearIntent(); - - protected Uri getAttachmentUri() { - return null; - } - - // Returns the mime type of the attachment (See ContentType class for definitions) - protected String getAttachmentType() { - return null; - } - - /** - * Build the notification using the given builder. - * @param builder - * @return The style of the notification. - */ - protected abstract NotificationCompat.Style build(NotificationCompat.Builder builder); - - protected void setAvatarUrlsForConversation(final String conversationId) { - } - - protected void setPeopleForConversation(final String conversationId) { - } - - /** - * Reserves request codes for this notification type. By default 2 codes are reserved, one for - * the main intent and another for the cancel intent. Override this function to reserve more. - */ - public int getNumRequestCodesNeeded() { - return NUM_REQUEST_CODES_NEEDED; - } - - public int getContentIntentRequestCode() { - return mBaseRequestCode + CONTENT_INTENT_REQUEST_CODE_OFFSET; - } - - public int getClearIntentRequestCode() { - return mBaseRequestCode + CLEAR_INTENT_REQUEST_CODE_OFFSET; - } - - /** - * Gets the appropriate icon needed for notifications. - */ - public abstract int getIcon(); - - /** - * @return the type of notification that should be used from {@link RealTimeChatNotifications} - * so that the proper ringtone and vibrate settings can be used. - */ - public int getLatestMessageNotificationType() { - return BugleNotifications.LOCAL_SMS_NOTIFICATION; - } - - /** - * @return the notification priority level for this notification. - */ - public abstract int getPriority(); - - /** @return custom ringtone URI or null if not set */ - public String getRingtoneUri() { - return null; - } - - public boolean getNotificationVibrate() { - return false; - } - - public long getLatestReceivedTimestamp() { - return Long.MIN_VALUE; - } -} diff --git a/src/com/android/messaging/datamodel/ParticipantRefresh.java b/src/com/android/messaging/datamodel/ParticipantRefresh.java deleted file mode 100644 index 5324496..0000000 --- a/src/com/android/messaging/datamodel/ParticipantRefresh.java +++ /dev/null @@ -1,738 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentValues; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.graphics.Color; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v4.util.ArrayMap; -import android.telephony.SubscriptionInfo; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Utility class for refreshing participant information based on matching contact. This updates - * 1. name, photo_uri, matching contact_id of participants. - * 2. generated_name of conversations. - * - * There are two kinds of participant refreshes, - * 1. Full refresh, this is triggered at application start or activity resumes after contact - * change is detected. - * 2. Partial refresh, this is triggered when a participant is added to a conversation. This - * normally happens during SMS sync. - */ -@VisibleForTesting -public class ParticipantRefresh { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Refresh all participants including ones that were resolved before. - */ - public static final int REFRESH_MODE_FULL = 0; - - /** - * Refresh all unresolved participants. - */ - public static final int REFRESH_MODE_INCREMENTAL = 1; - - /** - * Force refresh all self participants. - */ - public static final int REFRESH_MODE_SELF_ONLY = 2; - - public static class ConversationParticipantsQuery { - public static final String[] PROJECTION = new String[] { - ConversationParticipantsColumns._ID, - ConversationParticipantsColumns.CONVERSATION_ID, - ConversationParticipantsColumns.PARTICIPANT_ID - }; - - public static final int INDEX_ID = 0; - public static final int INDEX_CONVERSATION_ID = 1; - public static final int INDEX_PARTICIPANT_ID = 2; - } - - // Track whether observer is initialized or not. - private static volatile boolean sObserverInitialized = false; - private static final Object sLock = new Object(); - private static final AtomicBoolean sFullRefreshScheduled = new AtomicBoolean(false); - private static final Runnable sFullRefreshRunnable = new Runnable() { - @Override - public void run() { - final boolean oldScheduled = sFullRefreshScheduled.getAndSet(false); - Assert.isTrue(oldScheduled); - refreshParticipants(REFRESH_MODE_FULL); - } - }; - private static final Runnable sSelfOnlyRefreshRunnable = new Runnable() { - @Override - public void run() { - refreshParticipants(REFRESH_MODE_SELF_ONLY); - } - }; - - /** - * A customized content resolver to track contact changes. - */ - public static class ContactContentObserver extends ContentObserver { - private volatile boolean mContactChanged = false; - - public ContactContentObserver() { - super(null); - } - - @Override - public void onChange(final boolean selfChange) { - super.onChange(selfChange); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Contacts changed"); - } - mContactChanged = true; - } - - public boolean getContactChanged() { - return mContactChanged; - } - - public void resetContactChanged() { - mContactChanged = false; - } - - public void initialize() { - // TODO: Handle enterprise contacts post M once contacts provider supports it - Factory.get().getApplicationContext().getContentResolver().registerContentObserver( - Phone.CONTENT_URI, true, this); - mContactChanged = true; // Force a full refresh on initialization. - } - } - - /** - * Refresh participants only if needed, i.e., application start or contact changed. - */ - public static void refreshParticipantsIfNeeded() { - if (ParticipantRefresh.getNeedFullRefresh() && - sFullRefreshScheduled.compareAndSet(false, true)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Started full participant refresh"); - } - SafeAsyncTask.executeOnThreadPool(sFullRefreshRunnable); - } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Skipped full participant refresh"); - } - } - - /** - * Refresh self participants on subscription or settings change. - */ - public static void refreshSelfParticipants() { - SafeAsyncTask.executeOnThreadPool(sSelfOnlyRefreshRunnable); - } - - private static boolean getNeedFullRefresh() { - final ContactContentObserver observer = Factory.get().getContactContentObserver(); - if (observer == null) { - // If there is no observer (for unittest cases), we don't need to refresh participants. - return false; - } - - if (!sObserverInitialized) { - synchronized (sLock) { - if (!sObserverInitialized) { - observer.initialize(); - sObserverInitialized = true; - } - } - } - - return observer.getContactChanged(); - } - - private static void resetNeedFullRefresh() { - final ContactContentObserver observer = Factory.get().getContactContentObserver(); - if (observer != null) { - observer.resetContactChanged(); - } - } - - /** - * This class is totally static. Make constructor to be private so that an instance - * of this class would not be created by by mistake. - */ - private ParticipantRefresh() { - } - - /** - * Refresh participants in Bugle. - * - * @param refreshMode the refresh mode desired. See {@link #REFRESH_MODE_FULL}, - * {@link #REFRESH_MODE_INCREMENTAL}, and {@link #REFRESH_MODE_SELF_ONLY} - */ - @VisibleForTesting - static void refreshParticipants(final int refreshMode) { - Assert.inRange(refreshMode, REFRESH_MODE_FULL, REFRESH_MODE_SELF_ONLY); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - switch (refreshMode) { - case REFRESH_MODE_FULL: - LogUtil.v(TAG, "Start full participant refresh"); - break; - case REFRESH_MODE_INCREMENTAL: - LogUtil.v(TAG, "Start partial participant refresh"); - break; - case REFRESH_MODE_SELF_ONLY: - LogUtil.v(TAG, "Start self participant refresh"); - break; - } - } - - if (!ContactUtil.hasReadContactsPermission() || !OsUtil.hasPhonePermission()) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Skipping participant referesh because of permissions"); - } - return; - } - - if (refreshMode == REFRESH_MODE_FULL) { - // resetNeedFullRefresh right away so that we will skip duplicated full refresh - // requests. - resetNeedFullRefresh(); - } - - if (refreshMode == REFRESH_MODE_FULL || refreshMode == REFRESH_MODE_SELF_ONLY) { - refreshSelfParticipantList(); - } - - final ArrayList changedParticipants = new ArrayList(); - - String selection = null; - String[] selectionArgs = null; - - if (refreshMode == REFRESH_MODE_INCREMENTAL) { - // In case of incremental refresh, filter out participants that are already resolved. - selection = ParticipantColumns.CONTACT_ID + "=?"; - selectionArgs = new String[] { - String.valueOf(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED) }; - } else if (refreshMode == REFRESH_MODE_SELF_ONLY) { - // In case of self-only refresh, filter out non-self participants. - selection = SELF_PARTICIPANTS_CLAUSE; - selectionArgs = null; - } - - final DatabaseWrapper db = DataModel.get().getDatabase(); - Cursor cursor = null; - boolean selfUpdated = false; - try { - cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - try { - final ParticipantData participantData = - ParticipantData.getFromCursor(cursor); - if (refreshParticipant(db, participantData)) { - if (participantData.isSelf()) { - selfUpdated = true; - } - updateParticipant(db, participantData); - final String id = participantData.getId(); - changedParticipants.add(id); - } - } catch (final Exception exception) { - // Failure to update one participant shouldn't cancel the entire refresh. - // Log the failure so we know what's going on and resume the loop. - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "ParticipantRefresh: Failed to " + - "update participant", exception); - } - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Number of participants refreshed:" + changedParticipants.size()); - } - - // Refresh conversations for participants that are changed. - if (changedParticipants.size() > 0) { - BugleDatabaseOperations.refreshConversationsForParticipants(changedParticipants); - } - if (selfUpdated) { - // Boom - MessagingContentProvider.notifyAllParticipantsChanged(); - MessagingContentProvider.notifyAllMessagesChanged(); - } - } - - private static final String SELF_PARTICIPANTS_CLAUSE = ParticipantColumns.SUB_ID - + " NOT IN ( " - + ParticipantData.OTHER_THAN_SELF_SUB_ID - + " )"; - - private static final Set getExistingSubIds() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final HashSet existingSubIds = new HashSet(); - - Cursor cursor = null; - try { - cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantsQuery.PROJECTION, - SELF_PARTICIPANTS_CLAUSE, null, null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - final int subId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID); - existingSubIds.add(subId); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return existingSubIds; - } - - private static final String UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL = - "UPDATE " + DatabaseHelper.PARTICIPANTS_TABLE + " SET " - + ParticipantColumns.SIM_SLOT_ID + " = %d, " - + ParticipantColumns.SUBSCRIPTION_COLOR + " = %d, " - + ParticipantColumns.SUBSCRIPTION_NAME + " = %s " - + " WHERE %s"; - - static String getUpdateSelfParticipantSubscriptionInfoSql(final int slotId, - final int subscriptionColor, final String subscriptionName, final String where) { - return String.format((Locale) null /* construct SQL string without localization */, - UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL, - slotId, subscriptionColor, subscriptionName, where); - } - - /** - * Ensure that there is a self participant corresponding to every active SIM. Also, ensure - * that any other older SIM self participants are marked as inactive. - */ - private static void refreshSelfParticipantList() { - if (!OsUtil.isAtLeastL_MR1()) { - return; - } - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final List subInfoRecords = - PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList(); - final ArrayMap activeSubscriptionIdToRecordMap = - new ArrayMap(); - db.beginTransaction(); - final Set existingSubIds = getExistingSubIds(); - - try { - if (subInfoRecords != null) { - for (final SubscriptionInfo subInfoRecord : subInfoRecords) { - final int subId = subInfoRecord.getSubscriptionId(); - // If its a new subscription, add it to the database. - if (!existingSubIds.contains(subId)) { - db.execSQL(DatabaseHelper.getCreateSelfParticipantSql(subId)); - // Add it to the local set to guard against duplicated entries returned - // by subscription manager. - existingSubIds.add(subId); - } - activeSubscriptionIdToRecordMap.put(subId, subInfoRecord); - - if (subId == PhoneUtils.getDefault().getDefaultSmsSubscriptionId()) { - // This is the system default subscription, so update the default self. - activeSubscriptionIdToRecordMap.put(ParticipantData.DEFAULT_SELF_SUB_ID, - subInfoRecord); - } - } - } - - // For subscriptions already in the database, refresh ParticipantColumns.SIM_SLOT_ID. - for (final Integer subId : activeSubscriptionIdToRecordMap.keySet()) { - final SubscriptionInfo record = activeSubscriptionIdToRecordMap.get(subId); - final String displayName = - DatabaseUtils.sqlEscapeString(record.getDisplayName().toString()); - db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql(record.getSimSlotIndex(), - record.getIconTint(), displayName, - ParticipantColumns.SUB_ID + " = " + subId)); - } - db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql( - ParticipantData.INVALID_SLOT_ID, Color.TRANSPARENT, "''", - ParticipantColumns.SUB_ID + " NOT IN (" + - Joiner.on(", ").join(activeSubscriptionIdToRecordMap.keySet()) + ")")); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - // Fix up conversation self ids by reverting to default self for conversations whose self - // ids are no longer active. - refreshConversationSelfIds(); - } - - /** - * Refresh one participant. - * @return true if the ParticipantData was changed - */ - public static boolean refreshParticipant(final DatabaseWrapper db, - final ParticipantData participantData) { - boolean updated = false; - - if (participantData.isSelf()) { - final int selfChange = refreshFromSelfProfile(db, participantData); - - if (selfChange == SELF_PROFILE_EXISTS) { - // If a self-profile exists, it takes precedence over Contacts data. So we are done. - return true; - } - - updated = (selfChange == SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED); - - // Fall-through and try to update based on Contacts data - } - - updated |= refreshFromContacts(db, participantData); - return updated; - } - - private static final int SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED = 1; - private static final int SELF_PROFILE_EXISTS = 2; - - private static int refreshFromSelfProfile(final DatabaseWrapper db, - final ParticipantData participantData) { - int changed = 0; - // Refresh the phone number based on information from telephony - if (participantData.updatePhoneNumberForSelfIfChanged()) { - changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED; - } - - if (OsUtil.isAtLeastL_MR1()) { - // Refresh the subscription info based on information from SubscriptionManager. - final SubscriptionInfo subscriptionInfo = - PhoneUtils.get(participantData.getSubId()).toLMr1().getActiveSubscriptionInfo(); - if (participantData.updateSubscriptionInfoForSelfIfChanged(subscriptionInfo)) { - changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED; - } - } - - // For self participant, try getting name/avatar from self profile in CP2 first. - // TODO: in case of multi-sim, profile would not be able to be used for - // different numbers. Need to figure out that. - Cursor selfCursor = null; - try { - selfCursor = ContactUtil.getSelf(db.getContext()).performSynchronousQuery(); - if (selfCursor != null && selfCursor.getCount() > 0) { - selfCursor.moveToNext(); - final long selfContactId = selfCursor.getLong(ContactUtil.INDEX_CONTACT_ID); - participantData.setContactId(selfContactId); - participantData.setFullName(selfCursor.getString( - ContactUtil.INDEX_DISPLAY_NAME)); - participantData.setFirstName( - ContactUtil.lookupFirstName(db.getContext(), selfContactId)); - participantData.setProfilePhotoUri(selfCursor.getString( - ContactUtil.INDEX_PHOTO_URI)); - participantData.setLookupKey(selfCursor.getString( - ContactUtil.INDEX_SELF_QUERY_LOOKUP_KEY)); - return SELF_PROFILE_EXISTS; - } - } catch (final Exception exception) { - // It's possible for contact query to fail and we don't want that to crash our app. - // However, we need to at least log the exception so we know something was wrong. - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " + - "participant. exception=" + exception); - } finally { - if (selfCursor != null) { - selfCursor.close(); - } - } - return changed; - } - - private static boolean refreshFromContacts(final DatabaseWrapper db, - final ParticipantData participantData) { - final String normalizedDestination = participantData.getNormalizedDestination(); - final long currentContactId = participantData.getContactId(); - final String currentDisplayName = participantData.getFullName(); - final String currentFirstName = participantData.getFirstName(); - final String currentPhotoUri = participantData.getProfilePhotoUri(); - final String currentContactDestination = participantData.getContactDestination(); - - Cursor matchingContactCursor = null; - long matchingContactId = -1; - String matchingDisplayName = null; - String matchingFirstName = null; - String matchingPhotoUri = null; - String matchingLookupKey = null; - String matchingDestination = null; - boolean updated = false; - - if (TextUtils.isEmpty(normalizedDestination)) { - // The normalized destination can be "" for the self id if we can't get it from the - // SIM. Some contact providers throw an IllegalArgumentException if you lookup "", - // so we early out. - return false; - } - - try { - matchingContactCursor = ContactUtil.lookupDestination(db.getContext(), - normalizedDestination).performSynchronousQuery(); - if (matchingContactCursor == null || matchingContactCursor.getCount() == 0) { - // If there is no match, mark the participant as contact not found. - if (currentContactId != ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND) { - participantData.setContactId(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND); - participantData.setFullName(null); - participantData.setFirstName(null); - participantData.setProfilePhotoUri(null); - participantData.setLookupKey(null); - updated = true; - } - return updated; - } - - while (matchingContactCursor.moveToNext()) { - final long contactId = matchingContactCursor.getLong(ContactUtil.INDEX_CONTACT_ID); - // Pick either the first contact or the contact with same id as previous matched - // contact id. - if (matchingContactId == -1 || currentContactId == contactId) { - matchingContactId = contactId; - matchingDisplayName = matchingContactCursor.getString( - ContactUtil.INDEX_DISPLAY_NAME); - matchingFirstName = ContactUtil.lookupFirstName(db.getContext(), contactId); - matchingPhotoUri = matchingContactCursor.getString( - ContactUtil.INDEX_PHOTO_URI); - matchingLookupKey = matchingContactCursor.getString( - ContactUtil.INDEX_LOOKUP_KEY); - matchingDestination = matchingContactCursor.getString( - ContactUtil.INDEX_PHONE_EMAIL); - } - - // There is no need to try other contacts if the current contactId was not filled... - if (currentContactId < 0 - // or we found the matching contact id - || currentContactId == contactId) { - break; - } - } - } catch (final Exception exception) { - // It's possible for contact query to fail and we don't want that to crash our app. - // However, we need to at least log the exception so we know something was wrong. - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " + - "participant. exception=" + exception); - return false; - } finally { - if (matchingContactCursor != null) { - matchingContactCursor.close(); - } - } - - // Update participant only if something changed. - final boolean isContactIdChanged = (matchingContactId != currentContactId); - final boolean isDisplayNameChanged = - !TextUtils.equals(matchingDisplayName, currentDisplayName); - final boolean isFirstNameChanged = !TextUtils.equals(matchingFirstName, currentFirstName); - final boolean isPhotoUrlChanged = !TextUtils.equals(matchingPhotoUri, currentPhotoUri); - final boolean isDestinationChanged = !TextUtils.equals(matchingDestination, - currentContactDestination); - - if (isContactIdChanged || isDisplayNameChanged || isFirstNameChanged || isPhotoUrlChanged - || isDestinationChanged) { - participantData.setContactId(matchingContactId); - participantData.setFullName(matchingDisplayName); - participantData.setFirstName(matchingFirstName); - participantData.setProfilePhotoUri(matchingPhotoUri); - participantData.setLookupKey(matchingLookupKey); - participantData.setContactDestination(matchingDestination); - if (isDestinationChanged) { - // Update the send destination to the new one entered by user in Contacts. - participantData.setSendDestination(matchingDestination); - } - updated = true; - } - - return updated; - } - - /** - * Update participant with matching contact's contactId, displayName and photoUri. - */ - private static void updateParticipant(final DatabaseWrapper db, - final ParticipantData participantData) { - final ContentValues values = new ContentValues(); - if (participantData.isSelf()) { - // Self participants can refresh their normalized phone numbers - values.put(ParticipantColumns.NORMALIZED_DESTINATION, - participantData.getNormalizedDestination()); - values.put(ParticipantColumns.DISPLAY_DESTINATION, - participantData.getDisplayDestination()); - } - values.put(ParticipantColumns.CONTACT_ID, participantData.getContactId()); - values.put(ParticipantColumns.LOOKUP_KEY, participantData.getLookupKey()); - values.put(ParticipantColumns.FULL_NAME, participantData.getFullName()); - values.put(ParticipantColumns.FIRST_NAME, participantData.getFirstName()); - values.put(ParticipantColumns.PROFILE_PHOTO_URI, participantData.getProfilePhotoUri()); - values.put(ParticipantColumns.CONTACT_DESTINATION, participantData.getContactDestination()); - values.put(ParticipantColumns.SEND_DESTINATION, participantData.getSendDestination()); - - db.beginTransaction(); - try { - db.update(DatabaseHelper.PARTICIPANTS_TABLE, values, ParticipantColumns._ID + "=?", - new String[] { participantData.getId() }); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Get a list of inactive self ids in the participants table. - */ - private static List getInactiveSelfParticipantIds() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final List inactiveSelf = new ArrayList(); - - final String selection = ParticipantColumns.SIM_SLOT_ID + "=? AND " + - SELF_PARTICIPANTS_CLAUSE; - Cursor cursor = null; - try { - cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, - new String[] { ParticipantColumns._ID }, - selection, new String[] { String.valueOf(ParticipantData.INVALID_SLOT_ID) }, - null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - final String participantId = cursor.getString(0); - inactiveSelf.add(participantId); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return inactiveSelf; - } - - /** - * Gets a list of conversations with the given self ids. - */ - private static List getConversationsWithSelfParticipantIds(final List selfIds) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final List conversationIds = new ArrayList(); - - Cursor cursor = null; - try { - final StringBuilder selectionList = new StringBuilder(); - for (int i = 0; i < selfIds.size(); i++) { - selectionList.append('?'); - if (i < selfIds.size() - 1) { - selectionList.append(','); - } - } - final String selection = - ConversationColumns.CURRENT_SELF_ID + " IN (" + selectionList + ")"; - cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns._ID }, - selection, selfIds.toArray(new String[0]), - null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - final String conversationId = cursor.getString(0); - conversationIds.add(conversationId); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return conversationIds; - } - - /** - * Refresh one conversation's self id. - */ - private static void updateConversationSelfId(final String conversationId, - final String selfId) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - db.beginTransaction(); - try { - BugleDatabaseOperations.updateConversationSelfIdInTransaction(db, conversationId, - selfId); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyConversationMetadataChanged(conversationId); - UIIntents.get().broadcastConversationSelfIdChange(db.getContext(), conversationId, selfId); - } - - /** - * After refreshing the self participant list, find all conversations with inactive self ids, - * and switch them back to system default. - */ - private static void refreshConversationSelfIds() { - final List inactiveSelfs = getInactiveSelfParticipantIds(); - if (inactiveSelfs.size() == 0) { - return; - } - final List conversationsToRefresh = - getConversationsWithSelfParticipantIds(inactiveSelfs); - if (conversationsToRefresh.size() == 0) { - return; - } - final DatabaseWrapper db = DataModel.get().getDatabase(); - final ParticipantData defaultSelf = - BugleDatabaseOperations.getOrCreateSelf(db, ParticipantData.DEFAULT_SELF_SUB_ID); - - if (defaultSelf != null) { - for (final String conversationId : conversationsToRefresh) { - updateConversationSelfId(conversationId, defaultSelf.getId()); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/SyncManager.java b/src/com/android/messaging/datamodel/SyncManager.java deleted file mode 100644 index b3571bf..0000000 --- a/src/com/android/messaging/datamodel/SyncManager.java +++ /dev/null @@ -1,478 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Telephony; -import android.support.v4.util.LongSparseArray; - -import com.android.messaging.datamodel.action.SyncMessagesAction; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -/** - * This class manages message sync with the Telephony SmsProvider/MmsProvider. - */ -public class SyncManager { - private static final String TAG = LogUtil.BUGLE_TAG; - - /** - * Record of any user customization to conversation settings - */ - public static class ConversationCustomization { - private final boolean mArchived; - private final boolean mMuted; - private final boolean mNoVibrate; - private final String mNotificationSoundUri; - - public ConversationCustomization(final boolean archived, final boolean muted, - final boolean noVibrate, final String notificationSoundUri) { - mArchived = archived; - mMuted = muted; - mNoVibrate = noVibrate; - mNotificationSoundUri = notificationSoundUri; - } - - public boolean isArchived() { - return mArchived; - } - - public boolean isMuted() { - return mMuted; - } - - public boolean noVibrate() { - return mNoVibrate; - } - - public String getNotificationSoundUri() { - return mNotificationSoundUri; - } - } - - SyncManager() { - } - - /** - * Timestamp of in progress sync - used to keep track of whether sync is running - */ - private long mSyncInProgressTimestamp = -1; - - /** - * Timestamp of current sync batch upper bound - used to determine if message makes batch dirty - */ - private long mCurrentUpperBoundTimestamp = -1; - - /** - * Timestamp of messages inserted since sync batch started - used to determine if batch dirty - */ - private long mMaxRecentChangeTimestamp = -1L; - - private final ThreadInfoCache mThreadInfoCache = new ThreadInfoCache(); - - /** - * User customization to conversations. If this is set, we need to recover them after - * a full sync. - */ - private LongSparseArray mCustomization = null; - - /** - * Start an incremental sync (backed off a few seconds) - */ - public static void sync() { - SyncMessagesAction.sync(); - } - - /** - * Start an incremental sync (with no backoff) - */ - public static void immediateSync() { - SyncMessagesAction.immediateSync(); - } - - /** - * Start a full sync (for debugging) - */ - public static void forceSync() { - SyncMessagesAction.fullSync(); - } - - /** - * Called from data model thread when starting a sync batch - * @param upperBoundTimestamp upper bound timestamp for sync batch - */ - public synchronized void startSyncBatch(final long upperBoundTimestamp) { - Assert.isTrue(mCurrentUpperBoundTimestamp < 0); - mCurrentUpperBoundTimestamp = upperBoundTimestamp; - mMaxRecentChangeTimestamp = -1L; - } - - /** - * Called from data model thread at end of batch to determine if any messages added in window - * @param lowerBoundTimestamp lower bound timestamp for sync batch - * @return true if message added within window from lower to upper bound timestamp of batch - */ - public synchronized boolean isBatchDirty(final long lowerBoundTimestamp) { - Assert.isTrue(mCurrentUpperBoundTimestamp >= 0); - final long max = mMaxRecentChangeTimestamp; - - final boolean dirty = (max >= 0 && max >= lowerBoundTimestamp); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Sync batch of messages from " + lowerBoundTimestamp - + " to " + mCurrentUpperBoundTimestamp + " is " - + (dirty ? "DIRTY" : "clean") + "; max change timestamp = " - + mMaxRecentChangeTimestamp); - } - - mCurrentUpperBoundTimestamp = -1L; - mMaxRecentChangeTimestamp = -1L; - - return dirty; - } - - /** - * Called from data model or background worker thread to indicate start of message add process - * (add must complete on that thread before action transitions to new thread/stage) - * @param timestamp timestamp of message being added - */ - public synchronized void onNewMessageInserted(final long timestamp) { - if (mCurrentUpperBoundTimestamp >= 0 && timestamp <= mCurrentUpperBoundTimestamp) { - // Message insert in current sync window - mMaxRecentChangeTimestamp = Math.max(mCurrentUpperBoundTimestamp, timestamp); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " before upper bound of " - + "current sync batch " + mCurrentUpperBoundTimestamp); - } - } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " after upper bound of " - + "current sync batch " + mCurrentUpperBoundTimestamp); - } - } - - /** - * Synchronously checks whether sync is allowed and starts sync if allowed - * @param full - true indicates a full (not incremental) sync operation - * @param startTimestamp - starttimestamp for this sync (if allowed) - * @return - true if sync should start - */ - public synchronized boolean shouldSync(final boolean full, final long startTimestamp) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncManager: Checking shouldSync " + (full ? "full " : "") - + "at " + startTimestamp); - } - - if (full) { - final long delayUntilFullSync = delayUntilFullSync(startTimestamp); - if (delayUntilFullSync > 0) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Full sync requested for " + startTimestamp - + " delayed for " + delayUntilFullSync + " ms"); - } - return false; - } - } - - if (isSyncing()) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Not allowed to " + (full ? "full " : "") - + "sync yet; still running sync started at " + mSyncInProgressTimestamp); - } - return false; - } - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Starting " + (full ? "full " : "") + "sync at " - + startTimestamp); - } - - mSyncInProgressTimestamp = startTimestamp; - - return true; - } - - /** - * Return delay (in ms) until allowed to run a full sync (0 meaning can run immediately) - * @param startTimestamp Timestamp used to start the sync - * @return 0 if allowed to run now, else delay in ms - */ - public long delayUntilFullSync(final long startTimestamp) { - final BugleGservices bugleGservices = BugleGservices.get(); - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - - final long lastFullSyncTime = prefs.getLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1L); - final long smsFullSyncBackoffTimeMillis = bugleGservices.getLong( - BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS, - BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT); - final long noFullSyncBefore = (lastFullSyncTime < 0 ? startTimestamp : - lastFullSyncTime + smsFullSyncBackoffTimeMillis); - - final long delayUntilFullSync = noFullSyncBefore - startTimestamp; - if (delayUntilFullSync > 0) { - return delayUntilFullSync; - } - return 0; - } - - /** - * Check if sync currently in progress (public for asserts/logging). - */ - public synchronized boolean isSyncing() { - return (mSyncInProgressTimestamp >= 0); - } - - /** - * Check if sync batch should be in progress - compares upperBound with in memory value - * @param upperBoundTimestamp - upperbound timestamp for sync batch - * @return - true if timestamps match (otherwise batch is orphan from older process) - */ - public synchronized boolean isSyncing(final long upperBoundTimestamp) { - Assert.isTrue(upperBoundTimestamp >= 0); - return (upperBoundTimestamp == mCurrentUpperBoundTimestamp); - } - - /** - * Check if sync has completed for the first time. - */ - public boolean getHasFirstSyncCompleted() { - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - return prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME, - BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT) != - BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT; - } - - /** - * Called once sync is complete - */ - public synchronized void complete() { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Sync started at " + mSyncInProgressTimestamp - + " marked as complete"); - } - mSyncInProgressTimestamp = -1L; - // Conversation customization only used once - mCustomization = null; - } - - private final ContentObserver mMmsSmsObserver = new TelephonyMessagesObserver(); - private boolean mSyncOnChanges = false; - private boolean mNotifyOnChanges = false; - - /** - * Register content observer when necessary and kick off a catch up sync - */ - public void updateSyncObserver(final Context context) { - registerObserver(context); - // Trigger an sms sync in case we missed and messages before registering this observer or - // becoming the SMS provider. - immediateSync(); - } - - private void registerObserver(final Context context) { - if (!PhoneUtils.getDefault().isDefaultSmsApp()) { - // Not default SMS app - need to actively monitor telephony but not notify - mNotifyOnChanges = false; - mSyncOnChanges = true; - } else if (OsUtil.isSecondaryUser()){ - // Secondary users default SMS app - need to actively monitor telephony and notify - mNotifyOnChanges = true; - mSyncOnChanges = true; - } else { - // Primary users default SMS app - don't monitor telephony (most changes from this app) - mNotifyOnChanges = false; - mSyncOnChanges = false; - } - if (mNotifyOnChanges || mSyncOnChanges) { - context.getContentResolver().registerContentObserver(Telephony.MmsSms.CONTENT_URI, - true, mMmsSmsObserver); - } else { - context.getContentResolver().unregisterContentObserver(mMmsSmsObserver); - } - } - - public synchronized void setCustomization( - final LongSparseArray customization) { - this.mCustomization = customization; - } - - public synchronized ConversationCustomization getCustomizationForThread(final long threadId) { - if (mCustomization != null) { - return mCustomization.get(threadId); - } - return null; - } - - public static void resetLastSyncTimestamps() { - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, - BuglePrefsKeys.LAST_FULL_SYNC_TIME_DEFAULT); - prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT); - } - - private class TelephonyMessagesObserver extends ContentObserver { - public TelephonyMessagesObserver() { - // Just run on default thread - super(null); - } - - // Implement the onChange(boolean) method to delegate the change notification to - // the onChange(boolean, Uri) method to ensure correct operation on older versions - // of the framework that did not have the onChange(boolean, Uri) method. - @Override - public void onChange(final boolean selfChange) { - onChange(selfChange, null); - } - - // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument. - @Override - public void onChange(final boolean selfChange, final Uri uri) { - // Handle change. - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncManager: Sms/Mms DB changed @" + System.currentTimeMillis() - + " for " + (uri == null ? "" : uri.toString()) + " " - + mSyncOnChanges + "/" + mNotifyOnChanges); - } - - if (mSyncOnChanges) { - // If sync is already running this will do nothing - but at end of each sync - // action there is a check for recent messages that should catch new changes. - SyncManager.immediateSync(); - } - if (mNotifyOnChanges) { - // TODO: Secondary users are not going to get notifications - } - } - } - - public ThreadInfoCache getThreadInfoCache() { - return mThreadInfoCache; - } - - public static class ThreadInfoCache { - // Cache of thread->conversationId map - private final LongSparseArray mThreadToConversationId = - new LongSparseArray(); - - // Cache of thread->recipients map - private final LongSparseArray> mThreadToRecipients = - new LongSparseArray>(); - - // Remember the conversation ids that need to be archived - private final HashSet mArchivedConversations = new HashSet<>(); - - public synchronized void clear() { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncManager: Cleared ThreadInfoCache"); - } - mThreadToConversationId.clear(); - mThreadToRecipients.clear(); - mArchivedConversations.clear(); - } - - public synchronized boolean isArchived(final String conversationId) { - return mArchivedConversations.contains(conversationId); - } - - /** - * Get or create a conversation based on the message's thread id - * - * @param threadId The message's thread - * @param refSubId The subId used for normalizing phone numbers in the thread - * @param customization The user setting customization to the conversation if any - * @return The existing conversation id or new conversation id - */ - public synchronized String getOrCreateConversation(final DatabaseWrapper db, - final long threadId, int refSubId, final ConversationCustomization customization) { - // This function has several components which need to be atomic. - Assert.isTrue(db.getDatabase().inTransaction()); - - // If we already have this conversation ID in our local map, just return it - String conversationId = mThreadToConversationId.get(threadId); - if (conversationId != null) { - return conversationId; - } - - final List recipients = getThreadRecipients(threadId); - final ArrayList participants = - BugleDatabaseOperations.getConversationParticipantsFromRecipients(recipients, - refSubId); - - if (customization != null) { - // There is user customization we need to recover - conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - customization.isArchived(), participants, customization.isMuted(), - customization.noVibrate(), customization.getNotificationSoundUri()); - if (customization.isArchived()) { - mArchivedConversations.add(conversationId); - } - } else { - conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - false/*archived*/, participants, false/*noNotification*/, - false/*noVibrate*/, null/*soundUri*/); - } - - if (conversationId != null) { - mThreadToConversationId.put(threadId, conversationId); - return conversationId; - } - - return null; - } - - - /** - * Load the recipients of a thread from telephony provider. If we fail, use - * a predefined unknown recipient. This should not return null. - * - * @param threadId - */ - public synchronized List getThreadRecipients(final long threadId) { - List recipients = mThreadToRecipients.get(threadId); - if (recipients == null) { - recipients = MmsUtils.getRecipientsByThread(threadId); - if (recipients != null && recipients.size() > 0) { - mThreadToRecipients.put(threadId, recipients); - } - } - - if (recipients == null || recipients.isEmpty()) { - LogUtil.w(TAG, "SyncManager : using unknown sender since thread " + threadId + - " couldn't find any recipients."); - - // We want to try our best to load the messages, - // so if recipient info is broken, try to fix it with unknown recipient - recipients = Lists.newArrayList(); - recipients.add(ParticipantData.getUnknownSenderDestination()); - } - - return recipients; - } - } -} diff --git a/src/com/android/messaging/datamodel/action/Action.java b/src/com/android/messaging/datamodel/action/Action.java deleted file mode 100644 index e4c332e..0000000 --- a/src/com/android/messaging/datamodel/action/Action.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelException; -import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; -import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener; -import com.android.messaging.util.LogUtil; - -import java.util.LinkedList; -import java.util.List; - -/** - * Base class for operations that perform application business logic off the main UI thread while - * holding a wake lock. - * . - * Note all derived classes need to provide real implementation of Parcelable (this is abstract) - */ -public abstract class Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - // Members holding the parameters common to all actions - no action state - public final String actionKey; - - // If derived classes keep their data in actionParameters then parcelable is trivial - protected Bundle actionParameters; - - // This does not get written to the parcel - private final List mBackgroundActions = new LinkedList(); - - /** - * Process the action locally - runs on action service thread. - * TODO: Currently, there is no way for this method to indicate failure - * @return result to be passed in to {@link ActionExecutedListener#onActionExecuted}. It is - * also the result passed in to {@link ActionCompletedListener#onActionSucceeded} if - * there is no background work. - */ - protected Object executeAction() { - return null; - } - - /** - * Queues up background work ie. {@link #doBackgroundWork} will be called on the - * background worker thread. - */ - protected void requestBackgroundWork() { - mBackgroundActions.add(this); - } - - /** - * Queues up background actions for background processing after the current action has - * completed its processing ({@link #executeAction}, {@link processBackgroundCompletion} - * or {@link #processBackgroundFailure}) on the Action thread. - * @param backgroundAction - */ - protected void requestBackgroundWork(final Action backgroundAction) { - mBackgroundActions.add(backgroundAction); - } - - /** - * Return flag indicating if any actions have been queued - */ - public boolean hasBackgroundActions() { - return !mBackgroundActions.isEmpty(); - } - - /** - * Send queued actions to the background worker provided - */ - public void sendBackgroundActions(final BackgroundWorker worker) { - worker.queueBackgroundWork(mBackgroundActions); - mBackgroundActions.clear(); - } - - /** - * Do work in a long running background worker thread. - * {@link #requestBackgroundWork} needs to be called for this method to - * be called. {@link #processBackgroundFailure} will be called on the Action service thread - * if this method throws {@link DataModelException}. - * @return response that is to be passed to {@link #processBackgroundResponse} - */ - protected Bundle doBackgroundWork() throws DataModelException { - return null; - } - - /** - * Process the success response from the background worker. Runs on action service thread. - * @param response the response returned by {@link #doBackgroundWork} - * @return result to be passed in to {@link ActionCompletedListener#onActionSucceeded} - */ - protected Object processBackgroundResponse(final Bundle response) { - return null; - } - - /** - * Called in case of failures when sending background actions. Runs on action service thread - * @return result to be passed in to {@link ActionCompletedListener#onActionFailed} - */ - protected Object processBackgroundFailure() { - return null; - } - - /** - * Constructor - */ - protected Action(final String key) { - this.actionKey = key; - this.actionParameters = new Bundle(); - } - - /** - * Constructor - */ - protected Action() { - this.actionKey = generateUniqueActionKey(getClass().getSimpleName()); - this.actionParameters = new Bundle(); - } - - /** - * Queue an action and monitor for processing by the ActionService via the factory helper - */ - protected void start(final ActionMonitor monitor) { - ActionMonitor.registerActionMonitor(this.actionKey, monitor); - DataModel.startActionService(this); - } - - /** - * Queue an action for processing by the ActionService via the factory helper - */ - public void start() { - DataModel.startActionService(this); - } - - /** - * Queue an action for delayed processing by the ActionService via the factory helper - */ - public void schedule(final int requestCode, final long delayMs) { - DataModel.scheduleAction(this, requestCode, delayMs); - } - - /** - * Called when action queues ActionService intent - */ - protected final void markStart() { - ActionMonitor.setState(this, ActionMonitor.STATE_CREATED, - ActionMonitor.STATE_QUEUED); - } - - /** - * Mark the beginning of local action execution - */ - protected final void markBeginExecute() { - ActionMonitor.setState(this, ActionMonitor.STATE_QUEUED, - ActionMonitor.STATE_EXECUTING); - } - - /** - * Mark the end of local action execution - either completes the action or queues - * background actions - */ - protected final void markEndExecute(final Object result) { - final boolean hasBackgroundActions = hasBackgroundActions(); - ActionMonitor.setExecutedState(this, ActionMonitor.STATE_EXECUTING, - hasBackgroundActions, result); - if (!hasBackgroundActions) { - ActionMonitor.setCompleteState(this, ActionMonitor.STATE_EXECUTING, - result, true); - } - } - - /** - * Update action state to indicate that the background worker is starting - */ - protected final void markBackgroundWorkStarting() { - ActionMonitor.setState(this, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); - } - - /** - * Update action state to indicate that the background worker has posted its response - * (or failure) to the Action service - */ - protected final void markBackgroundCompletionQueued() { - ActionMonitor.setState(this, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED); - } - - /** - * Update action state to indicate the background action failed but is being re-queued for retry - */ - protected final void markBackgroundWorkQueued() { - ActionMonitor.setState(this, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); - } - - /** - * Called by ActionService to process a response from the background worker - * @param response the response returned by {@link #doBackgroundWork} - */ - protected final void processBackgroundWorkResponse(final Bundle response) { - ActionMonitor.setState(this, - ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED, - ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE); - final Object result = processBackgroundResponse(response); - ActionMonitor.setCompleteState(this, - ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, result, true); - } - - /** - * Called by ActionService when a background action fails - */ - protected final void processBackgroundWorkFailure() { - final Object result = processBackgroundFailure(); - ActionMonitor.setCompleteState(this, ActionMonitor.STATE_UNDEFINED, - result, false); - } - - private static final Object sLock = new Object(); - private static long sActionIdx = System.currentTimeMillis() * 1000; - - /** - * Helper method to generate a unique operation index - */ - protected static long getActionIdx() { - long idx = 0; - synchronized (sLock) { - idx = ++sActionIdx; - } - return idx; - } - - /** - * This helper can be used to generate a unique key used to identify an action. - * @param baseKey - key generated to identify the action parameters - * @return - composite key generated by appending unique index - */ - protected static String generateUniqueActionKey(final String baseKey) { - final StringBuilder key = new StringBuilder(); - if (!TextUtils.isEmpty(baseKey)) { - key.append(baseKey); - } - key.append(":"); - key.append(getActionIdx()); - return key.toString(); - } - - /** - * Most derived classes use this base implementation (unless they include files handles) - */ - @Override - public int describeContents() { - return 0; - } - - /** - * Derived classes need to implement writeToParcel (but typically should call this method - * to parcel Action member variables before they parcel their member variables). - */ - public void writeActionToParcel(final Parcel parcel, final int flags) { - parcel.writeString(this.actionKey); - parcel.writeBundle(this.actionParameters); - } - - /** - * Helper for derived classes to implement parcelable - */ - public Action(final Parcel in) { - this.actionKey = in.readString(); - // Note: Need to set classloader to ensure we can un-parcel classes from this package - this.actionParameters = in.readBundle(Action.class.getClassLoader()); - } -} diff --git a/src/com/android/messaging/datamodel/action/ActionMonitor.java b/src/com/android/messaging/datamodel/action/ActionMonitor.java deleted file mode 100644 index cb080aa..0000000 --- a/src/com/android/messaging/datamodel/action/ActionMonitor.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Handler; -import android.support.v4.util.SimpleArrayMap; -import android.text.TextUtils; - -import com.android.messaging.util.Assert.RunsOnAnyThread; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.ThreadUtil; -import com.google.common.annotations.VisibleForTesting; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -/** - * Base class for action monitors - * Actions come in various flavors but - * o) Fire and forget - no monitor - * o) Immediate local processing only - will trigger ActionCompletedListener when done - * o) Background worker processing only - will trigger ActionCompletedListener when done - * o) Immediate local processing followed by background work followed by more local processing - * - will trigger ActionExecutedListener once local processing complete and - * ActionCompletedListener when second set of local process (dealing with background - * worker response) is complete - */ -public class ActionMonitor { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Interface used to notify on completion of local execution for an action - */ - public interface ActionExecutedListener { - /** - * @param result value returned by {@link Action#executeAction} - */ - @RunsOnMainThread - abstract void onActionExecuted(ActionMonitor monitor, final Action action, - final Object data, final Object result); - } - - /** - * Interface used to notify action completion - */ - public interface ActionCompletedListener { - /** - * @param result object returned from processing the action. This is the value returned by - * {@link Action#executeAction} if there is no background work, or - * else the value returned by - * {@link Action#processBackgroundResponse} - */ - @RunsOnMainThread - abstract void onActionSucceeded(ActionMonitor monitor, - final Action action, final Object data, final Object result); - /** - * @param result value returned by {@link Action#processBackgroundFailure} - */ - @RunsOnMainThread - abstract void onActionFailed(ActionMonitor monitor, final Action action, - final Object data, final Object result); - } - - /** - * Interface for being notified of action state changes - used for profiling, testing only - */ - protected interface ActionStateChangedListener { - /** - * @param action the action that is changing state - * @param state the new state of the action - */ - @RunsOnAnyThread - void onActionStateChanged(Action action, int state); - } - - /** - * Operations always start out as STATE_CREATED and finish as STATE_COMPLETE. - * Some common state transition sequences in between include: - *

    - *
  • Local data change only : STATE_QUEUED - STATE_EXECUTING - *
  • Background worker request only : STATE_BACKGROUND_ACTIONS_QUEUED - * - STATE_EXECUTING_BACKGROUND_ACTION - * - STATE_BACKGROUND_COMPLETION_QUEUED - * - STATE_PROCESSING_BACKGROUND_RESPONSE - *
  • Local plus background worker request : STATE_QUEUED - STATE_EXECUTING - * - STATE_BACKGROUND_ACTIONS_QUEUED - * - STATE_EXECUTING_BACKGROUND_ACTION - * - STATE_BACKGROUND_COMPLETION_QUEUED - * - STATE_PROCESSING_BACKGROUND_RESPONSE - *
- */ - protected static final int STATE_UNDEFINED = 0; - protected static final int STATE_CREATED = 1; // Just created - protected static final int STATE_QUEUED = 2; // Action queued for processing - protected static final int STATE_EXECUTING = 3; // Action processing on datamodel thread - protected static final int STATE_BACKGROUND_ACTIONS_QUEUED = 4; - protected static final int STATE_EXECUTING_BACKGROUND_ACTION = 5; - // The background work has completed, either returning a success response or resulting in a - // failure - protected static final int STATE_BACKGROUND_COMPLETION_QUEUED = 6; - protected static final int STATE_PROCESSING_BACKGROUND_RESPONSE = 7; - protected static final int STATE_COMPLETE = 8; // Action complete - - /** - * Lock used to protect access to state and listeners - */ - private final Object mLock = new Object(); - - /** - * Current state of action - */ - @VisibleForTesting - protected int mState; - - /** - * Listener which is notified on action completion - */ - private ActionCompletedListener mCompletedListener; - - /** - * Listener which is notified on action executed - */ - private ActionExecutedListener mExecutedListener; - - /** - * Listener which is notified of state changes - */ - private ActionStateChangedListener mStateChangedListener; - - /** - * Handler used to post results back to caller - */ - private final Handler mHandler; - - /** - * Data passed back to listeners (associated with the action when it is created) - */ - private final Object mData; - - /** - * The action key is used to determine equivalence of operations and their requests - */ - private final String mActionKey; - - /** - * Get action key identifying associated action - */ - public String getActionKey() { - return mActionKey; - } - - /** - * Unregister listeners so that they will not be called back - override this method if needed - */ - public void unregister() { - clearListeners(); - } - - /** - * Unregister listeners so that they will not be called - */ - protected final void clearListeners() { - synchronized (mLock) { - mCompletedListener = null; - mExecutedListener = null; - } - } - - /** - * Create a monitor associated with a particular action instance - */ - protected ActionMonitor(final int initialState, final String actionKey, - final Object data) { - mHandler = ThreadUtil.getMainThreadHandler(); - mActionKey = actionKey; - mState = initialState; - mData = data; - } - - /** - * Return flag to indicate if action is complete - */ - public boolean isComplete() { - boolean complete = false; - synchronized (mLock) { - complete = (mState == STATE_COMPLETE); - } - return complete; - } - - /** - * Set listener that will be called with action completed result - */ - protected final void setCompletedListener(final ActionCompletedListener listener) { - synchronized (mLock) { - mCompletedListener = listener; - } - } - - /** - * Set listener that will be called with local execution result - */ - protected final void setExecutedListener(final ActionExecutedListener listener) { - synchronized (mLock) { - mExecutedListener = listener; - } - } - - /** - * Set listener that will be called with local execution result - */ - protected final void setStateChangedListener(final ActionStateChangedListener listener) { - synchronized (mLock) { - mStateChangedListener = listener; - } - } - - /** - * Perform a state update transition - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param newState - new state which will be set - */ - @VisibleForTesting - protected void updateState(final Action action, final int expectedOldState, - final int newState) { - ActionStateChangedListener listener = null; - synchronized (mLock) { - if (expectedOldState != STATE_UNDEFINED && - mState != expectedOldState) { - throw new IllegalStateException("On updateState to " + newState + " was " + mState - + " expecting " + expectedOldState); - } - if (newState != mState) { - mState = newState; - listener = mStateChangedListener; - } - } - if (listener != null) { - listener.onActionStateChanged(action, newState); - } - } - - /** - * Perform a state update transition - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param newState - new state which will be set - */ - static void setState(final Action action, final int expectedOldState, - final int newState) { - int oldMonitorState = expectedOldState; - int newMonitorState = newState; - final ActionMonitor monitor - = ActionMonitor.lookupActionMonitor(action.actionKey); - if (monitor != null) { - oldMonitorState = monitor.mState; - monitor.updateState(action, expectedOldState, newState); - newMonitorState = monitor.mState; - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - df.setTimeZone(TimeZone.getTimeZone("UTC")); - LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date()) - + "UTC State = " + oldMonitorState + " - " + newMonitorState); - } - } - - /** - * Mark action complete - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param result - object returned from processing the action. This is the value returned by - * {@link Action#executeAction} if there is no background work, or - * else the value returned by {@link Action#processBackgroundResponse} - * or {@link Action#processBackgroundFailure} - */ - private final void complete(final Action action, - final int expectedOldState, final Object result, - final boolean succeeded) { - ActionCompletedListener completedListener = null; - synchronized (mLock) { - setState(action, expectedOldState, STATE_COMPLETE); - completedListener = mCompletedListener; - mExecutedListener = null; - mStateChangedListener = null; - } - if (completedListener != null) { - // Marshal to UI thread - mHandler.post(new Runnable() { - @Override - public void run() { - ActionCompletedListener listener = null; - synchronized (mLock) { - if (mCompletedListener != null) { - listener = mCompletedListener; - } - mCompletedListener = null; - } - if (listener != null) { - if (succeeded) { - listener.onActionSucceeded(ActionMonitor.this, - action, mData, result); - } else { - listener.onActionFailed(ActionMonitor.this, - action, mData, result); - } - } - } - }); - } - } - - /** - * Mark action complete - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param result - object returned from processing the action. This is the value returned by - * {@link Action#executeAction} if there is no background work, or - * else the value returned by {@link Action#processBackgroundResponse} - * or {@link Action#processBackgroundFailure} - */ - static void setCompleteState(final Action action, final int expectedOldState, - final Object result, final boolean succeeded) { - int oldMonitorState = expectedOldState; - final ActionMonitor monitor - = ActionMonitor.lookupActionMonitor(action.actionKey); - if (monitor != null) { - oldMonitorState = monitor.mState; - monitor.complete(action, expectedOldState, result, succeeded); - unregisterActionMonitorIfComplete(action.actionKey, monitor); - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - df.setTimeZone(TimeZone.getTimeZone("UTC")); - LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date()) - + "UTC State = " + oldMonitorState + " - " + STATE_COMPLETE); - } - } - - /** - * Mark action complete - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param hasBackgroundActions - has the completing action requested background work - * @param result - the return value of {@link Action#executeAction} - */ - final void executed(final Action action, - final int expectedOldState, final boolean hasBackgroundActions, final Object result) { - ActionExecutedListener executedListener = null; - synchronized (mLock) { - if (hasBackgroundActions) { - setState(action, expectedOldState, STATE_BACKGROUND_ACTIONS_QUEUED); - } - executedListener = mExecutedListener; - } - if (executedListener != null) { - // Marshal to UI thread - mHandler.post(new Runnable() { - @Override - public void run() { - ActionExecutedListener listener = null; - synchronized (mLock) { - if (mExecutedListener != null) { - listener = mExecutedListener; - mExecutedListener = null; - } - } - if (listener != null) { - listener.onActionExecuted(ActionMonitor.this, - action, mData, result); - } - } - }); - } - } - - /** - * Mark action complete - * @param action - action whose state is updating - * @param expectedOldState - expected existing state of action (can be UNKNOWN) - * @param hasBackgroundActions - has the completing action requested background work - * @param result - the return value of {@link Action#executeAction} - */ - static void setExecutedState(final Action action, - final int expectedOldState, final boolean hasBackgroundActions, final Object result) { - int oldMonitorState = expectedOldState; - final ActionMonitor monitor - = ActionMonitor.lookupActionMonitor(action.actionKey); - if (monitor != null) { - oldMonitorState = monitor.mState; - monitor.executed(action, expectedOldState, hasBackgroundActions, result); - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - df.setTimeZone(TimeZone.getTimeZone("UTC")); - LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date()) - + "UTC State = " + oldMonitorState + " - EXECUTED"); - } - } - - /** - * Map of action monitors indexed by actionKey - */ - @VisibleForTesting - static SimpleArrayMap sActionMonitors = - new SimpleArrayMap(); - - /** - * Insert new monitor into map - */ - static void registerActionMonitor(final String actionKey, - final ActionMonitor monitor) { - if (monitor != null - && (TextUtils.isEmpty(monitor.getActionKey()) - || TextUtils.isEmpty(actionKey) - || !actionKey.equals(monitor.getActionKey()))) { - throw new IllegalArgumentException("Monitor key " + monitor.getActionKey() - + " not compatible with action key " + actionKey); - } - synchronized (sActionMonitors) { - sActionMonitors.put(actionKey, monitor); - } - } - - /** - * Find monitor associated with particular action - */ - private static ActionMonitor lookupActionMonitor(final String actionKey) { - ActionMonitor monitor = null; - synchronized (sActionMonitors) { - monitor = sActionMonitors.get(actionKey); - } - return monitor; - } - - /** - * Remove monitor from map - */ - @VisibleForTesting - static void unregisterActionMonitor(final String actionKey, - final ActionMonitor monitor) { - if (monitor != null) { - synchronized (sActionMonitors) { - sActionMonitors.remove(actionKey); - } - } - } - - /** - * Remove monitor from map if the action is complete - */ - static void unregisterActionMonitorIfComplete(final String actionKey, - final ActionMonitor monitor) { - if (monitor != null && monitor.isComplete()) { - synchronized (sActionMonitors) { - sActionMonitors.remove(actionKey); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/action/ActionService.java b/src/com/android/messaging/datamodel/action/ActionService.java deleted file mode 100644 index 29225fa..0000000 --- a/src/com/android/messaging/datamodel/action/ActionService.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.PendingIntent; -import android.content.Context; -import android.os.Bundle; - -/** - * Class providing interface for the ActionService - can be stubbed for testing - */ -public class ActionService { - protected static PendingIntent makeStartActionPendingIntent(final Context context, - final Action action, final int requestCode, final boolean launchesAnActivity) { - return ActionServiceImpl.makeStartActionPendingIntent(context, action, requestCode, - launchesAnActivity); - } - - /** - * Start an action by posting it over the the ActionService - */ - public void startAction(final Action action) { - ActionServiceImpl.startAction(action); - } - - /** - * Schedule a delayed action by posting it over the the ActionService - */ - public void scheduleAction(final Action action, final int code, - final long delayMs) { - ActionServiceImpl.scheduleAction(action, code, delayMs); - } - - /** - * Process a response from the BackgroundWorker in the ActionService - */ - protected void handleResponseFromBackgroundWorker( - final Action action, final Bundle response) { - ActionServiceImpl.handleResponseFromBackgroundWorker(action, response); - } - - /** - * Process a failure from the BackgroundWorker in the ActionService - */ - protected void handleFailureFromBackgroundWorker(final Action action, - final Exception exception) { - ActionServiceImpl.handleFailureFromBackgroundWorker(action, exception); - } -} diff --git a/src/com/android/messaging/datamodel/action/ActionServiceImpl.java b/src/com/android/messaging/datamodel/action/ActionServiceImpl.java deleted file mode 100644 index a408dac..0000000 --- a/src/com/android/messaging/datamodel/action/ActionServiceImpl.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.AlarmManager; -import android.app.IntentService; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.LoggingTimer; -import com.android.messaging.util.WakeLockHelper; -import com.google.common.annotations.VisibleForTesting; - -/** - * ActionService used to perform background processing for data model - */ -public class ActionServiceImpl extends IntentService { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final boolean VERBOSE = false; - - public ActionServiceImpl() { - super("ActionService"); - } - - /** - * Start action by sending intent to the service - * @param action - action to start - */ - protected static void startAction(final Action action) { - final Intent intent = makeIntent(OP_START_ACTION); - final Bundle actionBundle = new Bundle(); - actionBundle.putParcelable(BUNDLE_ACTION, action); - intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); - action.markStart(); - startServiceWithIntent(intent); - } - - /** - * Schedule an action to run after specified delay using alarm manager to send pendingintent - * @param action - action to start - * @param requestCode - request code used to collapse requests - * @param delayMs - delay in ms (from now) before action will start - */ - protected static void scheduleAction(final Action action, final int requestCode, - final long delayMs) { - final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION); - final Bundle actionBundle = new Bundle(); - actionBundle.putParcelable(BUNDLE_ACTION, action); - intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); - - PendingActionReceiver.scheduleAlarm(intent, requestCode, delayMs); - } - - /** - * Handle response returned by BackgroundWorker - * @param request - request generating response - * @param response - response from service - */ - protected static void handleResponseFromBackgroundWorker(final Action action, - final Bundle response) { - final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_RESPONSE); - - final Bundle actionBundle = new Bundle(); - actionBundle.putParcelable(BUNDLE_ACTION, action); - intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); - intent.putExtra(EXTRA_WORKER_RESPONSE, response); - - startServiceWithIntent(intent); - } - - /** - * Handle response returned by BackgroundWorker - * @param request - request generating failure - */ - protected static void handleFailureFromBackgroundWorker(final Action action, - final Exception exception) { - final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_FAILURE); - - final Bundle actionBundle = new Bundle(); - actionBundle.putParcelable(BUNDLE_ACTION, action); - intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); - intent.putExtra(EXTRA_WORKER_EXCEPTION, exception); - - startServiceWithIntent(intent); - } - - // ops - @VisibleForTesting - protected static final int OP_START_ACTION = 200; - @VisibleForTesting - protected static final int OP_RECEIVE_BACKGROUND_RESPONSE = 201; - @VisibleForTesting - protected static final int OP_RECEIVE_BACKGROUND_FAILURE = 202; - - // extras - @VisibleForTesting - protected static final String EXTRA_OP_CODE = "op"; - @VisibleForTesting - protected static final String EXTRA_ACTION_BUNDLE = "datamodel_action_bundle"; - @VisibleForTesting - protected static final String EXTRA_WORKER_EXCEPTION = "worker_exception"; - @VisibleForTesting - protected static final String EXTRA_WORKER_RESPONSE = "worker_response"; - @VisibleForTesting - protected static final String EXTRA_WORKER_UPDATE = "worker_update"; - @VisibleForTesting - protected static final String BUNDLE_ACTION = "bundle_action"; - - private BackgroundWorker mBackgroundWorker; - - /** - * Allocate an intent with a specific opcode. - */ - private static Intent makeIntent(final int opcode) { - final Intent intent = new Intent(Factory.get().getApplicationContext(), - ActionServiceImpl.class); - intent.putExtra(EXTRA_OP_CODE, opcode); - return intent; - } - - /** - * Broadcast receiver for alarms scheduled through ActionService. - */ - public static class PendingActionReceiver extends BroadcastReceiver { - static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION"; - - /** - * Allocate an intent with a specific opcode and alarm action. - */ - public static Intent makeIntent(final int opcode) { - final Intent intent = new Intent(Factory.get().getApplicationContext(), - PendingActionReceiver.class); - intent.setAction(ACTION); - intent.putExtra(EXTRA_OP_CODE, opcode); - return intent; - } - - public static void scheduleAlarm(final Intent intent, final int requestCode, - final long delayMs) { - final Context context = Factory.get().getApplicationContext(); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT); - - final AlarmManager mgr = - (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - - if (delayMs < Long.MAX_VALUE) { - mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + delayMs, pendingIntent); - } else { - mgr.cancel(pendingIntent); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onReceive(final Context context, final Intent intent) { - ActionServiceImpl.startServiceWithIntent(intent); - } - } - - /** - * Creates a pending intent that will trigger a data model action when the intent is - * triggered - */ - public static PendingIntent makeStartActionPendingIntent(final Context context, - final Action action, final int requestCode, final boolean launchesAnActivity) { - final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION); - final Bundle actionBundle = new Bundle(); - actionBundle.putParcelable(BUNDLE_ACTION, action); - intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); - if (launchesAnActivity) { - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - } - return PendingIntent.getBroadcast(context, requestCode, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate() { - super.onCreate(); - mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService(); - DataModel.get().getConnectivityUtil().registerForSignalStrength(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - DataModel.get().getConnectivityUtil().unregisterForSignalStrength(); - } - - private static final String WAKELOCK_ID = "bugle_datamodel_service_wakelock"; - @VisibleForTesting - static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); - - /** - * Queue intent to the ActionService after acquiring wake lock - */ - private static void startServiceWithIntent(final Intent intent) { - final Context context = Factory.get().getApplicationContext(); - final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); - // Increase refCount on wake lock - acquiring if necessary - if (VERBOSE) { - LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode); - } - sWakeLock.acquire(context, intent, opcode); - intent.setClass(context, ActionServiceImpl.class); - - // TODO: Note that intent will be quietly discarded if it exceeds available rpc - // memory (in total around 1MB). See this article for background - // http://developer.android.com/reference/android/os/TransactionTooLargeException.html - // Perhaps we should keep large structures in the action monitor? - if (context.startService(intent) == null) { - LogUtil.e(TAG, - "ActionService.startServiceWithIntent: failed to start service for intent " - + intent); - sWakeLock.release(intent, opcode); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void onHandleIntent(final Intent intent) { - if (intent == null) { - // Shouldn't happen but sometimes does following another crash. - LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent"); - return; - } - final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); - sWakeLock.ensure(intent, opcode); - - try { - Action action; - final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE); - actionBundle.setClassLoader(getClassLoader()); - switch(opcode) { - case OP_START_ACTION: { - action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); - executeAction(action); - break; - } - - case OP_RECEIVE_BACKGROUND_RESPONSE: { - action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); - final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE); - processBackgroundResponse(action, response); - break; - } - - case OP_RECEIVE_BACKGROUND_FAILURE: { - action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); - processBackgroundFailure(action); - break; - } - - default: - throw new RuntimeException("Unrecognized opcode in ActionServiceImpl"); - } - - action.sendBackgroundActions(mBackgroundWorker); - } finally { - // Decrease refCount on wake lock - releasing if necessary - sWakeLock.release(intent, opcode); - } - } - - private static final long EXECUTION_TIME_WARN_LIMIT_MS = 1000; // 1 second - /** - * Local execution of action on ActionService thread - */ - private void executeAction(final Action action) { - action.markBeginExecute(); - - final LoggingTimer timer = createLoggingTimer(action, "#executeAction"); - timer.start(); - - final Object result = action.executeAction(); - - timer.stopAndLog(); - - action.markEndExecute(result); - } - - /** - * Process response on ActionService thread - */ - private void processBackgroundResponse(final Action action, final Bundle response) { - final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundResponse"); - timer.start(); - - action.processBackgroundWorkResponse(response); - - timer.stopAndLog(); - } - - /** - * Process failure on ActionService thread - */ - private void processBackgroundFailure(final Action action) { - final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundFailure"); - timer.start(); - - action.processBackgroundWorkFailure(); - - timer.stopAndLog(); - } - - private static LoggingTimer createLoggingTimer( - final Action action, final String methodName) { - return new LoggingTimer(TAG, action.getClass().getSimpleName() + methodName, - EXECUTION_TIME_WARN_LIMIT_MS); - } -} diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorker.java b/src/com/android/messaging/datamodel/action/BackgroundWorker.java deleted file mode 100644 index aad3c07..0000000 --- a/src/com/android/messaging/datamodel/action/BackgroundWorker.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import java.util.List; - -/** - * Interface between action service and its workers - */ -public class BackgroundWorker { - - /** - * Send list of requests from action service to a worker - */ - public void queueBackgroundWork(final List backgroundActions) { - BackgroundWorkerService.queueBackgroundWork(backgroundActions); - } -} diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java b/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java deleted file mode 100644 index 4d4b150..0000000 --- a/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelException; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.LoggingTimer; -import com.android.messaging.util.WakeLockHelper; -import com.google.common.annotations.VisibleForTesting; - -import java.util.List; - -/** - * Background worker service is an initial example of a background work queue handler - * Used to actually "send" messages which may take some time and should not block ActionService - * or UI - */ -public class BackgroundWorkerService extends IntentService { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final boolean VERBOSE = false; - - private static final String WAKELOCK_ID = "bugle_background_worker_wakelock"; - @VisibleForTesting - static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); - - private final ActionService mHost; - - public BackgroundWorkerService() { - super("BackgroundWorker"); - mHost = DataModel.get().getActionService(); - } - - /** - * Queue a list of requests from action service to this worker - */ - public static void queueBackgroundWork(final List actions) { - for (final Action action : actions) { - startServiceWithAction(action, 0); - } - } - - // ops - @VisibleForTesting - protected static final int OP_PROCESS_REQUEST = 400; - - // extras - @VisibleForTesting - protected static final String EXTRA_OP_CODE = "op"; - @VisibleForTesting - protected static final String EXTRA_ACTION = "action"; - @VisibleForTesting - protected static final String EXTRA_ATTEMPT = "retry_attempt"; - - /** - * Queue action intent to the BackgroundWorkerService after acquiring wake lock - */ - private static void startServiceWithAction(final Action action, - final int retryCount) { - final Intent intent = new Intent(); - intent.putExtra(EXTRA_ACTION, action); - intent.putExtra(EXTRA_ATTEMPT, retryCount); - startServiceWithIntent(OP_PROCESS_REQUEST, intent); - } - - /** - * Queue intent to the BackgroundWorkerService after acquiring wake lock - */ - private static void startServiceWithIntent(final int opcode, final Intent intent) { - final Context context = Factory.get().getApplicationContext(); - - intent.setClass(context, BackgroundWorkerService.class); - intent.putExtra(EXTRA_OP_CODE, opcode); - sWakeLock.acquire(context, intent, opcode); - if (VERBOSE) { - LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode); - } - - if (context.startService(intent) == null) { - LogUtil.e(TAG, - "BackgroundWorkerService.startServiceWithAction: failed to start service for " - + opcode); - sWakeLock.release(intent, opcode); - } - } - - @Override - protected void onHandleIntent(final Intent intent) { - if (intent == null) { - // Shouldn't happen but sometimes does following another crash. - LogUtil.w(TAG, "BackgroundWorkerService.onHandleIntent: Called with null intent"); - return; - } - final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); - sWakeLock.ensure(intent, opcode); - - try { - switch(opcode) { - case OP_PROCESS_REQUEST: { - final Action action = intent.getParcelableExtra(EXTRA_ACTION); - final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, -1); - doBackgroundWork(action, attempt); - break; - } - - default: - throw new RuntimeException("Unrecognized opcode in BackgroundWorkerService"); - } - } finally { - sWakeLock.release(intent, opcode); - } - } - - /** - * Local execution of background work for action on ActionService thread - */ - private void doBackgroundWork(final Action action, final int attempt) { - action.markBackgroundWorkStarting(); - Bundle response = null; - try { - final LoggingTimer timer = new LoggingTimer( - TAG, action.getClass().getSimpleName() + "#doBackgroundWork"); - timer.start(); - - response = action.doBackgroundWork(); - - timer.stopAndLog(); - action.markBackgroundCompletionQueued(); - mHost.handleResponseFromBackgroundWorker(action, response); - } catch (final Exception exception) { - final boolean retry = false; - LogUtil.e(TAG, "Error in background worker", exception); - if (!(exception instanceof DataModelException)) { - // DataModelException is expected (sort-of) and handled in handleFailureFromWorker - // below, but other exceptions should crash ENG builds - Assert.fail("Unexpected error in background worker - abort"); - } - if (retry) { - action.markBackgroundWorkQueued(); - startServiceWithAction(action, attempt + 1); - } else { - action.markBackgroundCompletionQueued(); - mHost.handleFailureFromBackgroundWorker(action, exception); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/action/BugleActionToasts.java b/src/com/android/messaging/datamodel/action/BugleActionToasts.java deleted file mode 100644 index f60facd..0000000 --- a/src/com/android/messaging/datamodel/action/BugleActionToasts.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.content.res.Resources; -import android.widget.Toast; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.ThreadUtil; - -import javax.annotation.Nullable; - -/** - * Shows one-time, transient notifications in response to action failures (i.e. permanent failures - * when sending a message) by showing toasts. - */ -public class BugleActionToasts { - /** - * Called when SendMessageAction or DownloadMmsAction finishes - * @param conversationId the conversation of the sent or downloaded message - * @param success did the action succeed - * @param status the message sending status - * @param isSms whether the message is sent using SMS - * @param subId the subId of the SIM related to this send - * @param isSend whether it is a send (false for download) - */ - static void onSendMessageOrManualDownloadActionCompleted( - final String conversationId, - final boolean success, - final int status, - final boolean isSms, - final int subId, - final boolean isSend) { - // We only show notifications for two cases, i.e. when mobile data is off or when we are - // in airplane mode, both of which fail fast with permanent failures. - if (!success && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) { - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - if (phoneUtils.isAirplaneModeOn()) { - if (isSend) { - showToast(R.string.send_message_failure_airplane_mode); - } else { - showToast(R.string.download_message_failure_airplane_mode); - } - return; - } else if (!isSms && !phoneUtils.isMobileDataEnabled()) { - if (isSend) { - showToast(R.string.send_message_failure_no_data); - } else { - showToast(R.string.download_message_failure_no_data); - } - return; - } - } - - if (AccessibilityUtil.isTouchExplorationEnabled(Factory.get().getApplicationContext())) { - final boolean isFocusedConversation = DataModel.get().isFocusedConversation(conversationId); - if (isFocusedConversation && success) { - // Using View.announceForAccessibility may be preferable, but we do not have a - // View, and so we use a toast instead. - showToast(isSend ? R.string.send_message_success - : R.string.download_message_success); - return; - } - - // {@link MessageNotificationState#checkFailedMessages} does not post a notification for - // failures in observable conversations. For accessibility, we provide an indication - // here. - final boolean isObservableConversation = DataModel.get().isNewMessageObservable( - conversationId); - if (isObservableConversation && !success) { - showToast(isSend ? R.string.send_message_failure - : R.string.download_message_failure); - } - } - } - - public static void onMessageReceived(final String conversationId, - @Nullable final ParticipantData sender, @Nullable final MessageData message) { - final Context context = Factory.get().getApplicationContext(); - if (AccessibilityUtil.isTouchExplorationEnabled(context)) { - final boolean isFocusedConversation = DataModel.get().isFocusedConversation( - conversationId); - if (isFocusedConversation) { - final Resources res = context.getResources(); - final String senderDisplayName = (sender == null) - ? res.getString(R.string.unknown_sender) : sender.getDisplayName(false); - final String announcement = res.getString( - R.string.incoming_message_announcement, senderDisplayName, - (message == null) ? "" : message.getMessageText()); - showToast(announcement); - } - } - } - - public static void onConversationDeleted() { - showToast(R.string.conversation_deleted); - } - - private static void showToast(final int messageResId) { - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - Toast.makeText(getApplicationContext(), - getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show(); - } - }); - } - - private static void showToast(final String message) { - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); - } - }); - } - - private static Context getApplicationContext() { - return Factory.get().getApplicationContext(); - } - - private static class UpdateDestinationBlockedActionToast - implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener { - private final Context mContext; - - UpdateDestinationBlockedActionToast(final Context context) { - mContext = context; - } - - @Override - public void onUpdateDestinationBlockedAction( - final UpdateDestinationBlockedAction action, - final boolean success, - final boolean block, - final String destination) { - if (success) { - Toast.makeText(mContext, - block - ? R.string.update_destination_blocked - : R.string.update_destination_unblocked, - Toast.LENGTH_LONG - ).show(); - } - } - } - - public static UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener - makeUpdateDestinationBlockedActionListener(final Context context) { - return new UpdateDestinationBlockedActionToast(context); - } -} diff --git a/src/com/android/messaging/datamodel/action/DeleteConversationAction.java b/src/com/android/messaging/datamodel/action/DeleteConversationAction.java deleted file mode 100644 index a00f6d6..0000000 --- a/src/com/android/messaging/datamodel/action/DeleteConversationAction.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelException; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.widget.WidgetConversationProvider; - -import java.util.ArrayList; -import java.util.List; - -/** - * Action used to delete a conversation. - */ -public class DeleteConversationAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - public static void deleteConversation(final String conversationId, final long cutoffTimestamp) { - final DeleteConversationAction action = new DeleteConversationAction(conversationId, - cutoffTimestamp); - action.start(); - } - - private static final String KEY_CONVERSATION_ID = "conversation_id"; - private static final String KEY_CUTOFF_TIMESTAMP = "cutoff_timestamp"; - - private DeleteConversationAction(final String conversationId, final long cutoffTimestamp) { - super(); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - // TODO: Should we set cuttoff timestamp to prevent us deleting new messages? - actionParameters.putLong(KEY_CUTOFF_TIMESTAMP, cutoffTimestamp); - } - - // Delete conversation from both the local DB and telephony in the background so sync cannot - // run concurrently and incorrectly try to recreate the conversation's messages locally. The - // telephony database can sometimes be quite slow to delete conversations, so we delete from - // the local DB first, notify the UI, and then delete from telephony. - @Override - protected Bundle doBackgroundWork() throws DataModelException { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final long cutoffTimestamp = actionParameters.getLong(KEY_CUTOFF_TIMESTAMP); - - if (!TextUtils.isEmpty(conversationId)) { - // First find the thread id for this conversation. - final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId); - - if (BugleDatabaseOperations.deleteConversation(db, conversationId, cutoffTimestamp)) { - LogUtil.i(TAG, "DeleteConversationAction: Deleted local conversation " - + conversationId); - - BugleActionToasts.onConversationDeleted(); - - // Remove notifications if necessary - BugleNotifications.update(true /* silent */, null /* conversationId */, - BugleNotifications.UPDATE_MESSAGES); - - // We have changed the conversation list - MessagingContentProvider.notifyConversationListChanged(); - - // Notify the widget the conversation is deleted so it can go into its configure state. - WidgetConversationProvider.notifyConversationDeleted( - Factory.get().getApplicationContext(), - conversationId); - } else { - LogUtil.w(TAG, "DeleteConversationAction: Could not delete local conversation " - + conversationId); - return null; - } - - // Now delete from telephony DB. MmsSmsProvider throws an exception if the thread id is - // less than 0. If it's greater than zero, it will delete all messages with that thread - // id, even if there's no corresponding row in the threads table. - if (threadId >= 0) { - final int count = MmsUtils.deleteThread(threadId, cutoffTimestamp); - if (count > 0) { - LogUtil.i(TAG, "DeleteConversationAction: Deleted telephony thread " - + threadId + " (cutoffTimestamp = " + cutoffTimestamp + ")"); - } else { - LogUtil.w(TAG, "DeleteConversationAction: Could not delete thread from " - + "telephony: conversationId = " + conversationId + ", thread id = " - + threadId); - } - } else { - LogUtil.w(TAG, "DeleteConversationAction: Local conversation " + conversationId - + " has an invalid telephony thread id; will delete messages individually"); - deleteConversationMessagesFromTelephony(); - } - } else { - LogUtil.e(TAG, "DeleteConversationAction: conversationId is empty"); - } - - return null; - } - - /** - * Deletes all the telephony messages for the local conversation being deleted. - *

- * This is a fallback used when the conversation is not associated with any telephony thread, - * or its thread id is invalid (e.g. negative). This is not common, but can happen sometimes - * (e.g. the Unknown Sender conversation). In the usual case of deleting a conversation, we - * don't need this because the telephony provider automatically deletes messages when a thread - * is deleted. - */ - private void deleteConversationMessagesFromTelephony() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - Assert.notNull(conversationId); - - final List messageUris = new ArrayList<>(); - Cursor cursor = null; - try { - cursor = db.query(DatabaseHelper.MESSAGES_TABLE, - new String[] { MessageColumns.SMS_MESSAGE_URI }, - MessageColumns.CONVERSATION_ID + "=?", - new String[] { conversationId }, - null, null, null); - while (cursor.moveToNext()) { - String messageUri = cursor.getString(0); - try { - messageUris.add(Uri.parse(messageUri)); - } catch (Exception e) { - LogUtil.e(TAG, "DeleteConversationAction: Could not parse message uri " - + messageUri); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - for (Uri messageUri : messageUris) { - int count = MmsUtils.deleteMessage(messageUri); - if (count > 0) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "DeleteConversationAction: Deleted telephony message " - + messageUri); - } - } else { - LogUtil.w(TAG, "DeleteConversationAction: Could not delete telephony message " - + messageUri); - } - } - } - - @Override - protected Object executeAction() { - requestBackgroundWork(); - return null; - } - - private DeleteConversationAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public DeleteConversationAction createFromParcel(final Parcel in) { - return new DeleteConversationAction(in); - } - - @Override - public DeleteConversationAction[] newArray(final int size) { - return new DeleteConversationAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/DeleteMessageAction.java b/src/com/android/messaging/datamodel/action/DeleteMessageAction.java deleted file mode 100644 index 9ddb2a6..0000000 --- a/src/com/android/messaging/datamodel/action/DeleteMessageAction.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.LogUtil; - -/** - * Action used to delete a single message. - */ -public class DeleteMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - public static void deleteMessage(final String messageId) { - final DeleteMessageAction action = new DeleteMessageAction(messageId); - action.start(); - } - - private static final String KEY_MESSAGE_ID = "message_id"; - - private DeleteMessageAction(final String messageId) { - super(); - actionParameters.putString(KEY_MESSAGE_ID, messageId); - } - - // Doing this work in the background so that we're not competing with sync - // which could bring the deleted message back to life between the time we deleted - // it locally and deleted it in telephony (sync is also done on doBackgroundWork). - // - // Previously this block of code deleted from telephony first but that can be very - // slow (on the order of seconds) so this was modified to first delete locally, trigger - // the UI update, then delete from telephony. - @Override - protected Bundle doBackgroundWork() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // First find the thread id for this conversation. - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - - if (!TextUtils.isEmpty(messageId)) { - // Check message still exists - final MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - if (message != null) { - // Delete from local DB - int count = BugleDatabaseOperations.deleteMessage(db, messageId); - if (count > 0) { - LogUtil.i(TAG, "DeleteMessageAction: Deleted local message " - + messageId); - } else { - LogUtil.w(TAG, "DeleteMessageAction: Could not delete local message " - + messageId); - } - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - // We may have changed the conversation list - MessagingContentProvider.notifyConversationListChanged(); - - final Uri messageUri = message.getSmsMessageUri(); - if (messageUri != null) { - // Delete from telephony DB - count = MmsUtils.deleteMessage(messageUri); - if (count > 0) { - LogUtil.i(TAG, "DeleteMessageAction: Deleted telephony message " - + messageUri); - } else { - LogUtil.w(TAG, "DeleteMessageAction: Could not delete message from " - + "telephony: messageId = " + messageId + ", telephony uri = " - + messageUri); - } - } else { - LogUtil.i(TAG, "DeleteMessageAction: Local message " + messageId - + " has no telephony uri."); - } - } else { - LogUtil.w(TAG, "DeleteMessageAction: Message " + messageId + " no longer exists"); - } - } - return null; - } - - /** - * Delete the message. - */ - @Override - protected Object executeAction() { - requestBackgroundWork(); - return null; - } - - private DeleteMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public DeleteMessageAction createFromParcel(final Parcel in) { - return new DeleteMessageAction(in); - } - - @Override - public DeleteMessageAction[] newArray(final int size) { - return new DeleteMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java deleted file mode 100644 index 7a8c907..0000000 --- a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.LogUtil; - -/** - * Downloads an MMS message. - *

- * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to - * access the EXTRA_* fields for setting up the 'downloaded' pending intent. - */ -public class DownloadMmsAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Interface for DownloadMmsAction listeners - */ - public interface DownloadMmsActionListener { - @RunsOnMainThread - abstract void onDownloadMessageStarting(final ActionMonitor monitor, - final Object data, final MessageData message); - @RunsOnMainThread - abstract void onDownloadMessageSucceeded(final ActionMonitor monitor, - final Object data, final MessageData message); - @RunsOnMainThread - abstract void onDownloadMessageFailed(final ActionMonitor monitor, - final Object data, final MessageData message); - } - - /** - * Queue download of an mms notification message (can only be called during execute of action) - */ - static boolean queueMmsForDownloadInBackground(final String messageId, - final Action processingAction) { - // When this method is being called, it is always from auto download - final DownloadMmsAction action = new DownloadMmsAction(); - // This could queue nothing - return action.queueAction(messageId, processingAction); - } - - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_CONVERSATION_ID = "conversation_id"; - private static final String KEY_PARTICIPANT_ID = "participant_id"; - private static final String KEY_CONTENT_LOCATION = "content_location"; - private static final String KEY_TRANSACTION_ID = "transaction_id"; - private static final String KEY_NOTIFICATION_URI = "notification_uri"; - private static final String KEY_SUB_ID = "sub_id"; - private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number"; - private static final String KEY_AUTO_DOWNLOAD = "auto_download"; - private static final String KEY_FAILURE_STATUS = "failure_status"; - - // Values we attach to the pending intent that's fired when the message is downloaded. - // Only applicable when downloading via the platform APIs on L+. - public static final String EXTRA_MESSAGE_ID = "message_id"; - public static final String EXTRA_CONTENT_URI = "content_uri"; - public static final String EXTRA_NOTIFICATION_URI = "notification_uri"; - public static final String EXTRA_SUB_ID = "sub_id"; - public static final String EXTRA_SUB_PHONE_NUMBER = "sub_phone_number"; - public static final String EXTRA_TRANSACTION_ID = "transaction_id"; - public static final String EXTRA_CONTENT_LOCATION = "content_location"; - public static final String EXTRA_AUTO_DOWNLOAD = "auto_download"; - public static final String EXTRA_RECEIVED_TIMESTAMP = "received_timestamp"; - public static final String EXTRA_CONVERSATION_ID = "conversation_id"; - public static final String EXTRA_PARTICIPANT_ID = "participant_id"; - public static final String EXTRA_STATUS_IF_FAILED = "status_if_failed"; - - private DownloadMmsAction() { - super(); - } - - @Override - protected Object executeAction() { - Assert.fail("DownloadMmsAction must be queued rather than started"); - return null; - } - - protected boolean queueAction(final String messageId, final Action processingAction) { - actionParameters.putString(KEY_MESSAGE_ID, messageId); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - // Read the message from local db - final MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - if (message != null && message.canDownloadMessage()) { - final Uri notificationUri = message.getSmsMessageUri(); - final String conversationId = message.getConversationId(); - final int status = message.getStatus(); - - final String selfId = message.getSelfId(); - final ParticipantData self = BugleDatabaseOperations - .getExistingParticipant(db, selfId); - final int subId = self.getSubId(); - actionParameters.putInt(KEY_SUB_ID, subId); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - actionParameters.putString(KEY_PARTICIPANT_ID, message.getParticipantId()); - actionParameters.putString(KEY_CONTENT_LOCATION, message.getMmsContentLocation()); - actionParameters.putString(KEY_TRANSACTION_ID, message.getMmsTransactionId()); - actionParameters.putParcelable(KEY_NOTIFICATION_URI, notificationUri); - actionParameters.putBoolean(KEY_AUTO_DOWNLOAD, isAutoDownload(status)); - - final long now = System.currentTimeMillis(); - if (message.getInDownloadWindow(now)) { - // We can still retry - actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination()); - - final int downloadingStatus = getDownloadingStatus(status); - // Update message status to indicate downloading. - updateMessageStatus(notificationUri, messageId, conversationId, - downloadingStatus, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED); - // Pre-compute the next status when failed so we don't have to load from db again - actionParameters.putInt(KEY_FAILURE_STATUS, getFailureStatus(downloadingStatus)); - - // Actual download happens in background - processingAction.requestBackgroundWork(this); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, - "DownloadMmsAction: Queued download of MMS message " + messageId); - } - return true; - } else { - LogUtil.w(TAG, "DownloadMmsAction: Download of MMS message " + messageId - + " failed (outside download window)"); - - // Retries depleted and we failed. Update the message status so we won't retry again - updateMessageStatus(notificationUri, messageId, conversationId, - MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED, - MessageData.RAW_TELEPHONY_STATUS_UNDEFINED); - if (status == MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD) { - // For auto download failure, we should send a DEFERRED NotifyRespInd - // to carrier to indicate we will manual download later - ProcessDownloadedMmsAction.sendDeferredRespStatus( - messageId, message.getMmsTransactionId(), - message.getMmsContentLocation(), subId); - return true; - } - } - } - return false; - } - - /** - * Find out the auto download state of this message based on its starting status - * - * @param status The starting status of the message. - * @return True if this is a message doing auto downloading, false otherwise - */ - private static boolean isAutoDownload(final int status) { - switch (status) { - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - return false; - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - return true; - default: - Assert.fail("isAutoDownload: invalid input status " + status); - return false; - } - } - - /** - * Get the corresponding downloading status based on the starting status of the message - * - * @param status The starting status of the message. - * @return The downloading status - */ - private static int getDownloadingStatus(final int status) { - switch (status) { - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING; - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - return MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING; - default: - Assert.fail("isAutoDownload: invalid input status " + status); - return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING; - } - } - - /** - * Get the corresponding failed status based on the current downloading status - * - * @param status The downloading status - * @return The status the message should have if downloading failed - */ - private static int getFailureStatus(final int status) { - switch (status) { - case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING: - return MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD; - case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING: - return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD; - default: - Assert.fail("isAutoDownload: invalid input status " + status); - return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD; - } - } - - @Override - protected Bundle doBackgroundWork() { - final Context context = Factory.get().getApplicationContext(); - final int subId = actionParameters.getInt(KEY_SUB_ID); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final Uri notificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI); - final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER); - final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID); - final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION); - final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD); - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID); - final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS); - - final long receivedTimestampRoundedToSecond = - 1000 * ((System.currentTimeMillis() + 500) / 1000); - - LogUtil.i(TAG, "DownloadMmsAction: Downloading MMS message " + messageId - + " (" + (autoDownload ? "auto" : "manual") + ")"); - - // Bundle some values we'll need after the message is downloaded (via platform APIs) - final Bundle extras = new Bundle(); - extras.putString(EXTRA_MESSAGE_ID, messageId); - extras.putString(EXTRA_CONVERSATION_ID, conversationId); - extras.putString(EXTRA_PARTICIPANT_ID, participantId); - extras.putInt(EXTRA_STATUS_IF_FAILED, statusIfFailed); - - // Start the download - final MmsUtils.StatusPlusUri status = MmsUtils.downloadMmsMessage(context, - notificationUri, subId, subPhoneNumber, transactionId, contentLocation, - autoDownload, receivedTimestampRoundedToSecond / 1000L, extras); - if (status == MmsUtils.STATUS_PENDING) { - // Async download; no status yet - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "DownloadMmsAction: Downloading MMS message " + messageId - + " asynchronously; waiting for pending intent to signal completion"); - } - } else { - // Inform sync that message has been added at local received timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(receivedTimestampRoundedToSecond); - // Handle downloaded message - ProcessDownloadedMmsAction.processMessageDownloadFastFailed(messageId, - notificationUri, conversationId, participantId, contentLocation, subId, - subPhoneNumber, statusIfFailed, autoDownload, transactionId, - status.resultCode); - } - return null; - } - - @Override - protected Object processBackgroundResponse(final Bundle response) { - // Nothing to do here; post-download actions handled by ProcessDownloadedMmsAction - return null; - } - - @Override - protected Object processBackgroundFailure() { - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID); - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID); - final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS); - final int subId = actionParameters.getInt(KEY_SUB_ID); - - ProcessDownloadedMmsAction.processDownloadActionFailure(messageId, - MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, - conversationId, participantId, statusIfFailed, subId, transactionId); - - return null; - } - - static void updateMessageStatus(final Uri messageUri, final String messageId, - final String conversationId, final int status, final int rawStatus) { - final Context context = Factory.get().getApplicationContext(); - // Downloading status just kept in local DB but need to fix up telephony DB first - if (status == MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING || - status == MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) { - MmsUtils.clearMmsStatus(context, messageUri); - } - // Then mark downloading status in our local DB - final ContentValues values = new ContentValues(); - values.put(MessageColumns.STATUS, status); - values.put(MessageColumns.RAW_TELEPHONY_STATUS, rawStatus); - final DatabaseWrapper db = DataModel.get().getDatabase(); - BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values); - - MessagingContentProvider.notifyMessagesChanged(conversationId); - } - - private DownloadMmsAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public DownloadMmsAction createFromParcel(final Parcel in) { - return new DownloadMmsAction(in); - } - - @Override - public DownloadMmsAction[] newArray(final int size) { - return new DownloadMmsAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java b/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java deleted file mode 100644 index ab320bf..0000000 --- a/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -public class DumpDatabaseAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - public static final String DUMP_NAME = "db_copy.db"; - private static final int BUFFER_SIZE = 16384; - - /** - * Copy the database to external storage - */ - public static void dumpDatabase() { - final DumpDatabaseAction action = new DumpDatabaseAction(); - action.start(); - } - - private DumpDatabaseAction() { - } - - @Override - protected Object executeAction() { - final Context context = Factory.get().getApplicationContext(); - final String dbName = DatabaseHelper.DATABASE_NAME; - BufferedOutputStream bos = null; - BufferedInputStream bis = null; - - long originalSize = 0; - final File inFile = context.getDatabasePath(dbName); - if (inFile.exists() && inFile.isFile()) { - originalSize = inFile.length(); - } - final File outFile = DebugUtils.getDebugFile(DUMP_NAME, true); - if (outFile != null) { - int totalBytes = 0; - try { - bos = new BufferedOutputStream(new FileOutputStream(outFile)); - bis = new BufferedInputStream(new FileInputStream(inFile)); - - final byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = bis.read(buffer)) > 0) { - bos.write(buffer, 0, bytesRead); - totalBytes += bytesRead; - } - } catch (final IOException e) { - LogUtil.w(TAG, "Exception copying the database;" - + " destination may not be complete.", e); - } finally { - if (bos != null) { - try { - bos.close(); - } catch (final IOException e) { - // Nothing to do - } - } - - if (bis != null) { - try { - bis.close(); - } catch (final IOException e) { - // Nothing to do - } - } - DebugUtils.ensureReadable(outFile); - LogUtil.i(TAG, "Dump complete; orig size: " + originalSize + - ", copy size: " + totalBytes); - } - } - return null; - } - - private DumpDatabaseAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public DumpDatabaseAction createFromParcel(final Parcel in) { - return new DumpDatabaseAction(in); - } - - @Override - public DumpDatabaseAction[] newArray(final int size) { - return new DumpDatabaseAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java b/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java deleted file mode 100644 index e3d131d..0000000 --- a/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.LogUtil; - -/** - * Action used to fixup actively downloading or sending status at startup - just in case we - * crash - never run this when a message might actually be sending or downloading. - */ -public class FixupMessageStatusOnStartupAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - public static void fixupMessageStatus() { - final FixupMessageStatusOnStartupAction action = new FixupMessageStatusOnStartupAction(); - action.start(); - } - - private FixupMessageStatusOnStartupAction() { - } - - @Override - protected Object executeAction() { - // Now mark any messages in active sending or downloading state as inactive - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - int downloadFailedCnt = 0; - int sendFailedCnt = 0; - try { - // For both sending and downloading messages, let's assume they failed. - // For MMS sent/downloaded via platform, the sent/downloaded pending intent - // may come back. That will update the message. User may see the message - // in wrong status within a short window if that happens. But this should - // rarely happen. This is a simple solution to situations like app gets killed - // while the pending intent is still in the fly. Alternatively, we could - // keep the status for platform sent/downloaded MMS and timeout these messages. - // But that is much more complex. - final ContentValues values = new ContentValues(); - values.put(DatabaseHelper.MessageColumns.STATUS, - MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED); - downloadFailedCnt += db.update(DatabaseHelper.MESSAGES_TABLE, values, - DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)", - new String[]{ - Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING), - Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) - }); - values.clear(); - - values.clear(); - values.put(DatabaseHelper.MessageColumns.STATUS, - MessageData.BUGLE_STATUS_OUTGOING_FAILED); - sendFailedCnt = db.update(DatabaseHelper.MESSAGES_TABLE, values, - DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)", - new String[]{ - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING), - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING) - }); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - LogUtil.i(TAG, "Fixup: Send failed - " + sendFailedCnt - + " Download failed - " + downloadFailedCnt); - - // Don't send contentObserver notifications as displayed text should not change - return null; - } - - private FixupMessageStatusOnStartupAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public FixupMessageStatusOnStartupAction createFromParcel(final Parcel in) { - return new FixupMessageStatusOnStartupAction(in); - } - - @Override - public FixupMessageStatusOnStartupAction[] newArray(final int size) { - return new FixupMessageStatusOnStartupAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java b/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java deleted file mode 100644 index b262141..0000000 --- a/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; -import com.android.messaging.datamodel.data.LaunchConversationData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; - -/** - * Action used to get or create a conversation for a list of conversation participants. - */ -public class GetOrCreateConversationAction extends Action implements Parcelable { - /** - * Interface for GetOrCreateConversationAction listeners - */ - public interface GetOrCreateConversationActionListener { - @RunsOnMainThread - abstract void onGetOrCreateConversationSucceeded(final ActionMonitor monitor, - final Object data, final String conversationId); - - @RunsOnMainThread - abstract void onGetOrCreateConversationFailed(final ActionMonitor monitor, - final Object data); - } - - public static GetOrCreateConversationActionMonitor getOrCreateConversation( - final ArrayList participants, final Object data, - final GetOrCreateConversationActionListener listener) { - final GetOrCreateConversationActionMonitor monitor = new - GetOrCreateConversationActionMonitor(data, listener); - final GetOrCreateConversationAction action = new GetOrCreateConversationAction(participants, - monitor.getActionKey()); - action.start(monitor); - return monitor; - } - - - public static GetOrCreateConversationActionMonitor getOrCreateConversation( - final String[] recipients, final Object data, final LaunchConversationData listener) { - final ArrayList participants = new ArrayList<>(); - for (String recipient : recipients) { - recipient = recipient.trim(); - if (!TextUtils.isEmpty(recipient)) { - participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient)); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "getOrCreateConversation hit empty recipient"); - } - } - return getOrCreateConversation(participants, data, listener); - } - - private static final String KEY_PARTICIPANTS_LIST = "participants_list"; - - private GetOrCreateConversationAction(final ArrayList participants, - final String actionKey) { - super(actionKey); - actionParameters.putParcelableArrayList(KEY_PARTICIPANTS_LIST, participants); - } - - /** - * Lookup the conversation or create a new one. - */ - @Override - protected Object executeAction() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // First find the thread id for this list of participants. - final ArrayList participants = - actionParameters.getParcelableArrayList(KEY_PARTICIPANTS_LIST); - BugleDatabaseOperations.sanitizeConversationParticipants(participants); - final ArrayList recipients = - BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants); - - final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(), - recipients); - - if (threadId < 0) { - LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't create a threadId in SMS db for numbers : " + - LogUtil.sanitizePII(recipients.toString())); - // TODO: Add a better way to indicate an error from executeAction. - return null; - } - - final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - false, participants, false, false, null); - - return conversationId; - } - - /** - * A monitor that notifies a listener upon completion - */ - public static class GetOrCreateConversationActionMonitor extends ActionMonitor - implements ActionCompletedListener { - private final GetOrCreateConversationActionListener mListener; - - GetOrCreateConversationActionMonitor(final Object data, - final GetOrCreateConversationActionListener listener) { - super(STATE_CREATED, generateUniqueActionKey("GetOrCreateConversationAction"), data); - setCompletedListener(this); - mListener = listener; - } - - @Override - public void onActionSucceeded(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - if (result == null) { - mListener.onGetOrCreateConversationFailed(monitor, data); - } else { - mListener.onGetOrCreateConversationSucceeded(monitor, data, (String) result); - } - } - - @Override - public void onActionFailed(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - // TODO: Currently onActionFailed is only called if there is an error in - // processing requests, not for errors in the local processing. - Assert.fail("Unreachable"); - mListener.onGetOrCreateConversationFailed(monitor, data); - } - } - - private GetOrCreateConversationAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public GetOrCreateConversationAction createFromParcel(final Parcel in) { - return new GetOrCreateConversationAction(in); - } - - @Override - public GetOrCreateConversationAction[] newArray(final int size) { - return new GetOrCreateConversationAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java b/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java deleted file mode 100644 index 7bfcfe0..0000000 --- a/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.sms.SmsReleaseStorage; -import com.android.messaging.util.Assert; - -/** - * Action used to handle low storage related issues on the device. - */ -public class HandleLowStorageAction extends Action implements Parcelable { - private static final int SUB_OP_CODE_CLEAR_MEDIA_MESSAGES = 100; - private static final int SUB_OP_CODE_CLEAR_OLD_MESSAGES = 101; - - public static void handleDeleteMediaMessages(final long durationInMillis) { - final HandleLowStorageAction action = new HandleLowStorageAction( - SUB_OP_CODE_CLEAR_MEDIA_MESSAGES, durationInMillis); - action.start(); - } - - public static void handleDeleteOldMessages(final long durationInMillis) { - final HandleLowStorageAction action = new HandleLowStorageAction( - SUB_OP_CODE_CLEAR_OLD_MESSAGES, durationInMillis); - action.start(); - } - - private static final String KEY_SUB_OP_CODE = "sub_op_code"; - private static final String KEY_CUTOFF_DURATION_MILLIS = "cutoff_duration_millis"; - - private HandleLowStorageAction(final int subOpcode, final long durationInMillis) { - super(); - actionParameters.putInt(KEY_SUB_OP_CODE, subOpcode); - actionParameters.putLong(KEY_CUTOFF_DURATION_MILLIS, durationInMillis); - } - - @Override - protected Object executeAction() { - final int subOpCode = actionParameters.getInt(KEY_SUB_OP_CODE); - final long durationInMillis = actionParameters.getLong(KEY_CUTOFF_DURATION_MILLIS); - switch (subOpCode) { - case SUB_OP_CODE_CLEAR_MEDIA_MESSAGES: - SmsReleaseStorage.deleteMessages(0, durationInMillis); - break; - - case SUB_OP_CODE_CLEAR_OLD_MESSAGES: - SmsReleaseStorage.deleteMessages(1, durationInMillis); - break; - - default: - Assert.fail("Unsupported action type!"); - break; - } - return true; - } - - private HandleLowStorageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public HandleLowStorageAction createFromParcel(final Parcel in) { - return new HandleLowStorageAction(in); - } - - @Override - public HandleLowStorageAction[] newArray(final int size) { - return new HandleLowStorageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java deleted file mode 100644 index 2567ca9..0000000 --- a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Action used to convert a draft message to an outgoing message. Its writes SMS messages to - * the telephony db, but {@link SendMessageAction} is responsible for inserting MMS message into - * the telephony DB. The latter also does the actual sending of the message in the background. - * The latter is also responsible for re-sending a failed message. - */ -public class InsertNewMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static long sLastSentMessageTimestamp = -1; - - /** - * Insert message (no listener) - */ - public static void insertNewMessage(final MessageData message) { - final InsertNewMessageAction action = new InsertNewMessageAction(message); - action.start(); - } - - /** - * Insert message (no listener) with a given non-default subId. - */ - public static void insertNewMessage(final MessageData message, final int subId) { - Assert.isFalse(subId == ParticipantData.DEFAULT_SELF_SUB_ID); - final InsertNewMessageAction action = new InsertNewMessageAction(message, subId); - action.start(); - } - - /** - * Insert message (no listener) - */ - public static void insertNewMessage(final int subId, final String recipients, - final String messageText, final String subject) { - final InsertNewMessageAction action = new InsertNewMessageAction( - subId, recipients, messageText, subject); - action.start(); - } - - public static long getLastSentMessageTimestamp() { - return sLastSentMessageTimestamp; - } - - private static final String KEY_SUB_ID = "sub_id"; - private static final String KEY_MESSAGE = "message"; - private static final String KEY_RECIPIENTS = "recipients"; - private static final String KEY_MESSAGE_TEXT = "message_text"; - private static final String KEY_SUBJECT_TEXT = "subject_text"; - - private InsertNewMessageAction(final MessageData message) { - this(message, ParticipantData.DEFAULT_SELF_SUB_ID); - actionParameters.putParcelable(KEY_MESSAGE, message); - } - - private InsertNewMessageAction(final MessageData message, final int subId) { - super(); - actionParameters.putParcelable(KEY_MESSAGE, message); - actionParameters.putInt(KEY_SUB_ID, subId); - } - - private InsertNewMessageAction(final int subId, final String recipients, - final String messageText, final String subject) { - super(); - if (TextUtils.isEmpty(recipients) || TextUtils.isEmpty(messageText)) { - Assert.fail("InsertNewMessageAction: Can't have empty recipients or message"); - } - actionParameters.putInt(KEY_SUB_ID, subId); - actionParameters.putString(KEY_RECIPIENTS, recipients); - actionParameters.putString(KEY_MESSAGE_TEXT, messageText); - actionParameters.putString(KEY_SUBJECT_TEXT, subject); - } - - /** - * Add message to database in pending state and queue actual sending - */ - @Override - protected Object executeAction() { - LogUtil.i(TAG, "InsertNewMessageAction: inserting new message"); - MessageData message = actionParameters.getParcelable(KEY_MESSAGE); - if (message == null) { - LogUtil.i(TAG, "InsertNewMessageAction: Creating MessageData with provided data"); - message = createMessage(); - if (message == null) { - LogUtil.w(TAG, "InsertNewMessageAction: Could not create MessageData"); - return null; - } - } - final DatabaseWrapper db = DataModel.get().getDatabase(); - final String conversationId = message.getConversationId(); - - final ParticipantData self = getSelf(db, conversationId, message); - if (self == null) { - return null; - } - message.bindSelfId(self.getId()); - // If the user taps the Send button before the conversation draft is created/loaded by - // ReadDraftDataAction (maybe the action service thread was busy), the MessageData may not - // have the participant id set. It should be equal to the self id, so we'll use that. - if (message.getParticipantId() == null) { - message.bindParticipantId(self.getId()); - } - - final long timestamp = System.currentTimeMillis(); - final ArrayList recipients = - BugleDatabaseOperations.getRecipientsForConversation(db, conversationId); - if (recipients.size() < 1) { - LogUtil.w(TAG, "InsertNewMessageAction: message recipients is empty"); - return null; - } - final int subId = self.getSubId(); - - // TODO: Work out whether to send with SMS or MMS (taking into account recipients)? - final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS); - if (isSms) { - String sendingConversationId = conversationId; - if (recipients.size() > 1) { - // Broadcast SMS - put message in "fake conversation" before farming out to real 1:1 - final long laterTimestamp = timestamp + 1; - // Send a single message - insertBroadcastSmsMessage(conversationId, message, subId, - laterTimestamp, recipients); - - sendingConversationId = null; - } - - for (final String recipient : recipients) { - // Start actual sending - insertSendingSmsMessage(message, subId, recipient, - timestamp, sendingConversationId); - } - - // Can now clear draft from conversation (deleting attachments if necessary) - BugleDatabaseOperations.updateDraftMessageData(db, conversationId, - null /* message */, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT); - } else { - final long timestampRoundedToSecond = 1000 * ((timestamp + 500) / 1000); - // Write place holder message directly referencing parts from the draft - final MessageData messageToSend = insertSendingMmsMessage(conversationId, - message, timestampRoundedToSecond); - - // Can now clear draft from conversation (preserving attachments which are now - // referenced by messageToSend) - BugleDatabaseOperations.updateDraftMessageData(db, conversationId, - messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT); - } - MessagingContentProvider.notifyConversationListChanged(); - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); - - return message; - } - - private ParticipantData getSelf( - final DatabaseWrapper db, final String conversationId, final MessageData message) { - ParticipantData self; - // Check if we are asked to bind to a non-default subId. This is directly passed in from - // the UI thread so that the sub id may be locked as soon as the user clicks on the Send - // button. - final int requestedSubId = actionParameters.getInt( - KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - if (requestedSubId != ParticipantData.DEFAULT_SELF_SUB_ID) { - self = BugleDatabaseOperations.getOrCreateSelf(db, requestedSubId); - } else { - String selfId = message.getSelfId(); - if (selfId == null) { - // The conversation draft provides no self id hint, meaning that 1) conversation - // self id was not loaded AND 2) the user didn't pick a SIM from the SIM selector. - // In this case, use the conversation's self id. - final ConversationListItemData conversation = - ConversationListItemData.getExistingConversation(db, conversationId); - if (conversation != null) { - selfId = conversation.getSelfId(); - } else { - LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId + - "already deleted before sending draft message " + - message.getMessageId() + ". Aborting InsertNewMessageAction."); - return null; - } - } - - // We do not use SubscriptionManager.DEFAULT_SUB_ID for sending a message, so we need - // to bind the message to the system default subscription if it's unbound. - final ParticipantData unboundSelf = BugleDatabaseOperations.getExistingParticipant( - db, selfId); - if (unboundSelf.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID - && OsUtil.isAtLeastL_MR1()) { - final int defaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId(); - self = BugleDatabaseOperations.getOrCreateSelf(db, defaultSubId); - } else { - self = unboundSelf; - } - } - return self; - } - - /** Create MessageData using KEY_RECIPIENTS, KEY_MESSAGE_TEXT and KEY_SUBJECT */ - private MessageData createMessage() { - // First find the thread id for this list of participants. - final String recipientsList = actionParameters.getString(KEY_RECIPIENTS); - final String messageText = actionParameters.getString(KEY_MESSAGE_TEXT); - final String subjectText = actionParameters.getString(KEY_SUBJECT_TEXT); - final int subId = actionParameters.getInt( - KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - - final ArrayList participants = new ArrayList<>(); - for (final String recipient : recipientsList.split(",")) { - participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, subId)); - } - if (participants.size() == 0) { - Assert.fail("InsertNewMessage: Empty participants"); - return null; - } - - final DatabaseWrapper db = DataModel.get().getDatabase(); - BugleDatabaseOperations.sanitizeConversationParticipants(participants); - final ArrayList recipients = - BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants); - if (recipients.size() == 0) { - Assert.fail("InsertNewMessage: Empty recipients"); - return null; - } - - final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(), - recipients); - - if (threadId < 0) { - Assert.fail("InsertNewMessage: Couldn't get threadId in SMS db for these recipients: " - + recipients.toString()); - // TODO: How do we fail the action? - return null; - } - - final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - false, participants, false, false, null); - - final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId); - - if (TextUtils.isEmpty(subjectText)) { - return MessageData.createDraftSmsMessage(conversationId, self.getId(), messageText); - } else { - return MessageData.createDraftMmsMessage(conversationId, self.getId(), messageText, - subjectText); - } - } - - private void insertBroadcastSmsMessage(final String conversationId, - final MessageData message, final int subId, final long laterTimestamp, - final ArrayList recipients) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "InsertNewMessageAction: Inserting broadcast SMS message " - + message.getMessageId()); - } - final Context context = Factory.get().getApplicationContext(); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // Inform sync that message is being added at timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(laterTimestamp); - - final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId); - final String address = TextUtils.join(" ", recipients); - - final String messageText = message.getMessageText(); - // Insert message into telephony database sms message table - final Uri messageUri = MmsUtils.insertSmsMessage(context, - Telephony.Sms.CONTENT_URI, - subId, - address, - messageText, - laterTimestamp, - Telephony.Sms.STATUS_COMPLETE, - Telephony.Sms.MESSAGE_TYPE_SENT, threadId); - if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) { - db.beginTransaction(); - try { - message.updateSendingMessage(conversationId, messageUri, laterTimestamp); - message.markMessageSent(laterTimestamp); - - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - - BugleDatabaseOperations.updateConversationMetadataInTransaction(db, - conversationId, message.getMessageId(), laterTimestamp, - false /* senderBlocked */, false /* shouldAutoSwitchSelfId */); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "InsertNewMessageAction: Inserted broadcast SMS message " - + message.getMessageId() + ", uri = " + message.getSmsMessageUri()); - } - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyPartsChanged(); - } else { - // Ignore error as we only really care about the individual messages? - LogUtil.e(TAG, - "InsertNewMessageAction: No uri for broadcast SMS " + message.getMessageId() - + " inserted into telephony DB"); - } - } - - /** - * Insert SMS messaging into our database and telephony db. - */ - private MessageData insertSendingSmsMessage(final MessageData content, final int subId, - final String recipient, final long timestamp, final String sendingConversationId) { - sLastSentMessageTimestamp = timestamp; - - final Context context = Factory.get().getApplicationContext(); - - // Inform sync that message is being added at timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(timestamp); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // Send a single message - long threadId; - String conversationId; - if (sendingConversationId == null) { - // For 1:1 message generated sending broadcast need to look up threadId+conversationId - threadId = MmsUtils.getOrCreateSmsThreadId(context, recipient); - conversationId = BugleDatabaseOperations.getOrCreateConversationFromRecipient( - db, threadId, false /* sender blocked */, - ParticipantData.getFromRawPhoneBySimLocale(recipient, subId)); - } else { - // Otherwise just look up threadId - threadId = BugleDatabaseOperations.getThreadId(db, sendingConversationId); - conversationId = sendingConversationId; - } - - final String messageText = content.getMessageText(); - - // Insert message into telephony database sms message table - final Uri messageUri = MmsUtils.insertSmsMessage(context, - Telephony.Sms.CONTENT_URI, - subId, - recipient, - messageText, - timestamp, - Telephony.Sms.STATUS_NONE, - Telephony.Sms.MESSAGE_TYPE_SENT, threadId); - - MessageData message = null; - if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) { - db.beginTransaction(); - try { - message = MessageData.createDraftSmsMessage(conversationId, - content.getSelfId(), messageText); - message.updateSendingMessage(conversationId, messageUri, timestamp); - - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - - // Do not update the conversation summary to reflect autogenerated 1:1 messages - if (sendingConversationId != null) { - BugleDatabaseOperations.updateConversationMetadataInTransaction(db, - conversationId, message.getMessageId(), timestamp, - false /* senderBlocked */, false /* shouldAutoSwitchSelfId */); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "InsertNewMessageAction: Inserted SMS message " - + message.getMessageId() + " (uri = " + message.getSmsMessageUri() - + ", timestamp = " + message.getReceivedTimeStamp() + ")"); - } - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyPartsChanged(); - } else { - LogUtil.e(TAG, "InsertNewMessageAction: No uri for SMS inserted into telephony DB"); - } - - return message; - } - - /** - * Insert MMS messaging into our database. - */ - private MessageData insertSendingMmsMessage(final String conversationId, - final MessageData message, final long timestamp) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - final List attachmentsUpdated = new ArrayList<>(); - try { - sLastSentMessageTimestamp = timestamp; - - // Insert "draft" message as placeholder until the final message is written to - // the telephony db - message.updateSendingMessage(conversationId, null/*messageUri*/, timestamp); - - // No need to inform SyncManager as message currently has no Uri... - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - - BugleDatabaseOperations.updateConversationMetadataInTransaction(db, - conversationId, message.getMessageId(), timestamp, - false /* senderBlocked */, false /* shouldAutoSwitchSelfId */); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "InsertNewMessageAction: Inserted MMS message " - + message.getMessageId() + " (timestamp = " + timestamp + ")"); - } - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyPartsChanged(); - - return message; - } - - private InsertNewMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public InsertNewMessageAction createFromParcel(final Parcel in) { - return new InsertNewMessageAction(in); - } - - @Override - public InsertNewMessageAction[] newArray(final int size) { - return new InsertNewMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java b/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java deleted file mode 100644 index 441a5a2..0000000 --- a/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony.Threads; -import android.provider.Telephony.ThreadsColumns; - -import com.android.messaging.Factory; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; - -public class LogTelephonyDatabaseAction extends Action implements Parcelable { - // Because we use sanitizePII, we should also use BUGLE_TAG - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final String[] ALL_THREADS_PROJECTION = { - Threads._ID, - Threads.DATE, - Threads.MESSAGE_COUNT, - Threads.RECIPIENT_IDS, - Threads.SNIPPET, - Threads.SNIPPET_CHARSET, - Threads.READ, - Threads.ERROR, - Threads.HAS_ATTACHMENT }; - - // Constants from the Telephony Database - private static final int ID = 0; - private static final int DATE = 1; - private static final int MESSAGE_COUNT = 2; - private static final int RECIPIENT_IDS = 3; - private static final int SNIPPET = 4; - private static final int SNIPPET_CHAR_SET = 5; - private static final int READ = 6; - private static final int ERROR = 7; - private static final int HAS_ATTACHMENT = 8; - - /** - * Log telephony data to logcat - */ - public static void dumpDatabase() { - final LogTelephonyDatabaseAction action = new LogTelephonyDatabaseAction(); - action.start(); - } - - private LogTelephonyDatabaseAction() { - } - - @Override - protected Object executeAction() { - final Context context = Factory.get().getApplicationContext(); - - if (!DebugUtils.isDebugEnabled()) { - LogUtil.e(TAG, "Can't log telephony database unless debugging is enabled"); - return null; - } - - if (!LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.w(TAG, "Can't log telephony database unless DEBUG is turned on for TAG: " + - TAG); - return null; - } - - LogUtil.d(TAG, "\n"); - LogUtil.d(TAG, "Dump of canoncial_addresses table"); - LogUtil.d(TAG, "*********************************"); - - Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(), - Uri.parse("content://mms-sms/canonical-addresses"), null, null, null, null); - - if (cursor == null) { - LogUtil.w(TAG, "null Cursor in content://mms-sms/canonical-addresses"); - } else { - try { - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String number = cursor.getString(1); - LogUtil.d(TAG, LogUtil.sanitizePII("id: " + id + " number: " + number)); - } - } finally { - cursor.close(); - } - } - - LogUtil.d(TAG, "\n"); - LogUtil.d(TAG, "Dump of threads table"); - LogUtil.d(TAG, "*********************"); - - cursor = SqliteWrapper.query(context, context.getContentResolver(), - Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(), - ALL_THREADS_PROJECTION, null, null, "date ASC"); - try { - while (cursor.moveToNext()) { - LogUtil.d(TAG, LogUtil.sanitizePII("threadId: " + cursor.getLong(ID) + - " " + ThreadsColumns.DATE + " : " + cursor.getLong(DATE) + - " " + ThreadsColumns.MESSAGE_COUNT + " : " + cursor.getInt(MESSAGE_COUNT) + - " " + ThreadsColumns.SNIPPET + " : " + cursor.getString(SNIPPET) + - " " + ThreadsColumns.READ + " : " + cursor.getInt(READ) + - " " + ThreadsColumns.ERROR + " : " + cursor.getInt(ERROR) + - " " + ThreadsColumns.HAS_ATTACHMENT + " : " + - cursor.getInt(HAS_ATTACHMENT) + - " " + ThreadsColumns.RECIPIENT_IDS + " : " + - cursor.getString(RECIPIENT_IDS))); - } - } finally { - cursor.close(); - } - - return null; - } - - private LogTelephonyDatabaseAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public LogTelephonyDatabaseAction createFromParcel(final Parcel in) { - return new LogTelephonyDatabaseAction(in); - } - - @Override - public LogTelephonyDatabaseAction[] newArray(final int size) { - return new LogTelephonyDatabaseAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/MarkAsReadAction.java b/src/com/android/messaging/datamodel/action/MarkAsReadAction.java deleted file mode 100644 index 31bc59d..0000000 --- a/src/com/android/messaging/datamodel/action/MarkAsReadAction.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.LogUtil; - -/** - * Action used to mark all the messages in a conversation as read - */ -public class MarkAsReadAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static final String KEY_CONVERSATION_ID = "conversation_id"; - - /** - * Mark all the messages as read for a particular conversation. - */ - public static void markAsRead(final String conversationId) { - final MarkAsReadAction action = new MarkAsReadAction(conversationId); - action.start(); - } - - private MarkAsReadAction(final String conversationId) { - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - } - - @Override - protected Object executeAction() { - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - - // TODO: Consider doing this in background service to avoid delaying other actions - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // Mark all messages in thread as read in telephony - final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId); - if (threadId != -1) { - MmsUtils.updateSmsReadStatus(threadId, Long.MAX_VALUE); - } - - // Update local db - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - values.put(MessageColumns.CONVERSATION_ID, conversationId); - values.put(MessageColumns.READ, 1); - values.put(MessageColumns.SEEN, 1); // if they read it, they saw it - - final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values, - "(" + MessageColumns.READ + " !=1 OR " + - MessageColumns.SEEN + " !=1 ) AND " + - MessageColumns.CONVERSATION_ID + "=?", - new String[] { conversationId }); - if (count > 0) { - MessagingContentProvider.notifyMessagesChanged(conversationId); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - // After marking messages as read, update the notifications. This will - // clear the now stale notifications. - BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL); - return null; - } - - private MarkAsReadAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MarkAsReadAction createFromParcel(final Parcel in) { - return new MarkAsReadAction(in); - } - - @Override - public MarkAsReadAction[] newArray(final int size) { - return new MarkAsReadAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java b/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java deleted file mode 100644 index 28f55fd..0000000 --- a/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.util.LogUtil; - -/** - * Action used to mark all messages as seen - */ -public class MarkAsSeenAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final String KEY_CONVERSATION_ID = "conversation_id"; - - /** - * Mark all messages as seen. - */ - public static void markAllAsSeen() { - final MarkAsSeenAction action = new MarkAsSeenAction((String) null/*conversationId*/); - action.start(); - } - - /** - * Mark all messages of a given conversation as seen. - */ - public static void markAsSeen(final String conversationId) { - final MarkAsSeenAction action = new MarkAsSeenAction(conversationId); - action.start(); - } - - /** - * ctor for MarkAsSeenAction. - * @param conversationId the conversation id for which to mark as seen, or null to mark all - * messages as seen - */ - public MarkAsSeenAction(final String conversationId) { - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - } - - @Override - protected Object executeAction() { - final String conversationId = - actionParameters.getString(KEY_CONVERSATION_ID); - final boolean hasSpecificConversation = !TextUtils.isEmpty(conversationId); - - // Everything in telephony should already have the seen bit set. - // Possible exception are messages which did not have seen set and - // were sync'ed into bugle. - - // Now mark the messages as seen in the bugle db - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - - try { - final ContentValues values = new ContentValues(); - values.put(MessageColumns.SEEN, 1); - - if (hasSpecificConversation) { - final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values, - MessageColumns.SEEN + " != 1 AND " + - MessageColumns.CONVERSATION_ID + "=?", - new String[] { conversationId }); - if (count > 0) { - MessagingContentProvider.notifyMessagesChanged(conversationId); - } - } else { - db.update(DatabaseHelper.MESSAGES_TABLE, values, - MessageColumns.SEEN + " != 1", null/*selectionArgs*/); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - // After marking messages as seen, update the notifications. This will - // clear the now stale notifications. - BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL); - return null; - } - - private MarkAsSeenAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MarkAsSeenAction createFromParcel(final Parcel in) { - return new MarkAsSeenAction(in); - } - - @Override - public MarkAsSeenAction[] newArray(final int size) { - return new MarkAsSeenAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java b/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java deleted file mode 100644 index fbd4e82..0000000 --- a/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentUris; -import android.content.ContentValues; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.concurrent.TimeUnit; - -public class ProcessDeliveryReportAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static final String KEY_URI = "uri"; - private static final String KEY_STATUS = "status"; - - private ProcessDeliveryReportAction(final Uri uri, final int status) { - actionParameters.putParcelable(KEY_URI, uri); - actionParameters.putInt(KEY_STATUS, status); - } - - public static void deliveryReportReceived(final Uri uri, final int status) { - final ProcessDeliveryReportAction action = new ProcessDeliveryReportAction(uri, status); - action.start(); - } - - @Override - protected Object executeAction() { - final Uri smsMessageUri = actionParameters.getParcelable(KEY_URI); - final int status = actionParameters.getInt(KEY_STATUS); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final long messageRowId = ContentUris.parseId(smsMessageUri); - if (messageRowId < 0) { - LogUtil.e(TAG, "ProcessDeliveryReportAction: can't find message"); - return null; - } - final long timeSentInMillis = System.currentTimeMillis(); - // Update telephony provider - if (smsMessageUri != null) { - MmsUtils.updateSmsStatusAndDateSent(smsMessageUri, status, timeSentInMillis); - } - - // Update local message - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - final int bugleStatus = SyncMessageBatch.bugleStatusForSms(true /*outgoing*/, - Telephony.Sms.MESSAGE_TYPE_SENT /* type */, status); - values.put(DatabaseHelper.MessageColumns.STATUS, bugleStatus); - values.put(DatabaseHelper.MessageColumns.SENT_TIMESTAMP, - TimeUnit.MILLISECONDS.toMicros(timeSentInMillis)); - - final MessageData messageData = - BugleDatabaseOperations.readMessageData(db, smsMessageUri); - - // Check the message was not removed before the delivery report comes in - if (messageData != null) { - Assert.isTrue(smsMessageUri.equals(messageData.getSmsMessageUri())); - - // Row must exist as was just loaded above (on ActionService thread) - BugleDatabaseOperations.updateMessageRow(db, messageData.getMessageId(), values); - - MessagingContentProvider.notifyMessagesChanged(messageData.getConversationId()); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - return null; - } - - private ProcessDeliveryReportAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ProcessDeliveryReportAction createFromParcel(final Parcel in) { - return new ProcessDeliveryReportAction(in); - } - - @Override - public ProcessDeliveryReportAction[] newArray(final int size) { - return new ProcessDeliveryReportAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java b/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java deleted file mode 100644 index 757ea05..0000000 --- a/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.Activity; -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony.Mms; -import android.telephony.SmsManager; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelException; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.MmsFileProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.mmslib.pdu.RetrieveConf; -import com.android.messaging.sms.DatabaseMessages; -import com.android.messaging.sms.MmsSender; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.google.common.io.Files; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; - -/** - * Processes an MMS message after it has been downloaded. - * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure). - */ -public class ProcessDownloadedMmsAction extends Action { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - // Always set when message downloaded - private static final String KEY_DOWNLOADED_BY_PLATFORM = "downloaded_by_platform"; - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_NOTIFICATION_URI = "notification_uri"; - private static final String KEY_CONVERSATION_ID = "conversation_id"; - private static final String KEY_PARTICIPANT_ID = "participant_id"; - private static final String KEY_STATUS_IF_FAILED = "status_if_failed"; - - // Set when message downloaded by platform (L+) - private static final String KEY_RESULT_CODE = "result_code"; - private static final String KEY_HTTP_STATUS_CODE = "http_status_code"; - private static final String KEY_CONTENT_URI = "content_uri"; - private static final String KEY_SUB_ID = "sub_id"; - private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number"; - private static final String KEY_TRANSACTION_ID = "transaction_id"; - private static final String KEY_CONTENT_LOCATION = "content_location"; - private static final String KEY_AUTO_DOWNLOAD = "auto_download"; - private static final String KEY_RECEIVED_TIMESTAMP = "received_timestamp"; - - // Set when message downloaded by us (legacy) - private static final String KEY_STATUS = "status"; - private static final String KEY_RAW_STATUS = "raw_status"; - private static final String KEY_MMS_URI = "mms_uri"; - - // Used to send a deferred response in response to auto-download failure - private static final String KEY_SEND_DEFERRED_RESP_STATUS = "send_deferred_resp_status"; - - // Results passed from background worker to processCompletion - private static final String BUNDLE_REQUEST_STATUS = "request_status"; - private static final String BUNDLE_RAW_TELEPHONY_STATUS = "raw_status"; - private static final String BUNDLE_MMS_URI = "mms_uri"; - - // This is called when MMS lib API returns via PendingIntent - public static void processMessageDownloaded(final int resultCode, final Bundle extras) { - final String messageId = extras.getString(DownloadMmsAction.EXTRA_MESSAGE_ID); - final Uri contentUri = extras.getParcelable(DownloadMmsAction.EXTRA_CONTENT_URI); - final Uri notificationUri = extras.getParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI); - final String conversationId = extras.getString(DownloadMmsAction.EXTRA_CONVERSATION_ID); - final String participantId = extras.getString(DownloadMmsAction.EXTRA_PARTICIPANT_ID); - Assert.notNull(messageId); - Assert.notNull(contentUri); - Assert.notNull(notificationUri); - Assert.notNull(conversationId); - Assert.notNull(participantId); - - final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction(); - final Bundle params = action.actionParameters; - params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true); - params.putString(KEY_MESSAGE_ID, messageId); - params.putInt(KEY_RESULT_CODE, resultCode); - params.putInt(KEY_HTTP_STATUS_CODE, - extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0)); - params.putParcelable(KEY_CONTENT_URI, contentUri); - params.putParcelable(KEY_NOTIFICATION_URI, notificationUri); - params.putInt(KEY_SUB_ID, - extras.getInt(DownloadMmsAction.EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID)); - params.putString(KEY_SUB_PHONE_NUMBER, - extras.getString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER)); - params.putString(KEY_TRANSACTION_ID, - extras.getString(DownloadMmsAction.EXTRA_TRANSACTION_ID)); - params.putString(KEY_CONTENT_LOCATION, - extras.getString(DownloadMmsAction.EXTRA_CONTENT_LOCATION)); - params.putBoolean(KEY_AUTO_DOWNLOAD, - extras.getBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD)); - params.putLong(KEY_RECEIVED_TIMESTAMP, - extras.getLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP)); - params.putString(KEY_CONVERSATION_ID, conversationId); - params.putString(KEY_PARTICIPANT_ID, participantId); - params.putInt(KEY_STATUS_IF_FAILED, - extras.getInt(DownloadMmsAction.EXTRA_STATUS_IF_FAILED)); - action.start(); - } - - // This is called for fast failing downloading (due to airplane mode or mobile data ) - public static void processMessageDownloadFastFailed(final String messageId, - final Uri notificationUri, final String conversationId, final String participantId, - final String contentLocation, final int subId, final String subPhoneNumber, - final int statusIfFailed, final boolean autoDownload, final String transactionId, - final int resultCode) { - Assert.notNull(messageId); - Assert.notNull(notificationUri); - Assert.notNull(conversationId); - Assert.notNull(participantId); - - final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction(); - final Bundle params = action.actionParameters; - params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true); - params.putString(KEY_MESSAGE_ID, messageId); - params.putInt(KEY_RESULT_CODE, resultCode); - params.putParcelable(KEY_NOTIFICATION_URI, notificationUri); - params.putInt(KEY_SUB_ID, subId); - params.putString(KEY_SUB_PHONE_NUMBER, subPhoneNumber); - params.putString(KEY_CONTENT_LOCATION, contentLocation); - params.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload); - params.putString(KEY_CONVERSATION_ID, conversationId); - params.putString(KEY_PARTICIPANT_ID, participantId); - params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed); - params.putString(KEY_TRANSACTION_ID, transactionId); - action.start(); - } - - public static void processDownloadActionFailure(final String messageId, final int status, - final int rawStatus, final String conversationId, final String participantId, - final int statusIfFailed, final int subId, final String transactionId) { - Assert.notNull(messageId); - Assert.notNull(conversationId); - Assert.notNull(participantId); - - final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction(); - final Bundle params = action.actionParameters; - params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, false); - params.putString(KEY_MESSAGE_ID, messageId); - params.putInt(KEY_STATUS, status); - params.putInt(KEY_RAW_STATUS, rawStatus); - params.putString(KEY_CONVERSATION_ID, conversationId); - params.putString(KEY_PARTICIPANT_ID, participantId); - params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed); - params.putInt(KEY_SUB_ID, subId); - params.putString(KEY_TRANSACTION_ID, transactionId); - action.start(); - } - - public static void sendDeferredRespStatus(final String messageId, final String transactionId, - final String contentLocation, final int subId) { - final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction(); - final Bundle params = action.actionParameters; - params.putString(KEY_MESSAGE_ID, messageId); - params.putString(KEY_TRANSACTION_ID, transactionId); - params.putString(KEY_CONTENT_LOCATION, contentLocation); - params.putBoolean(KEY_SEND_DEFERRED_RESP_STATUS, true); - params.putInt(KEY_SUB_ID, subId); - action.start(); - } - - private ProcessDownloadedMmsAction() { - // Callers must use one of the static methods above - } - - @Override - protected Object executeAction() { - // Fire up the background worker - requestBackgroundWork(); - return null; - } - - @Override - protected Bundle doBackgroundWork() throws DataModelException { - final Context context = Factory.get().getApplicationContext(); - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID); - final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION); - final boolean sendDeferredRespStatus = - actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS, false); - - // Send a response indicating that auto-download failed - if (sendDeferredRespStatus) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "DownloadMmsAction: Auto-download of message " + messageId - + " failed; sending DEFERRED NotifyRespInd"); - } - MmsUtils.sendNotifyResponseForMmsDownload( - context, - subId, - MmsUtils.stringToBytes(transactionId, "UTF-8"), - contentLocation, - PduHeaders.STATUS_DEFERRED); - return null; - } - - // Processing a real MMS download - final boolean downloadedByPlatform = actionParameters.getBoolean( - KEY_DOWNLOADED_BY_PLATFORM); - - final int status; - int rawStatus = MmsUtils.PDU_HEADER_VALUE_UNDEFINED; - Uri mmsUri = null; - - if (downloadedByPlatform) { - final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); - if (resultCode == Activity.RESULT_OK) { - final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI); - final File downloadedFile = MmsFileProvider.getFile(contentUri); - byte[] downloadedData = null; - try { - downloadedData = Files.toByteArray(downloadedFile); - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "ProcessDownloadedMmsAction: MMS download file not found: " - + downloadedFile.getAbsolutePath()); - } catch (final IOException e) { - LogUtil.e(TAG, "ProcessDownloadedMmsAction: Error reading MMS download file: " - + downloadedFile.getAbsolutePath(), e); - } - - // Can delete the temp file now - if (downloadedFile.exists()) { - downloadedFile.delete(); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ProcessDownloadedMmsAction: Deleted temp file with " - + "downloaded MMS pdu: " + downloadedFile.getAbsolutePath()); - } - } - - if (downloadedData != null) { - final RetrieveConf retrieveConf = - MmsSender.parseRetrieveConf(downloadedData, subId); - if (MmsUtils.isDumpMmsEnabled()) { - MmsUtils.dumpPdu(downloadedData, retrieveConf); - } - if (retrieveConf != null) { - // Insert the downloaded MMS into telephony - final Uri notificationUri = actionParameters.getParcelable( - KEY_NOTIFICATION_URI); - final String subPhoneNumber = actionParameters.getString( - KEY_SUB_PHONE_NUMBER); - final boolean autoDownload = actionParameters.getBoolean( - KEY_AUTO_DOWNLOAD); - final long receivedTimestampInSeconds = - actionParameters.getLong(KEY_RECEIVED_TIMESTAMP); - - // Inform sync we're adding a message to telephony - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(receivedTimestampInSeconds * 1000L); - - final MmsUtils.StatusPlusUri result = - MmsUtils.insertDownloadedMessageAndSendResponse(context, - notificationUri, subId, subPhoneNumber, transactionId, - contentLocation, autoDownload, receivedTimestampInSeconds, - retrieveConf); - status = result.status; - rawStatus = result.rawStatus; - mmsUri = result.uri; - } else { - // Invalid response PDU - status = MmsUtils.MMS_REQUEST_MANUAL_RETRY; - } - } else { - // Failed to read download file - status = MmsUtils.MMS_REQUEST_MANUAL_RETRY; - } - } else { - LogUtil.w(TAG, "ProcessDownloadedMmsAction: Platform returned error resultCode: " - + resultCode); - final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); - status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode); - } - } else { - // Message was already processed by the internal API, or the download action failed. - // In either case, we just need to copy the status to the response bundle. - status = actionParameters.getInt(KEY_STATUS); - rawStatus = actionParameters.getInt(KEY_RAW_STATUS); - mmsUri = actionParameters.getParcelable(KEY_MMS_URI); - } - - final Bundle response = new Bundle(); - response.putInt(BUNDLE_REQUEST_STATUS, status); - response.putInt(BUNDLE_RAW_TELEPHONY_STATUS, rawStatus); - response.putParcelable(BUNDLE_MMS_URI, mmsUri); - return response; - } - - @Override - protected Object processBackgroundResponse(final Bundle response) { - if (response == null) { - // No message download to process; doBackgroundWork sent a notify deferred response - Assert.isTrue(actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS)); - return null; - } - - final int status = response.getInt(BUNDLE_REQUEST_STATUS); - final int rawStatus = response.getInt(BUNDLE_RAW_TELEPHONY_STATUS); - final Uri messageUri = response.getParcelable(BUNDLE_MMS_URI); - final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - - // Do post-processing on downloaded message - final MessageData message = processResult(status, rawStatus, messageUri); - - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - // If we were trying to auto-download but have failed need to send the deferred response - if (autoDownload && message == null && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) { - final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID); - final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION); - sendDeferredRespStatus(messageId, transactionId, contentLocation, subId); - } - - if (autoDownload) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - MessageData toastMessage = message; - if (toastMessage == null) { - // If the downloaded failed (message is null), then we should announce the - // receiving of the wap push message. Load the wap push message here instead. - toastMessage = BugleDatabaseOperations.readMessageData(db, messageId); - } - if (toastMessage != null) { - final ParticipantData sender = ParticipantData.getFromId( - db, toastMessage.getParticipantId()); - BugleActionToasts.onMessageReceived( - toastMessage.getConversationId(), sender, toastMessage); - } - } else { - final boolean success = message != null && status == MmsUtils.MMS_REQUEST_SUCCEEDED; - BugleActionToasts.onSendMessageOrManualDownloadActionCompleted( - // If download failed, use the wap push message's conversation instead - success ? message.getConversationId() - : actionParameters.getString(KEY_CONVERSATION_ID), - success, status, false/*isSms*/, subId, false /*isSend*/); - } - - final boolean failed = (messageUri == null); - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(failed, this); - if (failed) { - BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS); - } - - return message; - } - - @Override - protected Object processBackgroundFailure() { - if (actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS)) { - // We can early-out for these failures. processResult is only designed to handle - // post-processing of MMS downloads (whether successful or not). - LogUtil.w(TAG, - "ProcessDownloadedMmsAction: Exception while sending deferred NotifyRespInd"); - return null; - } - - // Background worker threw an exception; require manual retry - processResult(MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, - null /* mmsUri */); - - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true /* failed */, - this); - - return null; - } - - private MessageData processResult(final int status, final int rawStatus, final Uri mmsUri) { - final Context context = Factory.get().getApplicationContext(); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final Uri mmsNotificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI); - final String notificationConversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final String notificationParticipantId = actionParameters.getString(KEY_PARTICIPANT_ID); - final int statusIfFailed = actionParameters.getInt(KEY_STATUS_IF_FAILED); - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - - Assert.notNull(messageId); - - LogUtil.i(TAG, "ProcessDownloadedMmsAction: Processed MMS download of message " + messageId - + "; status is " + MmsUtils.getRequestStatusDescription(status)); - - DatabaseMessages.MmsMessage mms = null; - if (status == MmsUtils.MMS_REQUEST_SUCCEEDED && mmsUri != null) { - // Delete the initial M-Notification.ind from telephony - SqliteWrapper.delete(context, context.getContentResolver(), - mmsNotificationUri, null, null); - - // Read the sent MMS from the telephony provider - mms = MmsUtils.loadMms(mmsUri); - } - - boolean messageInFocusedConversation = false; - boolean messageInObservableConversation = false; - String conversationId = null; - MessageData message = null; - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - if (mms != null) { - final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId()); - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - - final List recipients = MmsUtils.getRecipientsByThread(mms.mThreadId); - String from = MmsUtils.getMmsSender(recipients, mms.getUri()); - if (from == null) { - LogUtil.w(TAG, - "Downloaded an MMS without sender address; using unknown sender."); - from = ParticipantData.getUnknownSenderDestination(); - } - final ParticipantData sender = ParticipantData.getFromRawPhoneBySimLocale(from, - subId); - final String senderParticipantId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender); - if (!senderParticipantId.equals(notificationParticipantId)) { - LogUtil.e(TAG, "ProcessDownloadedMmsAction: Downloaded MMS message " - + messageId + " has different sender (participantId = " - + senderParticipantId + ") than notification (" - + notificationParticipantId + ")"); - } - final boolean blockedSender = BugleDatabaseOperations.isBlockedDestination( - db, sender.getNormalizedDestination()); - conversationId = BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, - mms.mThreadId, blockedSender, subId); - - messageInFocusedConversation = - DataModel.get().isFocusedConversation(conversationId); - messageInObservableConversation = - DataModel.get().isNewMessageObservable(conversationId); - - // TODO: Also write these values to the telephony provider - mms.mRead = messageInFocusedConversation; - mms.mSeen = messageInObservableConversation; - - // Translate to our format - message = MmsUtils.createMmsMessage(mms, conversationId, senderParticipantId, - selfId, MessageData.BUGLE_STATUS_INCOMING_COMPLETE); - // Update image sizes. - message.updateSizesForImageParts(); - // Inform sync that message has been added at local received timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(message.getReceivedTimeStamp()); - final MessageData current = BugleDatabaseOperations.readMessageData(db, messageId); - if (current == null) { - LogUtil.w(TAG, "Message deleted prior to update"); - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - } else { - // Overwrite existing notification message - message.updateMessageId(messageId); - // Write message - BugleDatabaseOperations.updateMessageInTransaction(db, message); - } - - if (!TextUtils.equals(notificationConversationId, conversationId)) { - // If this is a group conversation, the message is moved. So the original - // 1v1 conversation (as referenced by notificationConversationId) could - // be left with no non-draft message. Delete the conversation if that - // happens. See the comment for the method below for why we need to do this. - if (!BugleDatabaseOperations.deleteConversationIfEmptyInTransaction( - db, notificationConversationId)) { - BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction( - db, notificationConversationId, messageId, - true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/); - } - } - - BugleDatabaseOperations.refreshConversationMetadataInTransaction(db, conversationId, - true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/); - } else { - messageInFocusedConversation = - DataModel.get().isFocusedConversation(notificationConversationId); - - // Default to retry status unless status indicates otherwise - int bugleStatus = statusIfFailed; - if (status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) { - bugleStatus = MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED; - } else if (status == MmsUtils.MMS_REQUEST_NO_RETRY) { - bugleStatus = MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE; - } - DownloadMmsAction.updateMessageStatus(mmsNotificationUri, messageId, - notificationConversationId, bugleStatus, rawStatus); - - // Log MMS download failed - final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); - final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); - - // Just in case this was the latest message update the summary data - BugleDatabaseOperations.refreshConversationMetadataInTransaction(db, - notificationConversationId, true /*shouldAutoSwitchSelfId*/, - false /*keepArchived*/); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - if (mmsUri != null) { - // Update mms table with read status now we know the conversation id - final ContentValues values = new ContentValues(1); - values.put(Mms.READ, messageInFocusedConversation); - SqliteWrapper.update(context, context.getContentResolver(), mmsUri, values, - null, null); - } - - // Show a notification to let the user know a new message has arrived - BugleNotifications.update(false /*silent*/, conversationId, BugleNotifications.UPDATE_ALL); - - // Messages may have changed in two conversations - if (conversationId != null) { - MessagingContentProvider.notifyMessagesChanged(conversationId); - } - MessagingContentProvider.notifyMessagesChanged(notificationConversationId); - MessagingContentProvider.notifyPartsChanged(); - - return message; - } - - private ProcessDownloadedMmsAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ProcessDownloadedMmsAction createFromParcel(final Parcel in) { - return new ProcessDownloadedMmsAction(in); - } - - @Override - public ProcessDownloadedMmsAction[] newArray(final int size) { - return new ProcessDownloadedMmsAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java deleted file mode 100644 index 8a41f4a..0000000 --- a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.os.Parcel; -import android.os.Parcelable; -import android.telephony.ServiceState; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.ConnectivityUtil.ConnectivityListener; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.HashSet; -import java.util.Set; - -/** - * Action used to lookup any messages in the pending send/download state and either fail them or - * retry their action. This action only initiates one retry at a time - further retries should be - * triggered by successful sending of a message, network status change or exponential backoff timer. - */ -public class ProcessPendingMessagesAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final int PENDING_INTENT_REQUEST_CODE = 101; - - public static void processFirstPendingMessage() { - // Clear any pending alarms or connectivity events - unregister(); - // Clear retry count - setRetry(0); - - // Start action - final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction(); - action.start(); - } - - public static void scheduleProcessPendingMessagesAction(final boolean failed, - final Action processingAction) { - LogUtil.i(TAG, "ProcessPendingMessagesAction: Scheduling pending messages" - + (failed ? "(message failed)" : "")); - // Can safely clear any pending alarms or connectivity events as either an action - // is currently running or we will run now or register if pending actions possible. - unregister(); - - final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp(); - boolean scheduleAlarm = false; - // If message succeeded and if Bugle is default SMS app just carry on with next message - if (!failed && isDefaultSmsApp) { - // Clear retry attempt count as something just succeeded - setRetry(0); - - // Lookup and queue next message for immediate processing by background worker - // iff there are no pending messages this will do nothing and return true. - final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction(); - if (action.queueActions(processingAction)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - if (processingAction.hasBackgroundActions()) { - LogUtil.v(TAG, "ProcessPendingMessagesAction: Action queued"); - } else { - LogUtil.v(TAG, "ProcessPendingMessagesAction: No actions to queue"); - } - } - // Have queued next action if needed, nothing more to do - return; - } - // In case of error queuing schedule a retry - scheduleAlarm = true; - LogUtil.w(TAG, "ProcessPendingMessagesAction: Action failed to queue; retrying"); - } - if (getHavePendingMessages() || scheduleAlarm) { - // Still have a pending message that needs to be queued for processing - final ConnectivityListener listener = new ConnectivityListener() { - @Override - public void onConnectivityStateChanged(final Context context, final Intent intent) { - final int networkType = - MmsUtils.getConnectivityEventNetworkType(context, intent); - if (networkType != ConnectivityManager.TYPE_MOBILE) { - return; - } - final boolean isConnected = !intent.getBooleanExtra( - ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - // TODO: Should we check in more detail? - if (isConnected) { - onConnected(); - } - } - - @Override - public void onPhoneStateChanged(final Context context, final int serviceState) { - if (serviceState == ServiceState.STATE_IN_SERVICE) { - onConnected(); - } - } - - private void onConnected() { - LogUtil.i(TAG, "ProcessPendingMessagesAction: Now connected; starting action"); - - // Clear any pending alarms or connectivity events but leave attempt count alone - unregister(); - - // Start action - final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction(); - action.start(); - } - }; - // Read and increment attempt number from shared prefs - final int retryAttempt = getNextRetry(); - register(listener, retryAttempt); - } else { - // No more pending messages (presumably the message that failed has expired) or it - // may be possible that a send and a download are already in process. - // Clear retry attempt count. - // TODO Might be premature if send and download in process... - // but worst case means we try to send a bit more often. - setRetry(0); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ProcessPendingMessagesAction: No more pending messages"); - } - } - } - - private static void register(final ConnectivityListener listener, final int retryAttempt) { - int retryNumber = retryAttempt; - - // Register to be notified about connectivity changes - DataModel.get().getConnectivityUtil().register(listener); - - final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction(); - final long initialBackoffMs = BugleGservices.get().getLong( - BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS, - BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT); - final long maxDelayMs = BugleGservices.get().getLong( - BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS, - BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT); - long delayMs; - long nextDelayMs = initialBackoffMs; - do { - delayMs = nextDelayMs; - retryNumber--; - nextDelayMs = delayMs * 2; - } - while (retryNumber > 0 && nextDelayMs < maxDelayMs); - - LogUtil.i(TAG, "ProcessPendingMessagesAction: Registering for retry #" + retryAttempt - + " in " + delayMs + " ms"); - - action.schedule(PENDING_INTENT_REQUEST_CODE, delayMs); - } - - private static void unregister() { - // Clear any pending alarms or connectivity events - DataModel.get().getConnectivityUtil().unregister(); - - final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction(); - action.schedule(PENDING_INTENT_REQUEST_CODE, Long.MAX_VALUE); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ProcessPendingMessagesAction: Unregistering for connectivity changed " - + "events and clearing scheduled alarm"); - } - } - - private static void setRetry(final int retryAttempt) { - final BuglePrefs prefs = Factory.get().getApplicationPrefs(); - prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt); - } - - private static int getNextRetry() { - final BuglePrefs prefs = Factory.get().getApplicationPrefs(); - final int retryAttempt = - prefs.getInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, 0) + 1; - prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt); - return retryAttempt; - } - - private ProcessPendingMessagesAction() { - } - - /** - * Read from the DB and determine if there are any messages we should process - * @return true if we have pending messages - */ - private static boolean getHavePendingMessages() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final long now = System.currentTimeMillis(); - - final String toSendMessageId = findNextMessageToSend(db, now); - if (toSendMessageId != null) { - return true; - } else { - final String toDownloadMessageId = findNextMessageToDownload(db, now); - if (toDownloadMessageId != null) { - return true; - } - } - // Messages may be in the process of sending/downloading even when there are no pending - // messages... - return false; - } - - /** - * Queue any pending actions - * @param actionState - * @return true if action queued (or no actions to queue) else false - */ - private boolean queueActions(final Action processingAction) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final long now = System.currentTimeMillis(); - boolean succeeded = true; - - // Will queue no more than one message to send plus one message to download - // This keeps outgoing messages "in order" but allow downloads to happen even if sending - // gets blocked until messages time out. Manual resend bumps messages to head of queue. - final String toSendMessageId = findNextMessageToSend(db, now); - final String toDownloadMessageId = findNextMessageToDownload(db, now); - if (toSendMessageId != null) { - LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId - + " for sending"); - // This could queue nothing - if (!SendMessageAction.queueForSendInBackground(toSendMessageId, processingAction)) { - LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message " - + toSendMessageId + " for sending"); - succeeded = false; - } - } - if (toDownloadMessageId != null) { - LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toDownloadMessageId - + " for download"); - // This could queue nothing - if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId, - processingAction)) { - LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message " - + toDownloadMessageId + " for download"); - succeeded = false; - } - } - if (toSendMessageId == null && toDownloadMessageId == null) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download"); - } - } - return succeeded; - } - - @Override - protected Object executeAction() { - // If triggered by alarm will not have unregistered yet - unregister(); - - if (PhoneUtils.getDefault().isDefaultSmsApp()) { - queueActions(this); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ProcessPendingMessagesAction: Not default SMS app; rescheduling"); - } - scheduleProcessPendingMessagesAction(true, this); - } - - return null; - } - - private static String findNextMessageToSend(final DatabaseWrapper db, final long now) { - String toSendMessageId = null; - db.beginTransaction(); - Cursor sending = null; - Cursor cursor = null; - int sendingCnt = 0; - int pendingCnt = 0; - int failedCnt = 0; - try { - // First check to see if we have any messages already sending - sending = db.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), - DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)", - new String[]{Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING), - Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)}, - null, - null, - DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC"); - final boolean messageCurrentlySending = sending.moveToNext(); - sendingCnt = sending.getCount(); - // Look for messages we could send - final ContentValues values = new ContentValues(); - values.put(DatabaseHelper.MessageColumns.STATUS, - MessageData.BUGLE_STATUS_OUTGOING_FAILED); - cursor = db.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), - DatabaseHelper.MessageColumns.STATUS + " IN (" - + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + "," - + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")", - null, - null, - null, - DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC"); - pendingCnt = cursor.getCount(); - - while (cursor.moveToNext()) { - final MessageData message = new MessageData(); - message.bind(cursor); - if (message.getInResendWindow(now)) { - // If no messages currently sending - if (!messageCurrentlySending) { - // Resend this message - toSendMessageId = message.getMessageId(); - // Before queuing the message for resending, check if the message's self is - // active. If not, switch back to the system's default subscription. - if (OsUtil.isAtLeastL_MR1()) { - final ParticipantData messageSelf = BugleDatabaseOperations - .getExistingParticipant(db, message.getSelfId()); - if (messageSelf == null || !messageSelf.isActiveSubscription()) { - final ParticipantData defaultSelf = BugleDatabaseOperations - .getOrCreateSelf(db, PhoneUtils.getDefault() - .getDefaultSmsSubscriptionId()); - if (defaultSelf != null) { - message.bindSelfId(defaultSelf.getId()); - final ContentValues selfValues = new ContentValues(); - selfValues.put(MessageColumns.SELF_PARTICIPANT_ID, - defaultSelf.getId()); - BugleDatabaseOperations.updateMessageRow(db, - message.getMessageId(), selfValues); - MessagingContentProvider.notifyMessagesChanged( - message.getConversationId()); - } - } - } - } - break; - } else { - failedCnt++; - - // Mark message as failed - BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values); - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - } - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - if (cursor != null) { - cursor.close(); - } - if (sending != null) { - sending.close(); - } - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ProcessPendingMessagesAction: " - + sendingCnt + " messages already sending, " - + pendingCnt + " messages to send, " - + failedCnt + " failed messages"); - } - - return toSendMessageId; - } - - private static String findNextMessageToDownload(final DatabaseWrapper db, final long now) { - String toDownloadMessageId = null; - db.beginTransaction(); - Cursor cursor = null; - int downloadingCnt = 0; - int pendingCnt = 0; - try { - // First check if we have any messages already downloading - downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE, - DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)", - new String[] { - Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING), - Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) - }); - - // TODO: This query is not actually needed if downloadingCnt == 0. - cursor = db.query(DatabaseHelper.MESSAGES_TABLE, - MessageData.getProjection(), - DatabaseHelper.MessageColumns.STATUS + " =? OR " - + DatabaseHelper.MessageColumns.STATUS + " =?", - new String[]{ - Integer.toString( - MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD), - Integer.toString( - MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD) - }, - null, - null, - DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC"); - - pendingCnt = cursor.getCount(); - - // If no messages are currently downloading and there is a download pending, - // queue the download of the oldest pending message. - if (downloadingCnt == 0 && cursor.moveToNext()) { - // Always start the next pending message. We will check if a download has - // expired in DownloadMmsAction and mark message failed there. - final MessageData message = new MessageData(); - message.bind(cursor); - toDownloadMessageId = message.getMessageId(); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - if (cursor != null) { - cursor.close(); - } - } - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ProcessPendingMessagesAction: " - + downloadingCnt + " messages already downloading, " - + pendingCnt + " messages to download"); - } - - return toDownloadMessageId; - } - - private ProcessPendingMessagesAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ProcessPendingMessagesAction createFromParcel(final Parcel in) { - return new ProcessPendingMessagesAction(in); - } - - @Override - public ProcessPendingMessagesAction[] newArray(final int size) { - return new ProcessPendingMessagesAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java deleted file mode 100644 index f408e47..0000000 --- a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.Activity; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.telephony.PhoneNumberUtils; -import android.telephony.SmsManager; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MmsFileProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.pdu.SendConf; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.sms.MmsSender; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.io.File; -import java.util.ArrayList; - -/** -* Update message status to reflect success or failure -* Can also update the message itself if a "final" message is now available from telephony db -*/ -public class ProcessSentMessageAction extends Action { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - // These are always set - private static final String KEY_SMS = "is_sms"; - private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform"; - - // These are set when we're processing a message sent by the user. They are null for messages - // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download). - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_MESSAGE_URI = "message_uri"; - private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri"; - private static final String KEY_SUB_ID = "sub_id"; - - // These are set for messages sent by the platform (L+) - public static final String KEY_RESULT_CODE = "result_code"; - public static final String KEY_HTTP_STATUS_CODE = "http_status_code"; - private static final String KEY_CONTENT_URI = "content_uri"; - private static final String KEY_RESPONSE = "response"; - private static final String KEY_RESPONSE_IMPORTANT = "response_important"; - - // These are set for messages we sent ourself (legacy), or which we fast-failed before sending. - private static final String KEY_STATUS = "status"; - private static final String KEY_RAW_STATUS = "raw_status"; - - // This is called when MMS lib API returns via PendingIntent - public static void processMmsSent(final int resultCode, final Uri messageUri, - final Bundle extras) { - final ProcessSentMessageAction action = new ProcessSentMessageAction(); - final Bundle params = action.actionParameters; - params.putBoolean(KEY_SMS, false); - params.putBoolean(KEY_SENT_BY_PLATFORM, true); - params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID)); - params.putParcelable(KEY_MESSAGE_URI, messageUri); - params.putParcelable(KEY_UPDATED_MESSAGE_URI, - extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI)); - params.putInt(KEY_SUB_ID, - extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID)); - params.putInt(KEY_RESULT_CODE, resultCode); - params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0)); - params.putParcelable(KEY_CONTENT_URI, - extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI)); - params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA)); - params.putBoolean(KEY_RESPONSE_IMPORTANT, - extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT)); - action.start(); - } - - public static void processMessageSentFastFailed(final String messageId, - final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms, - final int status, final int rawStatus, final int resultCode) { - final ProcessSentMessageAction action = new ProcessSentMessageAction(); - final Bundle params = action.actionParameters; - params.putBoolean(KEY_SMS, isSms); - params.putBoolean(KEY_SENT_BY_PLATFORM, false); - params.putString(KEY_MESSAGE_ID, messageId); - params.putParcelable(KEY_MESSAGE_URI, messageUri); - params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri); - params.putInt(KEY_SUB_ID, subId); - params.putInt(KEY_STATUS, status); - params.putInt(KEY_RAW_STATUS, rawStatus); - params.putInt(KEY_RESULT_CODE, resultCode); - action.start(); - } - - private ProcessSentMessageAction() { - // Callers must use one of the static methods above - } - - /** - * Update message status to reflect success or failure - * Can also update the message itself if a "final" message is now available from telephony db - */ - @Override - protected Object executeAction() { - final Context context = Factory.get().getApplicationContext(); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); - final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI); - final boolean isSms = actionParameters.getBoolean(KEY_SMS); - final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM); - - int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY); - int rawStatus = actionParameters.getInt(KEY_RAW_STATUS, - MmsUtils.PDU_HEADER_VALUE_UNDEFINED); - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - - if (sentByPlatform) { - // Delete temporary file backing the contentUri passed to MMS service - final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI); - Assert.isTrue(contentUri != null); - final File tempFile = MmsFileProvider.getFile(contentUri); - long messageSize = 0; - if (tempFile.exists()) { - messageSize = tempFile.length(); - tempFile.delete(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing " - + "MMS pdu: " + contentUri); - } - } - - final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); - final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT); - if (resultCode == Activity.RESULT_OK) { - if (responseImportant) { - // Get the status from the response PDU and update telephony - final byte[] response = actionParameters.getByteArray(KEY_RESPONSE); - final SendConf sendConf = MmsSender.parseSendConf(response, subId); - if (sendConf != null) { - final MmsUtils.StatusPlusUri result = - MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf); - status = result.status; - rawStatus = result.rawStatus; - } - } - } else { - String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: " - + resultCode; - final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); - if (httpStatusCode != 0) { - errorMsg += (", HTTP status code: " + httpStatusCode); - } - LogUtil.w(TAG, errorMsg); - status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode); - - // Check for MMS messages that failed because they exceeded the maximum size, - // indicated by an I/O error from the platform. - if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) { - if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) { - rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG; - } - } - } - } - if (messageId != null) { - final int resultCode = actionParameters.getInt(KEY_RESULT_CODE); - final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE); - processResult( - messageId, updatedMessageUri, status, rawStatus, isSms, this, subId, - resultCode, httpStatusCode); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was " - + "probably a notify response for an MMS download)"); - } - } - return null; - } - - static void processResult(final String messageId, Uri updatedMessageUri, int status, - final int rawStatus, final boolean isSms, final Action processingAction, - final int subId, final int resultCode, final int httpStatusCode) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - final MessageData originalMessage = message; - if (message == null) { - LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId - + " missing from local database"); - return; - } - final String conversationId = message.getConversationId(); - if (updatedMessageUri != null) { - // Update message if we have newly written final message in the telephony db - final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri, - conversationId, message.getParticipantId(), message.getSelfId()); - if (update != null) { - // Set message Id of final message to that of the existing place holder. - update.updateMessageId(message.getMessageId()); - // Update image sizes. - update.updateSizesForImageParts(); - // Temp attachments are no longer needed - for (final MessagePartData part : message.getParts()) { - part.destroySync(); - } - message = update; - // processResult will rewrite the complete message as part of update - } else { - updatedMessageUri = null; - status = MmsUtils.MMS_REQUEST_MANUAL_RETRY; - LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message"); - } - } - - final long timestamp = System.currentTimeMillis(); - boolean failed; - if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) { - message.markMessageSent(timestamp); - failed = false; - } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY - && message.getInResendWindow(timestamp)) { - message.markMessageNotSent(timestamp); - message.setRawTelephonyStatus(rawStatus); - failed = false; - } else { - message.markMessageFailed(timestamp); - message.setRawTelephonyStatus(rawStatus); - message.setMessageSeen(false); - failed = true; - } - - // We have special handling for when a message to an emergency number fails. In this case, - // we notify immediately of any failure (even if we auto-retry), and instruct the user to - // try calling the emergency number instead. - if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) { - final ArrayList recipients = - BugleDatabaseOperations.getRecipientsForConversation(db, conversationId); - for (final String recipient : recipients) { - if (PhoneNumberUtils.isEmergencyNumber(recipient)) { - BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId); - message.markMessageFailedEmergencyNumber(timestamp); - failed = true; - break; - } - } - } - - // Update the message status and optionally refresh the message with final parts/values. - if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) { - // We shouldn't show any notifications if we're not allowed to modify Telephony for - // this message. - if (failed) { - BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS); - } - BugleActionToasts.onSendMessageOrManualDownloadActionCompleted( - conversationId, !failed, status, isSms, subId, true/*isSend*/); - } - - LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS") - + " message " + message.getMessageId() - + " in conversation " + conversationId - + "; status is " + MmsUtils.getRequestStatusDescription(status)); - - // Whether we succeeded or failed we will check and maybe schedule some more work - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction( - status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction); - } - - private ProcessSentMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ProcessSentMessageAction createFromParcel(final Parcel in) { - return new ProcessSentMessageAction(in); - } - - @Override - public ProcessSentMessageAction[] newArray(final int size) { - return new ProcessSentMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java b/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java deleted file mode 100644 index 7ac646b..0000000 --- a/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -public class ReadDraftDataAction extends Action implements Parcelable { - - /** - * Interface for ReadDraftDataAction listeners - */ - public interface ReadDraftDataActionListener { - @RunsOnMainThread - abstract void onReadDraftDataSucceeded(final ReadDraftDataAction action, - final Object data, final MessageData message, - final ConversationListItemData conversation); - @RunsOnMainThread - abstract void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data); - } - - /** - * Read draft message and associated data (with listener) - */ - public static ReadDraftDataActionMonitor readDraftData(final String conversationId, - final MessageData incomingDraft, final Object data, - final ReadDraftDataActionListener listener) { - final ReadDraftDataActionMonitor monitor = new ReadDraftDataActionMonitor(data, - listener); - final ReadDraftDataAction action = new ReadDraftDataAction(conversationId, - incomingDraft, monitor.getActionKey()); - action.start(monitor); - return monitor; - } - - private static final String KEY_CONVERSATION_ID = "conversationId"; - private static final String KEY_INCOMING_DRAFT = "draftMessage"; - - private ReadDraftDataAction(final String conversationId, final MessageData incomingDraft, - final String actionKey) { - super(actionKey); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - actionParameters.putParcelable(KEY_INCOMING_DRAFT, incomingDraft); - } - - @VisibleForTesting - class DraftData { - public final MessageData message; - public final ConversationListItemData conversation; - - DraftData(final MessageData message, final ConversationListItemData conversation) { - this.message = message; - this.conversation = conversation; - } - } - - @Override - protected Object executeAction() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final MessageData incomingDraft = actionParameters.getParcelable(KEY_INCOMING_DRAFT); - final ConversationListItemData conversation = - ConversationListItemData.getExistingConversation(db, conversationId); - MessageData message = null; - if (conversation != null) { - if (incomingDraft == null) { - message = BugleDatabaseOperations.readDraftMessageData(db, conversationId, - conversation.getSelfId()); - } - if (message == null) { - message = MessageData.createDraftMessage(conversationId, conversation.getSelfId(), - incomingDraft); - LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: created draft. " - + "conversationId=" + conversationId - + " selfId=" + conversation.getSelfId()); - } else { - LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: read draft. " - + "conversationId=" + conversationId - + " selfId=" + conversation.getSelfId()); - } - return new DraftData(message, conversation); - } - return null; - } - - /** - * An operation that notifies a listener upon completion - */ - public static class ReadDraftDataActionMonitor extends ActionMonitor - implements ActionCompletedListener { - - private final ReadDraftDataActionListener mListener; - - ReadDraftDataActionMonitor(final Object data, - final ReadDraftDataActionListener completed) { - super(STATE_CREATED, generateUniqueActionKey("ReadDraftDataAction"), data); - setCompletedListener(this); - mListener = completed; - } - - @Override - public void onActionSucceeded(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - final DraftData draft = (DraftData) result; - if (draft == null) { - mListener.onReadDraftDataFailed((ReadDraftDataAction) action, data); - } else { - mListener.onReadDraftDataSucceeded((ReadDraftDataAction) action, data, - draft.message, draft.conversation); - } - } - - @Override - public void onActionFailed(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - Assert.fail("Reading draft should not fail"); - } - } - - private ReadDraftDataAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ReadDraftDataAction createFromParcel(final Parcel in) { - return new ReadDraftDataAction(in); - } - - @Override - public ReadDraftDataAction[] newArray(final int size) { - return new ReadDraftDataAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java deleted file mode 100644 index 6794b17..0000000 --- a/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DataModelException; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.sms.DatabaseMessages; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.LogUtil; - -import java.util.List; - -/** - * Action used to "receive" an incoming message - */ -public class ReceiveMmsMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static final String KEY_SUB_ID = "sub_id"; - private static final String KEY_PUSH_DATA = "push_data"; - private static final String KEY_TRANSACTION_ID = "transaction_id"; - private static final String KEY_CONTENT_LOCATION = "content_location"; - - /** - * Create a message received from a particular number in a particular conversation - */ - public ReceiveMmsMessageAction(final int subId, final byte[] pushData) { - actionParameters.putInt(KEY_SUB_ID, subId); - actionParameters.putByteArray(KEY_PUSH_DATA, pushData); - } - - @Override - protected Object executeAction() { - final Context context = Factory.get().getApplicationContext(); - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - final byte[] pushData = actionParameters.getByteArray(KEY_PUSH_DATA); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // Write received message to telephony DB - MessageData message = null; - final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId); - - final long received = System.currentTimeMillis(); - // Inform sync that message has been added at local received timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(received); - - // TODO: Should use local time to set received time in MMS message - final DatabaseMessages.MmsMessage mms = MmsUtils.processReceivedPdu( - context, pushData, self.getSubId(), self.getNormalizedDestination()); - - if (mms != null) { - final List recipients = MmsUtils.getRecipientsByThread(mms.mThreadId); - String from = MmsUtils.getMmsSender(recipients, mms.getUri()); - if (from == null) { - LogUtil.w(TAG, "Received an MMS without sender address; using unknown sender."); - from = ParticipantData.getUnknownSenderDestination(); - } - final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale( - from, subId); - final boolean blocked = BugleDatabaseOperations.isBlockedDestination( - db, rawSender.getNormalizedDestination()); - final boolean autoDownload = (!blocked && MmsUtils.allowMmsAutoRetrieve(subId)); - final String conversationId = - BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, mms.mThreadId, - blocked, subId); - - final boolean messageInFocusedConversation = - DataModel.get().isFocusedConversation(conversationId); - final boolean messageInObservableConversation = - DataModel.get().isNewMessageObservable(conversationId); - - // TODO: Also write these values to the telephony provider - mms.mRead = messageInFocusedConversation; - mms.mSeen = messageInObservableConversation || blocked; - - // Write received placeholder message to our DB - db.beginTransaction(); - try { - final String participantId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender); - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - - message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId, - (autoDownload ? MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD : - MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD)); - // Write the message - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - - if (!autoDownload) { - BugleDatabaseOperations.updateConversationMetadataInTransaction(db, - conversationId, message.getMessageId(), message.getReceivedTimeStamp(), - blocked, true /* shouldAutoSwitchSelfId */); - final ParticipantData sender = ParticipantData .getFromId( - db, participantId); - BugleActionToasts.onMessageReceived(conversationId, sender, message); - } - // else update the conversation once we have downloaded final message (or failed) - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - // Update conversation if not immediately initiating a download - if (!autoDownload) { - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - MessagingContentProvider.notifyPartsChanged(); - - // Show a notification to let the user know a new message has arrived - BugleNotifications.update(false/*silent*/, conversationId, - BugleNotifications.UPDATE_ALL); - - // Send the NotifyRespInd with DEFERRED status since no auto download - actionParameters.putString(KEY_TRANSACTION_ID, mms.mTransactionId); - actionParameters.putString(KEY_CONTENT_LOCATION, mms.mContentLocation); - requestBackgroundWork(); - } - - LogUtil.i(TAG, "ReceiveMmsMessageAction: Received MMS message " + message.getMessageId() - + " in conversation " + message.getConversationId() - + ", uri = " + message.getSmsMessageUri()); - } else { - LogUtil.e(TAG, "ReceiveMmsMessageAction: Skipping processing of incoming PDU"); - } - - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); - - return message; - } - - @Override - protected Bundle doBackgroundWork() throws DataModelException { - final Context context = Factory.get().getApplicationContext(); - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID); - final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION); - MmsUtils.sendNotifyResponseForMmsDownload( - context, - subId, - MmsUtils.stringToBytes(transactionId, "UTF-8"), - contentLocation, - PduHeaders.STATUS_DEFERRED); - // We don't need to return anything. - return null; - } - - private ReceiveMmsMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ReceiveMmsMessageAction createFromParcel(final Parcel in) { - return new ReceiveMmsMessageAction(in); - } - - @Override - public ReceiveMmsMessageAction[] newArray(final int size) { - return new ReceiveMmsMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java deleted file mode 100644 index 5ffb35d..0000000 --- a/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony.Sms; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -/** - * Action used to "receive" an incoming message - */ -public class ReceiveSmsMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static final String KEY_MESSAGE_VALUES = "message_values"; - - /** - * Create a message received from a particular number in a particular conversation - */ - public ReceiveSmsMessageAction(final ContentValues messageValues) { - actionParameters.putParcelable(KEY_MESSAGE_VALUES, messageValues); - } - - @Override - protected Object executeAction() { - final Context context = Factory.get().getApplicationContext(); - final ContentValues messageValues = actionParameters.getParcelable(KEY_MESSAGE_VALUES); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // Get the SIM subscription ID - Integer subId = messageValues.getAsInteger(Sms.SUBSCRIPTION_ID); - if (subId == null) { - subId = ParticipantData.DEFAULT_SELF_SUB_ID; - } - // Make sure we have a sender address - String address = messageValues.getAsString(Sms.ADDRESS); - if (TextUtils.isEmpty(address)) { - LogUtil.w(TAG, "Received an SMS without an address; using unknown sender."); - address = ParticipantData.getUnknownSenderDestination(); - messageValues.put(Sms.ADDRESS, address); - } - final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale( - address, subId); - - // TODO: Should use local timestamp for this? - final long received = messageValues.getAsLong(Sms.DATE); - // Inform sync that message has been added at local received timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(received); - - // Make sure we've got a thread id - final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(context, address); - messageValues.put(Sms.THREAD_ID, threadId); - final boolean blocked = BugleDatabaseOperations.isBlockedDestination( - db, rawSender.getNormalizedDestination()); - final String conversationId = BugleDatabaseOperations. - getOrCreateConversationFromRecipient(db, threadId, blocked, rawSender); - - final boolean messageInFocusedConversation = - DataModel.get().isFocusedConversation(conversationId); - final boolean messageInObservableConversation = - DataModel.get().isNewMessageObservable(conversationId); - - MessageData message = null; - // Only the primary user gets to insert the message into the telephony db and into bugle's - // db. The secondary user goes through this path, but skips doing the actual insert. It - // goes through this path because it needs to compute messageInFocusedConversation in order - // to calculate whether to skip the notification and play a soft sound if the user is - // already in the conversation. - if (!OsUtil.isSecondaryUser()) { - final boolean read = messageValues.getAsBoolean(Sms.Inbox.READ) - || messageInFocusedConversation; - // If you have read it you have seen it - final boolean seen = read || messageInObservableConversation || blocked; - messageValues.put(Sms.Inbox.READ, read ? Integer.valueOf(1) : Integer.valueOf(0)); - - // incoming messages are marked as seen in the telephony db - messageValues.put(Sms.Inbox.SEEN, 1); - - // Insert into telephony - final Uri messageUri = context.getContentResolver().insert(Sms.Inbox.CONTENT_URI, - messageValues); - - if (messageUri != null) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ReceiveSmsMessageAction: Inserted SMS message into telephony, " - + "uri = " + messageUri); - } - } else { - LogUtil.e(TAG, "ReceiveSmsMessageAction: Failed to insert SMS into telephony!"); - } - - final String text = messageValues.getAsString(Sms.BODY); - final String subject = messageValues.getAsString(Sms.SUBJECT); - final long sent = messageValues.getAsLong(Sms.DATE_SENT); - final ParticipantData self = ParticipantData.getSelfParticipant(subId); - final Integer pathPresent = messageValues.getAsInteger(Sms.REPLY_PATH_PRESENT); - final String smsServiceCenter = messageValues.getAsString(Sms.SERVICE_CENTER); - String conversationServiceCenter = null; - // Only set service center if message REPLY_PATH_PRESENT = 1 - if (pathPresent != null && pathPresent == 1 && !TextUtils.isEmpty(smsServiceCenter)) { - conversationServiceCenter = smsServiceCenter; - } - db.beginTransaction(); - try { - final String participantId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender); - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - - message = MessageData.createReceivedSmsMessage(messageUri, conversationId, - participantId, selfId, text, subject, sent, received, seen, read); - - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - - BugleDatabaseOperations.updateConversationMetadataInTransaction(db, conversationId, - message.getMessageId(), message.getReceivedTimeStamp(), blocked, - conversationServiceCenter, true /* shouldAutoSwitchSelfId */); - - final ParticipantData sender = ParticipantData.getFromId(db, participantId); - BugleActionToasts.onMessageReceived(conversationId, sender, message); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - LogUtil.i(TAG, "ReceiveSmsMessageAction: Received SMS message " + message.getMessageId() - + " in conversation " + message.getConversationId() - + ", uri = " + messageUri); - - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "ReceiveSmsMessageAction: Not inserting received SMS message for " - + "secondary user."); - } - } - // Show a notification to let the user know a new message has arrived - BugleNotifications.update(false/*silent*/, conversationId, BugleNotifications.UPDATE_ALL); - - MessagingContentProvider.notifyMessagesChanged(conversationId); - MessagingContentProvider.notifyPartsChanged(); - - return message; - } - - private ReceiveSmsMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ReceiveSmsMessageAction createFromParcel(final Parcel in) { - return new ReceiveSmsMessageAction(in); - } - - @Override - public ReceiveSmsMessageAction[] newArray(final int size) { - return new ReceiveSmsMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java b/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java deleted file mode 100644 index e899b0c..0000000 --- a/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.app.PendingIntent; -import android.content.ContentValues; -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.LogUtil; - -/** - * Action to manually start an MMS download (after failed or manual mms download) - */ -public class RedownloadMmsAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final int REQUEST_CODE_PENDING_INTENT = 102; - - /** - * Download an MMS message - */ - public static void redownloadMessage(final String messageId) { - final RedownloadMmsAction action = new RedownloadMmsAction(messageId); - action.start(); - } - - /** - * Get a pending intent of for downloading an MMS - */ - public static PendingIntent getPendingIntentForRedownloadMms( - final Context context, final String messageId) { - final Action action = new RedownloadMmsAction(messageId); - return ActionService.makeStartActionPendingIntent(context, - action, REQUEST_CODE_PENDING_INTENT, false /*launchesAnActivity*/); - } - - // Core parameters needed for all types of message - private static final String KEY_MESSAGE_ID = "message_id"; - - /** - * Constructor used for retrying sending in the background (only message id available) - */ - RedownloadMmsAction(final String messageId) { - super(); - actionParameters.putString(KEY_MESSAGE_ID, messageId); - } - - /** - * Read message from database and change status to allow downloading - */ - @Override - protected Object executeAction() { - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - // Check message can be redownloaded - if (message != null && message.canRedownloadMessage()) { - final long timestamp = System.currentTimeMillis(); - - final ContentValues values = new ContentValues(2); - values.put(DatabaseHelper.MessageColumns.STATUS, - MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD); - values.put(DatabaseHelper.MessageColumns.RETRY_START_TIMESTAMP, timestamp); - - // Row must exist as was just loaded above (on ActionService thread) - BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values); - - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - - // Whether we succeeded or failed we will check and maybe schedule some more work - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); - } else { - message = null; - LogUtil.e(LogUtil.BUGLE_TAG, - "Attempt to download a missing or un-redownloadable message"); - } - // Immediately update the notifications in case we came from the download action from a - // heads-up notification. This will dismiss the heads-up notification. - BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL); - return message; - } - - private RedownloadMmsAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public RedownloadMmsAction createFromParcel(final Parcel in) { - return new RedownloadMmsAction(in); - } - - @Override - public RedownloadMmsAction[] newArray(final int size) { - return new RedownloadMmsAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/ResendMessageAction.java b/src/com/android/messaging/datamodel/action/ResendMessageAction.java deleted file mode 100644 index 2201965..0000000 --- a/src/com/android/messaging/datamodel/action/ResendMessageAction.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.LogUtil; - -/** - * Action used to manually resend an outgoing message - */ -public class ResendMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Manual send of existing message (no listener) - */ - public static void resendMessage(final String messageId) { - final ResendMessageAction action = new ResendMessageAction(messageId); - action.start(); - } - - // Core parameters needed for all types of message - private static final String KEY_MESSAGE_ID = "message_id"; - - /** - * Constructor used for retrying sending in the background (only message id available) - */ - ResendMessageAction(final String messageId) { - super(); - actionParameters.putString(KEY_MESSAGE_ID, messageId); - } - - /** - * Read message from database and change status to allow sending - */ - @Override - protected Object executeAction() { - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - // Check message can be resent - if (message != null && message.canResendMessage()) { - final boolean isMms = message.getIsMms(); - long timestamp = System.currentTimeMillis(); - if (isMms) { - // MMS expects timestamp rounded to nearest second - timestamp = 1000 * ((timestamp + 500) / 1000); - } - - LogUtil.i(TAG, "ResendMessageAction: Resending message " + messageId - + "; changed timestamp from " + message.getReceivedTimeStamp() + " to " - + timestamp); - - final ContentValues values = new ContentValues(); - values.put(MessageColumns.STATUS, MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND); - values.put(MessageColumns.RECEIVED_TIMESTAMP, timestamp); - values.put(MessageColumns.SENT_TIMESTAMP, timestamp); - values.put(MessageColumns.RETRY_START_TIMESTAMP, timestamp); - - // Row must exist as was just loaded above (on ActionService thread) - BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values); - - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - - // Whether we succeeded or failed we will check and maybe schedule some more work - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); - - return message; - } else { - String error = "ResendMessageAction: Cannot resend message " + messageId + "; "; - if (message != null) { - error += ("status = " + MessageData.getStatusDescription(message.getStatus())); - } else { - error += "not found in database"; - } - LogUtil.e(TAG, error); - } - - return null; - } - - private ResendMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ResendMessageAction createFromParcel(final Parcel in) { - return new ResendMessageAction(in); - } - - @Override - public ResendMessageAction[] newArray(final int size) { - return new ResendMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/SendMessageAction.java b/src/com/android/messaging/datamodel/action/SendMessageAction.java deleted file mode 100644 index d7ebe8f..0000000 --- a/src/com/android/messaging/datamodel/action/SendMessageAction.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Sms; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; - -/** - * Action used to send an outgoing message. It writes MMS messages to the telephony db - * ({@link InsertNewMessageAction}) writes SMS messages to the telephony db). It also - * initiates the actual sending. It will all be used for re-sending a failed message. - * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure). - *

- * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to - * access the EXTRA_* fields for setting up the 'sent' pending intent. - */ -public class SendMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Queue sending of existing message (can only be called during execute of action) - */ - static boolean queueForSendInBackground(final String messageId, - final Action processingAction) { - final SendMessageAction action = new SendMessageAction(); - return action.queueAction(messageId, processingAction); - } - - public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false; - public static final int MAX_SMS_RETRY = 3; - - // Core parameters needed for all types of message - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_MESSAGE = "message"; - private static final String KEY_MESSAGE_URI = "message_uri"; - private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number"; - - // For sms messages a few extra values are included in the bundle - private static final String KEY_RECIPIENT = "recipient"; - private static final String KEY_RECIPIENTS = "recipients"; - private static final String KEY_SMS_SERVICE_CENTER = "sms_service_center"; - - // Values we attach to the pending intent that's fired when the message is sent. - // Only applicable when sending via the platform APIs on L+. - public static final String KEY_SUB_ID = "sub_id"; - public static final String EXTRA_MESSAGE_ID = "message_id"; - public static final String EXTRA_UPDATED_MESSAGE_URI = "updated_message_uri"; - public static final String EXTRA_CONTENT_URI = "content_uri"; - public static final String EXTRA_RESPONSE_IMPORTANT = "response_important"; - - /** - * Constructor used for retrying sending in the background (only message id available) - */ - private SendMessageAction() { - super(); - } - - /** - * Read message from database and queue actual sending - */ - private boolean queueAction(final String messageId, final Action processingAction) { - actionParameters.putString(KEY_MESSAGE_ID, messageId); - - final long timestamp = System.currentTimeMillis(); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final MessageData message = BugleDatabaseOperations.readMessage(db, messageId); - // Check message can be resent - if (message != null && message.canSendMessage()) { - final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS); - - final ParticipantData self = BugleDatabaseOperations.getExistingParticipant( - db, message.getSelfId()); - final Uri messageUri = message.getSmsMessageUri(); - final String conversationId = message.getConversationId(); - - // Update message status - if (message.getYetToSend()) { - // Initial sending of message - message.markMessageSending(timestamp); - } else { - // Automatic resend of message - message.markMessageResending(timestamp); - } - if (!updateMessageAndStatus(isSms, message, null /* messageUri */, false /*notify*/)) { - // If message is missing in the telephony database we don't need to send it - return false; - } - - final ArrayList recipients = - BugleDatabaseOperations.getRecipientsForConversation(db, conversationId); - - // Update action state with parameters needed for background sending - actionParameters.putParcelable(KEY_MESSAGE_URI, messageUri); - actionParameters.putParcelable(KEY_MESSAGE, message); - actionParameters.putStringArrayList(KEY_RECIPIENTS, recipients); - actionParameters.putInt(KEY_SUB_ID, self.getSubId()); - actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination()); - - if (isSms) { - final String smsc = BugleDatabaseOperations.getSmsServiceCenterForConversation( - db, conversationId); - actionParameters.putString(KEY_SMS_SERVICE_CENTER, smsc); - - if (recipients.size() == 1) { - final String recipient = recipients.get(0); - - actionParameters.putString(KEY_RECIPIENT, recipient); - // Queue actual sending for SMS - processingAction.requestBackgroundWork(this); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SendMessageAction: Queued SMS message " + messageId - + " for sending"); - } - return true; - } else { - LogUtil.wtf(TAG, "Trying to resend a broadcast SMS - not allowed"); - } - } else { - // Queue actual sending for MMS - processingAction.requestBackgroundWork(this); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SendMessageAction: Queued MMS message " + messageId - + " for sending"); - } - return true; - } - } - - return false; - } - - - /** - * Never called - */ - @Override - protected Object executeAction() { - Assert.fail("SendMessageAction must be queued rather than started"); - return null; - } - - /** - * Send message on background worker thread - */ - @Override - protected Bundle doBackgroundWork() { - final MessageData message = actionParameters.getParcelable(KEY_MESSAGE); - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI); - Uri updatedMessageUri = null; - final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS; - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER); - - LogUtil.i(TAG, "SendMessageAction: Sending " + (isSms ? "SMS" : "MMS") + " message " - + messageId + " in conversation " + message.getConversationId()); - - int status; - int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - int resultCode = MessageData.UNKNOWN_RESULT_CODE; - if (isSms) { - Assert.notNull(messageUri); - final String recipient = actionParameters.getString(KEY_RECIPIENT); - final String messageText = message.getMessageText(); - final String smsServiceCenter = actionParameters.getString(KEY_SMS_SERVICE_CENTER); - final boolean deliveryReportRequired = MmsUtils.isDeliveryReportRequired(subId); - - status = MmsUtils.sendSmsMessage(recipient, messageText, messageUri, subId, - smsServiceCenter, deliveryReportRequired); - } else { - final Context context = Factory.get().getApplicationContext(); - final ArrayList recipients = - actionParameters.getStringArrayList(KEY_RECIPIENTS); - if (messageUri == null) { - final long timestamp = message.getReceivedTimeStamp(); - - // Inform sync that message has been added at local received timestamp - final SyncManager syncManager = DataModel.get().getSyncManager(); - syncManager.onNewMessageInserted(timestamp); - - // For MMS messages first need to write to telephony (resizing images if needed) - updatedMessageUri = MmsUtils.insertSendingMmsMessage(context, recipients, - message, subId, subPhoneNumber, timestamp); - if (updatedMessageUri != null) { - messageUri = updatedMessageUri; - // To prevent Sync seeing inconsistent state must write to DB on this thread - updateMessageUri(messageId, updatedMessageUri); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SendMessageAction: Updated message " + messageId - + " with new uri " + messageUri); - } - } - } - if (messageUri != null) { - // Actually send the MMS - final Bundle extras = new Bundle(); - extras.putString(EXTRA_MESSAGE_ID, messageId); - extras.putParcelable(EXTRA_UPDATED_MESSAGE_URI, updatedMessageUri); - final MmsUtils.StatusPlusUri result = MmsUtils.sendMmsMessage(context, subId, - messageUri, extras); - if (result == MmsUtils.STATUS_PENDING) { - // Async send, so no status yet - LogUtil.d(TAG, "SendMessageAction: Sending MMS message " + messageId - + " asynchronously; waiting for callback to finish processing"); - return null; - } - status = result.status; - rawStatus = result.rawStatus; - resultCode = result.resultCode; - } else { - status = MmsUtils.MMS_REQUEST_MANUAL_RETRY; - } - } - - // When we fast-fail before calling the MMS lib APIs (e.g. airplane mode, - // sending message is deleted). - ProcessSentMessageAction.processMessageSentFastFailed(messageId, messageUri, - updatedMessageUri, subId, isSms, status, rawStatus, resultCode); - return null; - } - - private void updateMessageUri(final String messageId, final Uri updatedMessageUri) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - values.put(MessageColumns.SMS_MESSAGE_URI, updatedMessageUri.toString()); - BugleDatabaseOperations.updateMessageRow(db, messageId, values); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - @Override - protected Object processBackgroundResponse(final Bundle response) { - // Nothing to do here, post-send tasks handled by ProcessSentMessageAction - return null; - } - - /** - * Update message status to reflect success or failure - */ - @Override - protected Object processBackgroundFailure() { - final String messageId = actionParameters.getString(KEY_MESSAGE_ID); - final MessageData message = actionParameters.getParcelable(KEY_MESSAGE); - final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS; - final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID); - final int resultCode = actionParameters.getInt(ProcessSentMessageAction.KEY_RESULT_CODE); - final int httpStatusCode = - actionParameters.getInt(ProcessSentMessageAction.KEY_HTTP_STATUS_CODE); - - ProcessSentMessageAction.processResult(messageId, null /* updatedMessageUri */, - MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, - isSms, this, subId, resultCode, httpStatusCode); - - // Whether we succeeded or failed we will check and maybe schedule some more work - ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true, this); - - return null; - } - - /** - * Update the message status (and message itself if necessary) - * @param isSms whether this is an SMS or MMS - * @param message message to update - * @param updatedMessageUri message uri for newly-inserted messages; null otherwise - * @param clearSeen whether the message 'seen' status should be reset if error occurs - */ - public static boolean updateMessageAndStatus(final boolean isSms, final MessageData message, - final Uri updatedMessageUri, final boolean clearSeen) { - final Context context = Factory.get().getApplicationContext(); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - // TODO: We're optimistically setting the type/box of outgoing messages to - // 'SENT' even before they actually are. We should technically be using QUEUED or OUTBOX - // instead, but if we do that, it's possible that the Messaging app will try to send them - // as part of its clean-up logic that runs when it starts (http://b/18155366). - // - // We also use the wrong status when inserting queued SMS messages in - // InsertNewMessageAction.insertBroadcastSmsMessage and insertSendingSmsMessage (should be - // QUEUED or OUTBOX), and in MmsUtils.insertSendReq (should be OUTBOX). - - boolean updatedTelephony = true; - int messageBox; - int type; - switch(message.getStatus()) { - case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE: - case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED: - type = Sms.MESSAGE_TYPE_SENT; - messageBox = Mms.MESSAGE_BOX_SENT; - break; - case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND: - case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY: - type = Sms.MESSAGE_TYPE_SENT; - messageBox = Mms.MESSAGE_BOX_SENT; - break; - case MessageData.BUGLE_STATUS_OUTGOING_SENDING: - case MessageData.BUGLE_STATUS_OUTGOING_RESENDING: - type = Sms.MESSAGE_TYPE_SENT; - messageBox = Mms.MESSAGE_BOX_SENT; - break; - case MessageData.BUGLE_STATUS_OUTGOING_FAILED: - case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER: - type = Sms.MESSAGE_TYPE_FAILED; - messageBox = Mms.MESSAGE_BOX_FAILED; - break; - default: - type = Sms.MESSAGE_TYPE_ALL; - messageBox = Mms.MESSAGE_BOX_ALL; - break; - } - // First in the telephony DB - if (isSms) { - // Ignore update message Uri - if (type != Sms.MESSAGE_TYPE_ALL) { - if (!MmsUtils.updateSmsMessageSendingStatus(context, message.getSmsMessageUri(), - type, message.getReceivedTimeStamp())) { - message.markMessageFailed(message.getSentTimeStamp()); - updatedTelephony = false; - } - } - } else if (message.getSmsMessageUri() != null) { - if (messageBox != Mms.MESSAGE_BOX_ALL) { - if (!MmsUtils.updateMmsMessageSendingStatus(context, message.getSmsMessageUri(), - messageBox, message.getReceivedTimeStamp())) { - message.markMessageFailed(message.getSentTimeStamp()); - updatedTelephony = false; - } - } - } - if (updatedTelephony) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS") - + " message " + message.getMessageId() - + " in telephony (" + message.getSmsMessageUri() + ")"); - } - } else { - LogUtil.w(TAG, "SendMessageAction: Failed to update " + (isSms ? "SMS" : "MMS") - + " message " + message.getMessageId() - + " in telephony (" + message.getSmsMessageUri() + "); marking message failed"); - } - - // Update the local DB - db.beginTransaction(); - try { - if (updatedMessageUri != null) { - // Update all message and part fields - BugleDatabaseOperations.updateMessageInTransaction(db, message); - BugleDatabaseOperations.refreshConversationMetadataInTransaction( - db, message.getConversationId(), false/* shouldAutoSwitchSelfId */, - false/*archived*/); - } else { - final ContentValues values = new ContentValues(); - values.put(MessageColumns.STATUS, message.getStatus()); - - if (clearSeen) { - // When a message fails to send, the message needs to - // be unseen to be selected as an error notification. - values.put(MessageColumns.SEEN, 0); - } - values.put(MessageColumns.RECEIVED_TIMESTAMP, message.getReceivedTimeStamp()); - values.put(MessageColumns.RAW_TELEPHONY_STATUS, message.getRawTelephonyStatus()); - - BugleDatabaseOperations.updateMessageRowIfExists(db, message.getMessageId(), - values); - } - db.setTransactionSuccessful(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS") - + " message " + message.getMessageId() + " in local db. Timestamp = " - + message.getReceivedTimeStamp()); - } - } finally { - db.endTransaction(); - } - - MessagingContentProvider.notifyMessagesChanged(message.getConversationId()); - if (updatedMessageUri != null) { - MessagingContentProvider.notifyPartsChanged(); - } - - return updatedTelephony; - } - - private SendMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SendMessageAction createFromParcel(final Parcel in) { - return new SendMessageAction(in); - } - - @Override - public SendMessageAction[] newArray(final int size) { - return new SendMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/SyncCursorPair.java b/src/com/android/messaging/datamodel/action/SyncCursorPair.java deleted file mode 100644 index b3a2676..0000000 --- a/src/com/android/messaging/datamodel/action/SyncCursorPair.java +++ /dev/null @@ -1,712 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Sms; -import android.support.v4.util.LongSparseArray; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.SyncManager.ThreadInfoCache; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.sms.DatabaseMessages; -import com.android.messaging.sms.DatabaseMessages.DatabaseMessage; -import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage; -import com.android.messaging.sms.DatabaseMessages.MmsMessage; -import com.android.messaging.sms.DatabaseMessages.SmsMessage; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.google.common.collect.Sets; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -/** - * Class holding a pair of cursors - one for local db and one for telephony provider - allowing - * synchronous stepping through messages as part of sync. - */ -class SyncCursorPair { - private static final String TAG = LogUtil.BUGLE_TAG; - - static final long SYNC_COMPLETE = -1L; - static final long SYNC_STARTING = Long.MAX_VALUE; - - private CursorIterator mLocalCursorIterator; - private CursorIterator mRemoteCursorsIterator; - - private final String mLocalSelection; - private final String mRemoteSmsSelection; - private final String mRemoteMmsSelection; - - /** - * Check if SMS has been synchronized. We compare the counts of messages on both - * sides and return true if they are equal. - * - * Note that this may not be the most reliable way to tell if messages are in sync. - * For example, the local misses one message and has one obsolete message. - * However, we have background sms sync once a while, also some other events might - * trigger a full sync. So we will eventually catch up. And this should be rare to - * happen. - * - * @return If sms is in sync with telephony sms/mms providers - */ - static boolean allSynchronized(final DatabaseWrapper db) { - return isSynchronized(db, LOCAL_MESSAGES_SELECTION, null, - getSmsTypeSelectionSql(), null, getMmsTypeSelectionSql(), null); - } - - SyncCursorPair(final long lowerBound, final long upperBound) { - mLocalSelection = getTimeConstrainedQuery( - LOCAL_MESSAGES_SELECTION, - MessageColumns.RECEIVED_TIMESTAMP, - lowerBound, - upperBound, - null /* threadColumn */, null /* threadId */); - mRemoteSmsSelection = getTimeConstrainedQuery( - getSmsTypeSelectionSql(), - "date", - lowerBound, - upperBound, - null /* threadColumn */, null /* threadId */); - mRemoteMmsSelection = getTimeConstrainedQuery( - getMmsTypeSelectionSql(), - "date", - ((lowerBound < 0) ? lowerBound : (lowerBound + 999) / 1000), /*seconds*/ - ((upperBound < 0) ? upperBound : (upperBound + 999) / 1000), /*seconds*/ - null /* threadColumn */, null /* threadId */); - } - - SyncCursorPair(final long threadId, final String conversationId) { - mLocalSelection = getTimeConstrainedQuery( - LOCAL_MESSAGES_SELECTION, - MessageColumns.RECEIVED_TIMESTAMP, - -1L, - -1L, - MessageColumns.CONVERSATION_ID, conversationId); - // Find all SMS messages (excluding drafts) within the sync window - mRemoteSmsSelection = getTimeConstrainedQuery( - getSmsTypeSelectionSql(), - "date", - -1L, - -1L, - Sms.THREAD_ID, Long.toString(threadId)); - mRemoteMmsSelection = getTimeConstrainedQuery( - getMmsTypeSelectionSql(), - "date", - -1L, /*seconds*/ - -1L, /*seconds*/ - Mms.THREAD_ID, Long.toString(threadId)); - } - - void query(final DatabaseWrapper db) { - // Load local messages in the sync window - mLocalCursorIterator = new LocalCursorIterator(db, mLocalSelection); - // Load remote messages in the sync window - mRemoteCursorsIterator = new RemoteCursorsIterator(mRemoteSmsSelection, - mRemoteMmsSelection); - } - - boolean isSynchronized(final DatabaseWrapper db) { - return isSynchronized(db, mLocalSelection, null, mRemoteSmsSelection, - null, mRemoteMmsSelection, null); - } - - void close() { - if (mLocalCursorIterator != null) { - mLocalCursorIterator.close(); - } - if (mRemoteCursorsIterator != null) { - mRemoteCursorsIterator.close(); - } - } - - long scan(final int maxMessagesToScan, - final int maxMessagesToUpdate, final ArrayList smsToAdd, - final LongSparseArray mmsToAdd, - final ArrayList messagesToDelete, - final SyncManager.ThreadInfoCache threadInfoCache) { - // Set of local messages matched with the timestamp of a remote message - final Set matchedLocalMessages = Sets.newHashSet(); - // Set of remote messages matched with the timestamp of a local message - final Set matchedRemoteMessages = Sets.newHashSet(); - long lastTimestampMillis = SYNC_STARTING; - // Number of messages scanned local and remote - int localCount = 0; - int remoteCount = 0; - // Seed the initial values of remote and local messages for comparison - DatabaseMessage remoteMessage = mRemoteCursorsIterator.next(); - DatabaseMessage localMessage = mLocalCursorIterator.next(); - // Iterate through messages on both sides in reverse time order - // Import messages in remote not in local, delete messages in local not in remote - while (localCount + remoteCount < maxMessagesToScan && smsToAdd.size() - + mmsToAdd.size() + messagesToDelete.size() < maxMessagesToUpdate) { - if (remoteMessage == null && localMessage == null) { - // No more message on both sides - scan complete - lastTimestampMillis = SYNC_COMPLETE; - break; - } else if ((remoteMessage == null && localMessage != null) || - (localMessage != null && remoteMessage != null && - localMessage.getTimestampInMillis() - > remoteMessage.getTimestampInMillis())) { - // Found a local message that is not in remote db - // Delete the local message - messagesToDelete.add((LocalDatabaseMessage) localMessage); - lastTimestampMillis = Math.min(lastTimestampMillis, - localMessage.getTimestampInMillis()); - // Advance to next local message - localMessage = mLocalCursorIterator.next(); - localCount += 1; - } else if ((localMessage == null && remoteMessage != null) || - (localMessage != null && remoteMessage != null && - localMessage.getTimestampInMillis() - < remoteMessage.getTimestampInMillis())) { - // Found a remote message that is not in local db - // Add the remote message - saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache); - lastTimestampMillis = Math.min(lastTimestampMillis, - remoteMessage.getTimestampInMillis()); - // Advance to next remote message - remoteMessage = mRemoteCursorsIterator.next(); - remoteCount += 1; - } else { - // Found remote and local messages at the same timestamp - final long matchedTimestamp = localMessage.getTimestampInMillis(); - lastTimestampMillis = Math.min(lastTimestampMillis, matchedTimestamp); - // Get the next local and remote messages - final DatabaseMessage remoteMessagePeek = mRemoteCursorsIterator.next(); - final DatabaseMessage localMessagePeek = mLocalCursorIterator.next(); - // Check if only one message on each side matches the current timestamp - // by looking at the next messages on both sides. If they are either null - // (meaning no more messages) or having a different timestamp. We want - // to optimize for this since this is the most common case when majority - // of the messages are in sync (so they one-to-one pair up at each timestamp), - // by not allocating the data structures required to compare a set of - // messages from both sides. - if ((remoteMessagePeek == null || - remoteMessagePeek.getTimestampInMillis() != matchedTimestamp) && - (localMessagePeek == null || - localMessagePeek.getTimestampInMillis() != matchedTimestamp)) { - // Optimize the common case where only one message on each side - // that matches the same timestamp - if (!remoteMessage.equals(localMessage)) { - // local != remote - // Delete local message - messagesToDelete.add((LocalDatabaseMessage) localMessage); - // Add remote message - saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache); - } - // Get next local and remote messages - localMessage = localMessagePeek; - remoteMessage = remoteMessagePeek; - localCount += 1; - remoteCount += 1; - } else { - // Rare case in which multiple messages are in the same timestamp - // on either or both sides - // Gather all the matched remote messages - matchedRemoteMessages.clear(); - matchedRemoteMessages.add(remoteMessage); - remoteCount += 1; - remoteMessage = remoteMessagePeek; - while (remoteMessage != null && - remoteMessage.getTimestampInMillis() == matchedTimestamp) { - Assert.isTrue(!matchedRemoteMessages.contains(remoteMessage)); - matchedRemoteMessages.add(remoteMessage); - remoteCount += 1; - remoteMessage = mRemoteCursorsIterator.next(); - } - // Gather all the matched local messages - matchedLocalMessages.clear(); - matchedLocalMessages.add(localMessage); - localCount += 1; - localMessage = localMessagePeek; - while (localMessage != null && - localMessage.getTimestampInMillis() == matchedTimestamp) { - if (matchedLocalMessages.contains(localMessage)) { - // Duplicate message is local database is deleted - messagesToDelete.add((LocalDatabaseMessage) localMessage); - } else { - matchedLocalMessages.add(localMessage); - } - localCount += 1; - localMessage = mLocalCursorIterator.next(); - } - // Delete messages local only - for (final DatabaseMessage msg : Sets.difference( - matchedLocalMessages, matchedRemoteMessages)) { - messagesToDelete.add((LocalDatabaseMessage) msg); - } - // Add messages remote only - for (final DatabaseMessage msg : Sets.difference( - matchedRemoteMessages, matchedLocalMessages)) { - saveMessageToAdd(smsToAdd, mmsToAdd, msg, threadInfoCache); - } - } - } - } - return lastTimestampMillis; - } - - DatabaseMessage getLocalMessage() { - return mLocalCursorIterator.next(); - } - - DatabaseMessage getRemoteMessage() { - return mRemoteCursorsIterator.next(); - } - - int getLocalPosition() { - return mLocalCursorIterator.getPosition(); - } - - int getRemotePosition() { - return mRemoteCursorsIterator.getPosition(); - } - - int getLocalCount() { - return mLocalCursorIterator.getCount(); - } - - int getRemoteCount() { - return mRemoteCursorsIterator.getCount(); - } - - /** - * An iterator for a database cursor - */ - interface CursorIterator { - /** - * Move to next element in the cursor - * - * @return The next element (which becomes the current) - */ - public DatabaseMessage next(); - /** - * Close the cursor - */ - public void close(); - /** - * Get the position - */ - public int getPosition(); - /** - * Get the count - */ - public int getCount(); - } - - private static final String ORDER_BY_DATE_DESC = "date DESC"; - - // A subquery that selects SMS/MMS messages in Bugle which are also in telephony - private static final String LOCAL_MESSAGES_SELECTION = String.format( - Locale.US, - "(%s NOTNULL)", - MessageColumns.SMS_MESSAGE_URI); - - private static final String ORDER_BY_TIMESTAMP_DESC = - MessageColumns.RECEIVED_TIMESTAMP + " DESC"; - - // TODO : This should move into the provider - private static class LocalMessageQuery { - private static final String[] PROJECTION = new String[] { - MessageColumns._ID, - MessageColumns.RECEIVED_TIMESTAMP, - MessageColumns.SMS_MESSAGE_URI, - MessageColumns.PROTOCOL, - MessageColumns.CONVERSATION_ID, - }; - private static final int INDEX_MESSAGE_ID = 0; - private static final int INDEX_MESSAGE_TIMESTAMP = 1; - private static final int INDEX_SMS_MESSAGE_URI = 2; - private static final int INDEX_MESSAGE_SMS_TYPE = 3; - private static final int INDEX_CONVERSATION_ID = 4; - } - - /** - * This class provides the same DatabaseMessage interface over a local SMS db message - */ - private static LocalDatabaseMessage getLocalDatabaseMessage(final Cursor cursor) { - if (cursor == null) { - return null; - } - return new LocalDatabaseMessage( - cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_ID), - cursor.getInt(LocalMessageQuery.INDEX_MESSAGE_SMS_TYPE), - cursor.getString(LocalMessageQuery.INDEX_SMS_MESSAGE_URI), - cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_TIMESTAMP), - cursor.getString(LocalMessageQuery.INDEX_CONVERSATION_ID)); - } - - /** - * The buffered cursor iterator for local SMS - */ - private static class LocalCursorIterator implements CursorIterator { - private Cursor mCursor; - private final DatabaseWrapper mDatabase; - - LocalCursorIterator(final DatabaseWrapper database, final String selection) - throws SQLiteException { - mDatabase = database; - try { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncCursorPair: Querying for local messages; selection = " - + selection); - } - mCursor = mDatabase.query( - DatabaseHelper.MESSAGES_TABLE, - LocalMessageQuery.PROJECTION, - selection, - null /*selectionArgs*/, - null/*groupBy*/, - null/*having*/, - ORDER_BY_TIMESTAMP_DESC); - } catch (final SQLiteException e) { - LogUtil.e(TAG, "SyncCursorPair: failed to query local sms/mms", e); - // Can't query local database. So let's throw up the exception and abort sync - // because we may end up import duplicate messages. - throw e; - } - } - - @Override - public DatabaseMessage next() { - if (mCursor != null && mCursor.moveToNext()) { - return getLocalDatabaseMessage(mCursor); - } - return null; - } - - @Override - public int getCount() { - return (mCursor == null ? 0 : mCursor.getCount()); - } - - @Override - public int getPosition() { - return (mCursor == null ? 0 : mCursor.getPosition()); - } - - @Override - public void close() { - if (mCursor != null) { - mCursor.close(); - mCursor = null; - } - } - } - - /** - * The cursor iterator for remote sms. - * Since SMS and MMS are stored in different tables in telephony provider, - * this class merges the two cursors and provides a unified view of messages - * from both cursors. Note that the order is DESC. - */ - private static class RemoteCursorsIterator implements CursorIterator { - private Cursor mSmsCursor; - private Cursor mMmsCursor; - private DatabaseMessage mNextSms; - private DatabaseMessage mNextMms; - - RemoteCursorsIterator(final String smsSelection, final String mmsSelection) - throws SQLiteException { - mSmsCursor = null; - mMmsCursor = null; - try { - final Context context = Factory.get().getApplicationContext(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncCursorPair: Querying for remote SMS; selection = " - + smsSelection); - } - mSmsCursor = SqliteWrapper.query( - context, - context.getContentResolver(), - Sms.CONTENT_URI, - SmsMessage.getProjection(), - smsSelection, - null /* selectionArgs */, - ORDER_BY_DATE_DESC); - if (mSmsCursor == null) { - LogUtil.w(TAG, "SyncCursorPair: Remote SMS query returned null cursor; " - + "need to cancel sync"); - throw new RuntimeException("Null cursor from remote SMS query"); - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncCursorPair: Querying for remote MMS; selection = " - + mmsSelection); - } - mMmsCursor = SqliteWrapper.query( - context, - context.getContentResolver(), - Mms.CONTENT_URI, - DatabaseMessages.MmsMessage.getProjection(), - mmsSelection, - null /* selectionArgs */, - ORDER_BY_DATE_DESC); - if (mMmsCursor == null) { - LogUtil.w(TAG, "SyncCursorPair: Remote MMS query returned null cursor; " - + "need to cancel sync"); - throw new RuntimeException("Null cursor from remote MMS query"); - } - // Move to the first element in the combined stream from both cursors - mNextSms = getSmsCursorNext(); - mNextMms = getMmsCursorNext(); - } catch (final SQLiteException e) { - LogUtil.e(TAG, "SyncCursorPair: failed to query remote messages", e); - // If we ignore this, the following code would think there is no remote message - // and will delete all the local sms. We should be cautious here. So instead, - // let's throw the exception to the caller and abort sms sync. We do the same - // thing if either of the remote cursors is null. - throw e; - } - } - - @Override - public DatabaseMessage next() { - DatabaseMessage result = null; - if (mNextSms != null && mNextMms != null) { - if (mNextSms.getTimestampInMillis() >= mNextMms.getTimestampInMillis()) { - result = mNextSms; - mNextSms = getSmsCursorNext(); - } else { - result = mNextMms; - mNextMms = getMmsCursorNext(); - } - } else { - if (mNextSms != null) { - result = mNextSms; - mNextSms = getSmsCursorNext(); - } else { - result = mNextMms; - mNextMms = getMmsCursorNext(); - } - } - return result; - } - - private DatabaseMessage getSmsCursorNext() { - if (mSmsCursor != null && mSmsCursor.moveToNext()) { - return SmsMessage.get(mSmsCursor); - } - return null; - } - - private DatabaseMessage getMmsCursorNext() { - if (mMmsCursor != null && mMmsCursor.moveToNext()) { - return MmsMessage.get(mMmsCursor); - } - return null; - } - - @Override - // Return approximate cursor position allowing for read ahead on two cursors (hence -1) - public int getPosition() { - return (mSmsCursor == null ? 0 : mSmsCursor.getPosition()) + - (mMmsCursor == null ? 0 : mMmsCursor.getPosition()) - 1; - } - - @Override - public int getCount() { - return (mSmsCursor == null ? 0 : mSmsCursor.getCount()) + - (mMmsCursor == null ? 0 : mMmsCursor.getCount()); - } - - @Override - public void close() { - if (mSmsCursor != null) { - mSmsCursor.close(); - mSmsCursor = null; - } - if (mMmsCursor != null) { - mMmsCursor.close(); - mMmsCursor = null; - } - } - } - - /** - * Type selection for importing sms messages. Only SENT and INBOX messages are imported. - * - * @return The SQL selection for importing sms messages - */ - public static String getSmsTypeSelectionSql() { - return MmsUtils.getSmsTypeSelectionSql(); - } - - /** - * Type selection for importing mms messages. - * - * Criteria: - * MESSAGE_BOX is INBOX, SENT or OUTBOX - * MESSAGE_TYPE is SEND_REQ (sent), RETRIEVE_CONF (received) or NOTIFICATION_IND (download) - * - * @return The SQL selection for importing mms messages. This selects the message type, - * not including the selection on timestamp. - */ - public static String getMmsTypeSelectionSql() { - return MmsUtils.getMmsTypeSelectionSql(); - } - - /** - * Get a SQL selection string using an existing selection and time window limits - * The limits are not applied if the value is < 0 - * - * @param typeSelection The existing selection - * @param from The inclusive lower bound - * @param to The exclusive upper bound - * @return The created SQL selection - */ - private static String getTimeConstrainedQuery(final String typeSelection, - final String timeColumn, final long from, final long to, - final String threadColumn, final String threadId) { - final StringBuilder queryBuilder = new StringBuilder(); - queryBuilder.append(typeSelection); - if (from > 0) { - queryBuilder.append(" AND ").append(timeColumn).append(">=").append(from); - } - if (to > 0) { - queryBuilder.append(" AND ").append(timeColumn).append("<").append(to); - } - if (!TextUtils.isEmpty(threadColumn) && !TextUtils.isEmpty(threadId)) { - queryBuilder.append(" AND ").append(threadColumn).append("=").append(threadId); - } - return queryBuilder.toString(); - } - - private static final String[] COUNT_PROJECTION = new String[] { "count()" }; - - private static int getCountFromCursor(final Cursor cursor) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getInt(0); - } - // We should only return a number if we were able to read it from the cursor. - // Otherwise, we throw an exception to cancel the sync. - String cursorDesc = ""; - if (cursor == null) { - cursorDesc = "null"; - } else if (cursor.getCount() == 0) { - cursorDesc = "empty"; - } - throw new IllegalArgumentException("Cannot get count from " + cursorDesc + " cursor"); - } - - private void saveMessageToAdd(final List smsToAdd, - final LongSparseArray mmsToAdd, final DatabaseMessage message, - final ThreadInfoCache threadInfoCache) { - long threadId; - if (message.getProtocol() == MessageData.PROTOCOL_MMS) { - final MmsMessage mms = (MmsMessage) message; - mmsToAdd.append(mms.getId(), mms); - threadId = mms.mThreadId; - } else { - final SmsMessage sms = (SmsMessage) message; - smsToAdd.add(sms); - threadId = sms.mThreadId; - } - // Cache the lookup and canonicalization of the phone number outside of the transaction... - threadInfoCache.getThreadRecipients(threadId); - } - - /** - * Check if SMS has been synchronized. We compare the counts of messages on both - * sides and return true if they are equal. - * - * Note that this may not be the most reliable way to tell if messages are in sync. - * For example, the local misses one message and has one obsolete message. - * However, we have background sms sync once a while, also some other events might - * trigger a full sync. So we will eventually catch up. And this should be rare to - * happen. - * - * @return If sms is in sync with telephony sms/mms providers - */ - private static boolean isSynchronized(final DatabaseWrapper db, final String localSelection, - final String[] localSelectionArgs, final String smsSelection, - final String[] smsSelectionArgs, final String mmsSelection, - final String[] mmsSelectionArgs) { - final Context context = Factory.get().getApplicationContext(); - Cursor localCursor = null; - Cursor remoteSmsCursor = null; - Cursor remoteMmsCursor = null; - try { - localCursor = db.query( - DatabaseHelper.MESSAGES_TABLE, - COUNT_PROJECTION, - localSelection, - localSelectionArgs, - null/*groupBy*/, - null/*having*/, - null/*orderBy*/); - final int localCount = getCountFromCursor(localCursor); - remoteSmsCursor = SqliteWrapper.query( - context, - context.getContentResolver(), - Sms.CONTENT_URI, - COUNT_PROJECTION, - smsSelection, - smsSelectionArgs, - null/*orderBy*/); - final int smsCount = getCountFromCursor(remoteSmsCursor); - remoteMmsCursor = SqliteWrapper.query( - context, - context.getContentResolver(), - Mms.CONTENT_URI, - COUNT_PROJECTION, - mmsSelection, - mmsSelectionArgs, - null/*orderBy*/); - final int mmsCount = getCountFromCursor(remoteMmsCursor); - final int remoteCount = smsCount + mmsCount; - final boolean isInSync = (localCount == remoteCount); - if (isInSync) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncCursorPair: Same # of local and remote messages = " - + localCount); - } - } else { - LogUtil.i(TAG, "SyncCursorPair: Not in sync; # local messages = " + localCount - + ", # remote message = " + remoteCount); - } - return isInSync; - } catch (final Exception e) { - LogUtil.e(TAG, "SyncCursorPair: failed to query local or remote message counts", e); - // If something is wrong in querying database, assume we are synced so - // we don't retry indefinitely - } finally { - if (localCursor != null) { - localCursor.close(); - } - if (remoteSmsCursor != null) { - remoteSmsCursor.close(); - } - if (remoteMmsCursor != null) { - remoteMmsCursor.close(); - } - } - return true; - } -} diff --git a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java b/src/com/android/messaging/datamodel/action/SyncMessageBatch.java deleted file mode 100644 index 972d691..0000000 --- a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.database.Cursor; -import android.database.sqlite.SQLiteConstraintException; -import android.provider.Telephony; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Sms; -import android.text.TextUtils; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.SyncManager.ThreadInfoCache; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage; -import com.android.messaging.sms.DatabaseMessages.MmsMessage; -import com.android.messaging.sms.DatabaseMessages.SmsMessage; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; - -/** - * Update local database with a batch of messages to add/delete in one transaction - */ -class SyncMessageBatch { - private static final String TAG = LogUtil.BUGLE_TAG; - - // Variables used during executeAction - private final HashSet mConversationsToUpdate; - // Cache of thread->conversationId map - private final ThreadInfoCache mCache; - - // Set of SMS messages to add - private final ArrayList mSmsToAdd; - // Set of MMS messages to add - private final ArrayList mMmsToAdd; - // Set of local messages to delete - private final ArrayList mMessagesToDelete; - - SyncMessageBatch(final ArrayList smsToAdd, - final ArrayList mmsToAdd, - final ArrayList messagesToDelete, - final ThreadInfoCache cache) { - mSmsToAdd = smsToAdd; - mMmsToAdd = mmsToAdd; - mMessagesToDelete = messagesToDelete; - mCache = cache; - mConversationsToUpdate = new HashSet(); - } - - void updateLocalDatabase() { - // Perform local database changes in one transaction - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - // Store all the SMS messages - for (final SmsMessage sms : mSmsToAdd) { - storeSms(db, sms); - } - // Store all the MMS messages - for (final MmsMessage mms : mMmsToAdd) { - storeMms(db, mms); - } - // Keep track of conversations with messages deleted - for (final LocalDatabaseMessage message : mMessagesToDelete) { - mConversationsToUpdate.add(message.getConversationId()); - } - // Batch delete local messages - batchDelete(db, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID, - messageListToIds(mMessagesToDelete)); - - for (final LocalDatabaseMessage message : mMessagesToDelete) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncMessageBatch: Deleted message " + message.getLocalId() - + " for SMS/MMS " + message.getUri() + " with timestamp " - + message.getTimestampInMillis()); - } - } - - // Update conversation state for imported messages, like snippet, - updateConversations(db); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - private static String[] messageListToIds(final List messagesToDelete) { - final String[] ids = new String[messagesToDelete.size()]; - for (int i = 0; i < ids.length; i++) { - ids[i] = Long.toString(messagesToDelete.get(i).getLocalId()); - } - return ids; - } - - /** - * Store the SMS message into local database. - * - * @param sms - */ - private void storeSms(final DatabaseWrapper db, final SmsMessage sms) { - if (sms.mBody == null) { - LogUtil.w(TAG, "SyncMessageBatch: SMS " + sms.mUri + " has no body; adding empty one"); - // try to fix it - sms.mBody = ""; - } - - if (TextUtils.isEmpty(sms.mAddress)) { - LogUtil.e(TAG, "SyncMessageBatch: SMS has no address; using unknown sender"); - // try to fix it - sms.mAddress = ParticipantData.getUnknownSenderDestination(); - } - - // TODO : We need to also deal with messages in a failed/retry state - final boolean isOutgoing = sms.mType != Sms.MESSAGE_TYPE_INBOX; - - final String otherPhoneNumber = sms.mAddress; - - // A forced resync of all messages should still keep the archived states. - // The database upgrade code notifies sync manager of this. We need to - // honor the original customization to this conversation if created. - final String conversationId = mCache.getOrCreateConversation(db, sms.mThreadId, sms.mSubId, - DataModel.get().getSyncManager().getCustomizationForThread(sms.mThreadId)); - if (conversationId == null) { - // Cannot create conversation for this message? This should not happen. - LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for SMS thread " - + sms.mThreadId); - return; - } - final ParticipantData self = ParticipantData.getSelfParticipant(sms.getSubId()); - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - final ParticipantData sender = isOutgoing ? - self : - ParticipantData.getFromRawPhoneBySimLocale(otherPhoneNumber, sms.getSubId()); - final String participantId = (isOutgoing ? selfId : - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender)); - - final int bugleStatus = bugleStatusForSms(isOutgoing, sms.mType, sms.mStatus); - - final MessageData message = MessageData.createSmsMessage( - sms.mUri, - participantId, - selfId, - conversationId, - bugleStatus, - sms.mSeen, - sms.mRead, - sms.mTimestampSentInMillis, - sms.mTimestampInMillis, - sms.mBody); - - // Inserting sms content into messages table - try { - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - } catch (SQLiteConstraintException e) { - rethrowSQLiteConstraintExceptionWithDetails(e, db, sms.mUri, sms.mThreadId, - conversationId, selfId, participantId); - } - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId() - + " for SMS " + message.getSmsMessageUri() + " received at " - + message.getReceivedTimeStamp()); - } - - // Keep track of updated conversation for later updating the conversation snippet, etc. - mConversationsToUpdate.add(conversationId); - } - - public static int bugleStatusForSms(final boolean isOutgoing, final int type, - final int status) { - int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN; - // For a message we sync either - if (isOutgoing) { - // Outgoing message not yet been sent - if (type == Telephony.Sms.MESSAGE_TYPE_FAILED || - type == Telephony.Sms.MESSAGE_TYPE_OUTBOX || - type == Telephony.Sms.MESSAGE_TYPE_QUEUED || - (type == Telephony.Sms.MESSAGE_TYPE_SENT && - status == Telephony.Sms.STATUS_FAILED)) { - // Not sent counts as failed and available for manual resend - bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED; - } else if (status == Sms.STATUS_COMPLETE) { - bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_DELIVERED; - } else { - // Otherwise outgoing message is complete - bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE; - } - } else { - // All incoming SMS messages are complete - bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE; - } - return bugleStatus; - } - - /** - * Store the MMS message into local database - * - * @param mms - */ - private void storeMms(final DatabaseWrapper db, final MmsMessage mms) { - if (mms.mParts.size() < 1) { - LogUtil.w(TAG, "SyncMessageBatch: MMS " + mms.mUri + " has no parts"); - } - - // TODO : We need to also deal with messages in a failed/retry state - final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX; - final boolean isNotification = (mms.mMmsMessageType == - PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); - - final String senderId = mms.mSender; - - // A forced resync of all messages should still keep the archived states. - // The database upgrade code notifies sync manager of this. We need to - // honor the original customization to this conversation if created. - final String conversationId = mCache.getOrCreateConversation(db, mms.mThreadId, mms.mSubId, - DataModel.get().getSyncManager().getCustomizationForThread(mms.mThreadId)); - if (conversationId == null) { - LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for MMS thread " - + mms.mThreadId); - return; - } - final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId()); - final String selfId = - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self); - final ParticipantData sender = isOutgoing ? - self : ParticipantData.getFromRawPhoneBySimLocale(senderId, mms.getSubId()); - final String participantId = (isOutgoing ? selfId : - BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender)); - - final int bugleStatus = MmsUtils.bugleStatusForMms(isOutgoing, isNotification, mms.mType); - - // Import message and all of the parts. - // TODO : For now we are importing these in the order we found them in the MMS - // database. Ideally we would load and parse the SMIL which describes how the parts relate - // to one another. - - // TODO: Need to set correct status on message - final MessageData message = MmsUtils.createMmsMessage(mms, conversationId, participantId, - selfId, bugleStatus); - - // Inserting mms content into messages table - try { - BugleDatabaseOperations.insertNewMessageInTransaction(db, message); - } catch (SQLiteConstraintException e) { - rethrowSQLiteConstraintExceptionWithDetails(e, db, mms.mUri, mms.mThreadId, - conversationId, selfId, participantId); - } - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId() - + " for MMS " + message.getSmsMessageUri() + " received at " - + message.getReceivedTimeStamp()); - } - - // Keep track of updated conversation for later updating the conversation snippet, etc. - mConversationsToUpdate.add(conversationId); - } - - // TODO: Remove this after we no longer see this crash (b/18375758) - private static void rethrowSQLiteConstraintExceptionWithDetails(SQLiteConstraintException e, - DatabaseWrapper db, String messageUri, long threadId, String conversationId, - String selfId, String senderId) { - // Add some extra debug information to the exception for tracking down b/18375758. - // The default detail message for SQLiteConstraintException tells us that a foreign - // key constraint failed, but not which one! Messages have foreign keys to 3 tables: - // conversations, participants (self), participants (sender). We'll query each one - // to determine which one(s) violated the constraint, and then throw a new exception - // with those details. - - String foundConversationId = null; - Cursor cursor = null; - try { - // Look for an existing conversation in the db with the conversation id - cursor = db.rawQuery("SELECT " + ConversationColumns._ID - + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE - + " WHERE " + ConversationColumns._ID + "=" + conversationId, - null); - if (cursor != null && cursor.moveToFirst()) { - Assert.isTrue(cursor.getCount() == 1); - foundConversationId = cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - ParticipantData foundSelfParticipant = - BugleDatabaseOperations.getExistingParticipant(db, selfId); - ParticipantData foundSenderParticipant = - BugleDatabaseOperations.getExistingParticipant(db, senderId); - - String errorMsg = "SQLiteConstraintException while inserting message for " + messageUri - + "; conversation id from getOrCreateConversation = " + conversationId - + " (lookup thread = " + threadId + "), found conversation id = " - + foundConversationId + ", found self participant = " - + LogUtil.sanitizePII(foundSelfParticipant.getNormalizedDestination()) - + " (lookup id = " + selfId + "), found sender participant = " - + LogUtil.sanitizePII(foundSenderParticipant.getNormalizedDestination()) - + " (lookup id = " + senderId + ")"; - throw new RuntimeException(errorMsg, e); - } - - /** - * Use the tracked latest message info to update conversations, including - * latest chat message and sort timestamp. - */ - private void updateConversations(final DatabaseWrapper db) { - for (final String conversationId : mConversationsToUpdate) { - if (BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(db, - conversationId)) { - continue; - } - - final boolean archived = mCache.isArchived(conversationId); - // Always attempt to auto-switch conversation self id for sync/import case. - BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(db, - conversationId, true /*shouldAutoSwitchSelfId*/, archived /*keepArchived*/); - } - } - - - /** - * Batch delete database rows by matching a column with a list of values, usually some - * kind of IDs. - * - * @param table - * @param column - * @param ids - * @return Total number of deleted messages - */ - private static int batchDelete(final DatabaseWrapper db, final String table, - final String column, final String[] ids) { - int totalDeleted = 0; - final int totalIds = ids.length; - for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) { - final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding - final int count = end - start; - final String batchSelection = String.format( - Locale.US, - "%s IN %s", - column, - MmsUtils.getSqlInOperand(count)); - final String[] batchSelectionArgs = Arrays.copyOfRange(ids, start, end); - final int deleted = db.delete( - table, - batchSelection, - batchSelectionArgs); - totalDeleted += deleted; - } - return totalDeleted; - } -} diff --git a/src/com/android/messaging/datamodel/action/SyncMessagesAction.java b/src/com/android/messaging/datamodel/action/SyncMessagesAction.java deleted file mode 100644 index f4a3e1f..0000000 --- a/src/com/android/messaging/datamodel/action/SyncMessagesAction.java +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.provider.Telephony.Mms; -import android.support.v4.util.LongSparseArray; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.SyncManager.ThreadInfoCache; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.sms.DatabaseMessages; -import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage; -import com.android.messaging.sms.DatabaseMessages.MmsMessage; -import com.android.messaging.sms.DatabaseMessages.SmsMessage; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * Action used to sync messages from smsmms db to local database - */ -public class SyncMessagesAction extends Action implements Parcelable { - static final long SYNC_FAILED = Long.MIN_VALUE; - - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - private static final String KEY_START_TIMESTAMP = "start_timestamp"; - private static final String KEY_MAX_UPDATE = "max_update"; - private static final String KEY_LOWER_BOUND = "lower_bound"; - private static final String KEY_UPPER_BOUND = "upper_bound"; - private static final String BUNDLE_KEY_LAST_TIMESTAMP = "last_timestamp"; - private static final String BUNDLE_KEY_SMS_MESSAGES = "sms_to_add"; - private static final String BUNDLE_KEY_MMS_MESSAGES = "mms_to_add"; - private static final String BUNDLE_KEY_MESSAGES_TO_DELETE = "messages_to_delete"; - - /** - * Start a full sync (backed off a few seconds to avoid pulling sending/receiving messages). - */ - public static void fullSync() { - final BugleGservices bugleGservices = BugleGservices.get(); - final long smsSyncBackoffTimeMillis = bugleGservices.getLong( - BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS, - BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT); - - final long now = System.currentTimeMillis(); - // TODO: Could base this off most recent message in db but now should be okay... - final long startTimestamp = now - smsSyncBackoffTimeMillis; - - final SyncMessagesAction action = new SyncMessagesAction(-1L, startTimestamp, - 0, startTimestamp); - action.start(); - } - - /** - * Start an incremental sync to pull messages since last sync (backed off a few seconds).. - */ - public static void sync() { - final BugleGservices bugleGservices = BugleGservices.get(); - final long smsSyncBackoffTimeMillis = bugleGservices.getLong( - BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS, - BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT); - - final long now = System.currentTimeMillis(); - // TODO: Could base this off most recent message in db but now should be okay... - final long startTimestamp = now - smsSyncBackoffTimeMillis; - - sync(startTimestamp); - } - - /** - * Start an incremental sync when the application starts up (no back off as not yet - * sending/receiving). - */ - public static void immediateSync() { - final long now = System.currentTimeMillis(); - // TODO: Could base this off most recent message in db but now should be okay... - final long startTimestamp = now; - - sync(startTimestamp); - } - - private static void sync(final long startTimestamp) { - if (!OsUtil.hasSmsPermission()) { - // Sync requires READ_SMS permission - return; - } - - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - // Lower bound is end of previous sync - final long syncLowerBoundTimeMillis = prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME, - BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT); - - final SyncMessagesAction action = new SyncMessagesAction(syncLowerBoundTimeMillis, - startTimestamp, 0, startTimestamp); - action.start(); - } - - private SyncMessagesAction(final long lowerBound, final long upperBound, - final int maxMessagesToUpdate, final long startTimestamp) { - actionParameters.putLong(KEY_LOWER_BOUND, lowerBound); - actionParameters.putLong(KEY_UPPER_BOUND, upperBound); - actionParameters.putInt(KEY_MAX_UPDATE, maxMessagesToUpdate); - actionParameters.putLong(KEY_START_TIMESTAMP, startTimestamp); - } - - @Override - protected Object executeAction() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND); - final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND); - final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE); - final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Request to sync messages from " - + lowerBoundTimeMillis + " to " + upperBoundTimeMillis + " (start timestamp = " - + startTimestamp + ", message update limit = " + initialMaxMessagesToUpdate - + ")"); - } - - final SyncManager syncManager = DataModel.get().getSyncManager(); - if (lowerBoundTimeMillis >= 0) { - // Cursors - final SyncCursorPair cursors = new SyncCursorPair(-1L, lowerBoundTimeMillis); - final boolean inSync = cursors.isSynchronized(db); - if (!inSync) { - if (syncManager.delayUntilFullSync(startTimestamp) == 0) { - lowerBoundTimeMillis = -1; - actionParameters.putLong(KEY_LOWER_BOUND, lowerBoundTimeMillis); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Messages before " - + lowerBoundTimeMillis + " not in sync; promoting to full sync"); - } - } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Messages before " - + lowerBoundTimeMillis + " not in sync; will do incremental sync"); - } - } else { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Messages before " + lowerBoundTimeMillis - + " are in sync"); - } - } - } - - // Check if sync allowed (can be too soon after last or one is already running) - if (syncManager.shouldSync(lowerBoundTimeMillis < 0, startTimestamp)) { - syncManager.startSyncBatch(upperBoundTimeMillis); - requestBackgroundWork(); - } - - return null; - } - - @Override - protected Bundle doBackgroundWork() { - final BugleGservices bugleGservices = BugleGservices.get(); - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final int maxMessagesToScan = bugleGservices.getInt( - BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN, - BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT); - - final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE); - final int smsSyncSubsequentBatchSizeMin = bugleGservices.getInt( - BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN, - BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN_DEFAULT); - final int smsSyncSubsequentBatchSizeMax = bugleGservices.getInt( - BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX, - BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX_DEFAULT); - - // Cap sync size to GServices limits - final int maxMessagesToUpdate = Math.max(smsSyncSubsequentBatchSizeMin, - Math.min(initialMaxMessagesToUpdate, smsSyncSubsequentBatchSizeMax)); - - final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND); - final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND); - - LogUtil.i(TAG, "SyncMessagesAction: Starting batch for messages from " - + lowerBoundTimeMillis + " to " + upperBoundTimeMillis - + " (message update limit = " + maxMessagesToUpdate + ", message scan limit = " - + maxMessagesToScan + ")"); - - // Clear last change time so that we can work out if this batch is dirty when it completes - final SyncManager syncManager = DataModel.get().getSyncManager(); - - // Clear the singleton cache that maps threads to recipients and to conversations. - final SyncManager.ThreadInfoCache cache = syncManager.getThreadInfoCache(); - cache.clear(); - - // Sms messages to store - final ArrayList smsToAdd = new ArrayList(); - // Mms messages to store - final LongSparseArray mmsToAdd = new LongSparseArray(); - // List of local SMS/MMS to remove - final ArrayList messagesToDelete = - new ArrayList(); - - long lastTimestampMillis = SYNC_FAILED; - if (syncManager.isSyncing(upperBoundTimeMillis)) { - // Cursors - final SyncCursorPair cursors = new SyncCursorPair(lowerBoundTimeMillis, - upperBoundTimeMillis); - - // Actually compare the messages using cursor pair - lastTimestampMillis = syncCursorPair(db, cursors, smsToAdd, mmsToAdd, - messagesToDelete, maxMessagesToScan, maxMessagesToUpdate, cache); - } - final Bundle response = new Bundle(); - - // If comparison succeeds bundle up the changes for processing in ActionService - if (lastTimestampMillis > SYNC_FAILED) { - final ArrayList mmsToAddList = new ArrayList(); - for (int i = 0; i < mmsToAdd.size(); i++) { - final MmsMessage mms = mmsToAdd.valueAt(i); - mmsToAddList.add(mms); - } - - response.putParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES, smsToAdd); - response.putParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES, mmsToAddList); - response.putParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE, messagesToDelete); - } - response.putLong(BUNDLE_KEY_LAST_TIMESTAMP, lastTimestampMillis); - - return response; - } - - /** - * Compare messages based on timestamp and uri - * @param db local database wrapper - * @param cursors cursor pair holding references to local and remote messages - * @param smsToAdd newly found sms messages to add - * @param mmsToAdd newly found mms messages to add - * @param messagesToDelete messages not found needing deletion - * @param maxMessagesToScan max messages to scan for changes - * @param maxMessagesToUpdate max messages to return for updates - * @param cache cache for conversation id / thread id / recipient set mapping - * @return timestamp of the oldest message seen during the sync scan - */ - private long syncCursorPair(final DatabaseWrapper db, final SyncCursorPair cursors, - final ArrayList smsToAdd, final LongSparseArray mmsToAdd, - final ArrayList messagesToDelete, final int maxMessagesToScan, - final int maxMessagesToUpdate, final ThreadInfoCache cache) { - long lastTimestampMillis; - final long startTimeMillis = SystemClock.elapsedRealtime(); - - // Number of messages scanned local and remote - int localPos = 0; - int remotePos = 0; - int localTotal = 0; - int remoteTotal = 0; - // Scan through the messages on both sides and prepare messages for local message table - // changes (including adding and deleting) - try { - cursors.query(db); - - localTotal = cursors.getLocalCount(); - remoteTotal = cursors.getRemoteCount(); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Scanning cursors (local count = " + localTotal - + ", remote count = " + remoteTotal + ", message update limit = " - + maxMessagesToUpdate + ", message scan limit = " + maxMessagesToScan - + ")"); - } - - lastTimestampMillis = cursors.scan(maxMessagesToScan, maxMessagesToUpdate, - smsToAdd, mmsToAdd, messagesToDelete, cache); - - localPos = cursors.getLocalPosition(); - remotePos = cursors.getRemotePosition(); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Scanned cursors (local position = " + localPos - + " of " + localTotal + ", remote position = " + remotePos + " of " - + remoteTotal + ")"); - } - - // Batch loading the parts of the MMS messages in this batch - loadMmsParts(mmsToAdd); - // Lookup senders for incoming mms messages - setMmsSenders(mmsToAdd, cache); - } catch (final SQLiteException e) { - LogUtil.e(TAG, "SyncMessagesAction: Database exception", e); - // Let's abort - lastTimestampMillis = SYNC_FAILED; - } catch (final Exception e) { - // We want to catch anything unexpected since this is running in a separate thread - // and any unexpected exception will just fail this thread silently. - // Let's crash for dogfooders! - LogUtil.wtf(TAG, "SyncMessagesAction: unexpected failure in scan", e); - lastTimestampMillis = SYNC_FAILED; - } finally { - if (cursors != null) { - cursors.close(); - } - } - - final long endTimeMillis = SystemClock.elapsedRealtime(); - - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: Scan complete (took " - + (endTimeMillis - startTimeMillis) + " ms). " + smsToAdd.size() - + " remote SMS to add, " + mmsToAdd.size() + " MMS to add, " - + messagesToDelete.size() + " local messages to delete. " - + "Oldest timestamp seen = " + lastTimestampMillis); - } - - return lastTimestampMillis; - } - - /** - * Perform local database updates and schedule follow on sync actions - */ - @Override - protected Object processBackgroundResponse(final Bundle response) { - final long lastTimestampMillis = response.getLong(BUNDLE_KEY_LAST_TIMESTAMP); - final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND); - final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND); - final int maxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE); - final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP); - - // Check with the sync manager if any conflicting updates have been made to databases - final SyncManager syncManager = DataModel.get().getSyncManager(); - final boolean orphan = !syncManager.isSyncing(upperBoundTimeMillis); - - // lastTimestampMillis used to indicate failure - if (orphan) { - // This batch does not match current in progress timestamp. - LogUtil.w(TAG, "SyncMessagesAction: Ignoring orphan sync batch for messages from " - + lowerBoundTimeMillis + " to " + upperBoundTimeMillis); - } else { - final boolean dirty = syncManager.isBatchDirty(lastTimestampMillis); - if (lastTimestampMillis == SYNC_FAILED) { - LogUtil.e(TAG, "SyncMessagesAction: Sync failed - terminating"); - - // Failed - update last sync times to throttle our failure rate - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - // Save sync completion time so next sync will start from here - prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp); - // Remember last full sync so that don't start background full sync right away - prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp); - - syncManager.complete(); - } else if (dirty) { - LogUtil.w(TAG, "SyncMessagesAction: Redoing dirty sync batch of messages from " - + lowerBoundTimeMillis + " to " + upperBoundTimeMillis); - - // Redo this batch - final SyncMessagesAction nextBatch = - new SyncMessagesAction(lowerBoundTimeMillis, upperBoundTimeMillis, - maxMessagesToUpdate, startTimestamp); - - syncManager.startSyncBatch(upperBoundTimeMillis); - requestBackgroundWork(nextBatch); - } else { - // Succeeded - final ArrayList smsToAdd = - response.getParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES); - final ArrayList mmsToAdd = - response.getParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES); - final ArrayList messagesToDelete = - response.getParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE); - - final int messagesUpdated = smsToAdd.size() + mmsToAdd.size() - + messagesToDelete.size(); - - // Perform local database changes in one transaction - long txnTimeMillis = 0; - if (messagesUpdated > 0) { - final long startTimeMillis = SystemClock.elapsedRealtime(); - final SyncMessageBatch batch = new SyncMessageBatch(smsToAdd, mmsToAdd, - messagesToDelete, syncManager.getThreadInfoCache()); - batch.updateLocalDatabase(); - final long endTimeMillis = SystemClock.elapsedRealtime(); - txnTimeMillis = endTimeMillis - startTimeMillis; - - LogUtil.i(TAG, "SyncMessagesAction: Updated local database " - + "(took " + txnTimeMillis + " ms). Added " - + smsToAdd.size() + " SMS, added " + mmsToAdd.size() + " MMS, deleted " - + messagesToDelete.size() + " messages."); - - // TODO: Investigate whether we can make this more fine-grained. - MessagingContentProvider.notifyEverythingChanged(); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: No local database updates to make"); - } - - if (!syncManager.getHasFirstSyncCompleted()) { - // If we have never completed a sync before (fresh install) and there are - // no messages, still inform the UI of a change so it can update syncing - // messages shown to the user - MessagingContentProvider.notifyConversationListChanged(); - MessagingContentProvider.notifyPartsChanged(); - } - } - // Determine if there are more messages that need to be scanned - if (lastTimestampMillis >= 0 && lastTimestampMillis >= lowerBoundTimeMillis) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SyncMessagesAction: More messages to sync; scheduling next " - + "sync batch now."); - } - - // Include final millisecond of last sync in next sync - final long newUpperBoundTimeMillis = lastTimestampMillis + 1; - final int newMaxMessagesToUpdate = nextBatchSize(messagesUpdated, - txnTimeMillis); - - final SyncMessagesAction nextBatch = - new SyncMessagesAction(lowerBoundTimeMillis, newUpperBoundTimeMillis, - newMaxMessagesToUpdate, startTimestamp); - - // Proceed with next batch - syncManager.startSyncBatch(newUpperBoundTimeMillis); - requestBackgroundWork(nextBatch); - } else { - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - // Save sync completion time so next sync will start from here - prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp); - if (lowerBoundTimeMillis < 0) { - // Remember last full sync so that don't start another full sync right away - prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp); - } - - final long now = System.currentTimeMillis(); - - // After any sync check if new messages have arrived - final SyncCursorPair recents = new SyncCursorPair(startTimestamp, now); - final SyncCursorPair olders = new SyncCursorPair(-1L, startTimestamp); - final DatabaseWrapper db = DataModel.get().getDatabase(); - if (!recents.isSynchronized(db)) { - LogUtil.i(TAG, "SyncMessagesAction: Changed messages after sync; " - + "scheduling an incremental sync now."); - - // Just add a new batch for recent messages - final SyncMessagesAction nextBatch = - new SyncMessagesAction(startTimestamp, now, 0, startTimestamp); - syncManager.startSyncBatch(now); - requestBackgroundWork(nextBatch); - // After partial sync verify sync state - } else if (lowerBoundTimeMillis >= 0 && !olders.isSynchronized(db)) { - // Add a batch going back to start of time - LogUtil.w(TAG, "SyncMessagesAction: Changed messages before sync batch; " - + "scheduling a full sync now."); - - final SyncMessagesAction nextBatch = - new SyncMessagesAction(-1L, startTimestamp, 0, startTimestamp); - - syncManager.startSyncBatch(startTimestamp); - requestBackgroundWork(nextBatch); - } else { - LogUtil.i(TAG, "SyncMessagesAction: All messages now in sync"); - - // All done, in sync - syncManager.complete(); - } - } - // Either sync should be complete or we should have a follow up request - Assert.isTrue(hasBackgroundActions() || !syncManager.isSyncing()); - } - } - - return null; - } - - /** - * Decide the next batch size based on the stats we collected with past batch - * @param messagesUpdated number of messages updated in this batch - * @param txnTimeMillis time the transaction took in ms - * @return Target number of messages to sync for next batch - */ - private static int nextBatchSize(final int messagesUpdated, final long txnTimeMillis) { - final BugleGservices bugleGservices = BugleGservices.get(); - final long smsSyncSubsequentBatchTimeLimitMillis = bugleGservices.getLong( - BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS, - BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT); - - if (txnTimeMillis <= 0) { - return 0; - } - // Number of messages we can sync within the batch time limit using - // the average sync time calculated based on the stats we collected - // in previous batch - return (int) ((double) (messagesUpdated) / (double) txnTimeMillis - * smsSyncSubsequentBatchTimeLimitMillis); - } - - /** - * Batch loading MMS parts for the messages in current batch - */ - private void loadMmsParts(final LongSparseArray mmses) { - final Context context = Factory.get().getApplicationContext(); - final int totalIds = mmses.size(); - for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) { - final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding - final int count = end - start; - final String batchSelection = String.format( - Locale.US, - "%s != '%s' AND %s IN %s", - Mms.Part.CONTENT_TYPE, - ContentType.APP_SMIL, - Mms.Part.MSG_ID, - MmsUtils.getSqlInOperand(count)); - final String[] batchSelectionArgs = new String[count]; - for (int i = 0; i < count; i++) { - batchSelectionArgs[i] = Long.toString(mmses.valueAt(start + i).getId()); - } - final Cursor cursor = SqliteWrapper.query( - context, - context.getContentResolver(), - MmsUtils.MMS_PART_CONTENT_URI, - DatabaseMessages.MmsPart.PROJECTION, - batchSelection, - batchSelectionArgs, - null/*sortOrder*/); - if (cursor != null) { - try { - while (cursor.moveToNext()) { - // Delay loading the media content for parsing for efficiency - // TODO: load the media and fill in the dimensions when - // we actually display it - final DatabaseMessages.MmsPart part = - DatabaseMessages.MmsPart.get(cursor, false/*loadMedia*/); - final DatabaseMessages.MmsMessage mms = mmses.get(part.mMessageId); - if (mms != null) { - mms.addPart(part); - } - } - } finally { - cursor.close(); - } - } - } - } - - /** - * Batch loading MMS sender for the messages in current batch - */ - private void setMmsSenders(final LongSparseArray mmses, - final ThreadInfoCache cache) { - // Store all the MMS messages - for (int i = 0; i < mmses.size(); i++) { - final MmsMessage mms = mmses.valueAt(i); - - final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX; - String senderId = null; - if (!isOutgoing) { - // We only need to find out sender phone number for received message - senderId = getMmsSender(mms, cache); - if (senderId == null) { - LogUtil.w(TAG, "SyncMessagesAction: Could not find sender of incoming MMS " - + "message " + mms.getUri() + "; using 'unknown sender' instead"); - senderId = ParticipantData.getUnknownSenderDestination(); - } - } - mms.setSender(senderId); - } - } - - /** - * Find out the sender of an MMS message - */ - private String getMmsSender(final MmsMessage mms, final ThreadInfoCache cache) { - final List recipients = cache.getThreadRecipients(mms.mThreadId); - Assert.notNull(recipients); - Assert.isTrue(recipients.size() > 0); - - if (recipients.size() == 1 - && recipients.get(0).equals(ParticipantData.getUnknownSenderDestination())) { - LogUtil.w(TAG, "SyncMessagesAction: MMS message " + mms.mUri + " has unknown sender " - + "(thread id = " + mms.mThreadId + ")"); - } - - return MmsUtils.getMmsSender(recipients, mms.mUri); - } - - private SyncMessagesAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SyncMessagesAction createFromParcel(final Parcel in) { - return new SyncMessagesAction(in); - } - - @Override - public SyncMessagesAction[] newArray(final int size) { - return new SyncMessagesAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java deleted file mode 100644 index 066ad74..0000000 --- a/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.util.Assert; - -public class UpdateConversationArchiveStatusAction extends Action { - - public static void archiveConversation(final String conversationId) { - final UpdateConversationArchiveStatusAction action = - new UpdateConversationArchiveStatusAction(conversationId, true /* isArchive */); - action.start(); - } - - public static void unarchiveConversation(final String conversationId) { - final UpdateConversationArchiveStatusAction action = - new UpdateConversationArchiveStatusAction(conversationId, false /* isArchive */); - action.start(); - } - - private static final String KEY_CONVERSATION_ID = "conversation_id"; - private static final String KEY_IS_ARCHIVE = "is_archive"; - - protected UpdateConversationArchiveStatusAction( - final String conversationId, final boolean isArchive) { - Assert.isTrue(!TextUtils.isEmpty(conversationId)); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - actionParameters.putBoolean(KEY_IS_ARCHIVE, isArchive); - } - - @Override - protected Object executeAction() { - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final boolean isArchived = actionParameters.getBoolean(KEY_IS_ARCHIVE); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - BugleDatabaseOperations.updateConversationArchiveStatusInTransaction( - db, conversationId, isArchived); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - MessagingContentProvider.notifyConversationListChanged(); - MessagingContentProvider.notifyConversationMetadataChanged(conversationId); - return null; - } - - protected UpdateConversationArchiveStatusAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public UpdateConversationArchiveStatusAction createFromParcel(final Parcel in) { - return new UpdateConversationArchiveStatusAction(in); - } - - @Override - public UpdateConversationArchiveStatusAction[] newArray(final int size) { - return new UpdateConversationArchiveStatusAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java deleted file mode 100644 index 6c9e739..0000000 --- a/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.util.Assert; - -/** - * Action used to update conversation options such as notification settings. - */ -public class UpdateConversationOptionsAction extends Action - implements Parcelable { - /** - * Enable/disable conversation notifications. - */ - public static void enableConversationNotifications(final String conversationId, - final boolean enableNotification) { - Assert.notNull(conversationId); - - final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction( - conversationId, enableNotification, null, null); - action.start(); - } - - /** - * Sets conversation notification sound. - */ - public static void setConversationNotificationSound(final String conversationId, - final String ringtoneUri) { - Assert.notNull(conversationId); - - final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction( - conversationId, null, ringtoneUri, null); - action.start(); - } - - /** - * Enable/disable vibrations for conversation notification. - */ - public static void enableVibrationForConversationNotification(final String conversationId, - final boolean enableVibration) { - Assert.notNull(conversationId); - - final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction( - conversationId, null, null, enableVibration); - action.start(); - } - - private static final String KEY_CONVERSATION_ID = "conversation_id"; - - // Keys for all settable settings. - private static final String KEY_ENABLE_NOTIFICATION = "enable_notification"; - private static final String KEY_RINGTONE_URI = "ringtone_uri"; - private static final String KEY_ENABLE_VIBRATION = "enable_vibration"; - - protected UpdateConversationOptionsAction(final String conversationId, - final Boolean enableNotification, final String ringtoneUri, - final Boolean enableVibration) { - Assert.notNull(conversationId); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - if (enableNotification != null) { - actionParameters.putBoolean(KEY_ENABLE_NOTIFICATION, enableNotification); - } - - if (ringtoneUri != null) { - actionParameters.putString(KEY_RINGTONE_URI, ringtoneUri); - } - - if (enableVibration != null) { - actionParameters.putBoolean(KEY_ENABLE_VIBRATION, enableVibration); - } - } - - protected void putOptionValuesInTransaction(final ContentValues values, - final DatabaseWrapper dbWrapper) { - Assert.isTrue(dbWrapper.getDatabase().inTransaction()); - if (actionParameters.containsKey(KEY_ENABLE_NOTIFICATION)) { - values.put(ConversationColumns.NOTIFICATION_ENABLED, - actionParameters.getBoolean(KEY_ENABLE_NOTIFICATION)); - } - - if (actionParameters.containsKey(KEY_RINGTONE_URI)) { - values.put(ConversationColumns.NOTIFICATION_SOUND_URI, - actionParameters.getString(KEY_RINGTONE_URI)); - } - - if (actionParameters.containsKey(KEY_ENABLE_VIBRATION)) { - values.put(ConversationColumns.NOTIFICATION_VIBRATION, - actionParameters.getBoolean(KEY_ENABLE_VIBRATION)); - } - } - - @Override - protected Object executeAction() { - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - putOptionValuesInTransaction(values, db); - - BugleDatabaseOperations.updateConversationRowIfExists(db, conversationId, values); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - MessagingContentProvider.notifyConversationMetadataChanged(conversationId); - return null; - } - - protected UpdateConversationOptionsAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public UpdateConversationOptionsAction createFromParcel(final Parcel in) { - return new UpdateConversationOptionsAction(in); - } - - @Override - public UpdateConversationOptionsAction[] newArray(final int size) { - return new UpdateConversationOptionsAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java b/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java deleted file mode 100644 index c74096d..0000000 --- a/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.util.Assert; - -public class UpdateDestinationBlockedAction extends Action { - public interface UpdateDestinationBlockedActionListener { - @Assert.RunsOnMainThread - abstract void onUpdateDestinationBlockedAction(final UpdateDestinationBlockedAction action, - final boolean success, - final boolean block, - final String destination); - } - - public static class UpdateDestinationBlockedActionMonitor extends ActionMonitor - implements ActionMonitor.ActionCompletedListener { - private final UpdateDestinationBlockedActionListener mListener; - - public UpdateDestinationBlockedActionMonitor( - Object data, UpdateDestinationBlockedActionListener mListener) { - super(STATE_CREATED, generateUniqueActionKey("UpdateDestinationBlockedAction"), data); - setCompletedListener(this); - this.mListener = mListener; - } - - private void onActionDone(final boolean succeeded, - final ActionMonitor monitor, - final Action action, - final Object data, - final Object result) { - mListener.onUpdateDestinationBlockedAction( - (UpdateDestinationBlockedAction) action, - succeeded, - action.actionParameters.getBoolean(KEY_BLOCKED), - action.actionParameters.getString(KEY_DESTINATION)); - } - - @Override - public void onActionSucceeded(final ActionMonitor monitor, - final Action action, - final Object data, - final Object result) { - onActionDone(true, monitor, action, data, result); - } - - @Override - public void onActionFailed(final ActionMonitor monitor, - final Action action, - final Object data, - final Object result) { - onActionDone(false, monitor, action, data, result); - } - } - - - public static UpdateDestinationBlockedActionMonitor updateDestinationBlocked( - final String destination, final boolean blocked, final String conversationId, - final UpdateDestinationBlockedActionListener listener) { - Assert.notNull(listener); - final UpdateDestinationBlockedActionMonitor monitor = - new UpdateDestinationBlockedActionMonitor(null, listener); - final UpdateDestinationBlockedAction action = - new UpdateDestinationBlockedAction(destination, blocked, conversationId, - monitor.getActionKey()); - action.start(monitor); - return monitor; - } - - private static final String KEY_CONVERSATION_ID = "conversation_id"; - private static final String KEY_DESTINATION = "destination"; - private static final String KEY_BLOCKED = "blocked"; - - protected UpdateDestinationBlockedAction( - final String destination, final boolean blocked, final String conversationId, - final String actionKey) { - super(actionKey); - Assert.isTrue(!TextUtils.isEmpty(destination)); - actionParameters.putString(KEY_DESTINATION, destination); - actionParameters.putBoolean(KEY_BLOCKED, blocked); - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - } - - @Override - protected Object executeAction() { - final String destination = actionParameters.getString(KEY_DESTINATION); - final boolean isBlocked = actionParameters.getBoolean(KEY_BLOCKED); - String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final DatabaseWrapper db = DataModel.get().getDatabase(); - BugleDatabaseOperations.updateDestination(db, destination, isBlocked); - if (conversationId == null) { - conversationId = BugleDatabaseOperations - .getConversationFromOtherParticipantDestination(db, destination); - } - if (conversationId != null) { - if (isBlocked) { - UpdateConversationArchiveStatusAction.archiveConversation(conversationId); - } else { - UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId); - } - MessagingContentProvider.notifyParticipantsChanged(conversationId); - } - return null; - } - - protected UpdateDestinationBlockedAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public UpdateDestinationBlockedAction createFromParcel(final Parcel in) { - return new UpdateDestinationBlockedAction(in); - } - - @Override - public UpdateDestinationBlockedAction[] newArray(final int size) { - return new UpdateDestinationBlockedAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java b/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java deleted file mode 100644 index 94e6f3b..0000000 --- a/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleNotifications; - -/** - * Updates the message notification (generally, to include voice replies we've - * made since the notification was first posted). - */ -public class UpdateMessageNotificationAction extends Action { - - public static void updateMessageNotification() { - new UpdateMessageNotificationAction().start(); - } - - private UpdateMessageNotificationAction() { - } - - @Override - protected Object executeAction() { - BugleNotifications.update(true /* silent */, BugleNotifications.UPDATE_MESSAGES); - return null; - } - - private UpdateMessageNotificationAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public UpdateMessageNotificationAction createFromParcel(final Parcel in) { - return new UpdateMessageNotificationAction(in); - } - - @Override - public UpdateMessageNotificationAction[] newArray(final int size) { - return new UpdateMessageNotificationAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java b/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java deleted file mode 100644 index 273dce9..0000000 --- a/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentValues; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.util.Assert; - -/** - * Action used to update size fields of a single part - */ -public class UpdateMessagePartSizeAction extends Action implements Parcelable { - /** - * Update size of part - */ - public static void updateSize(final String partId, final int width, final int height) { - Assert.notNull(partId); - Assert.inRange(width, 0, Integer.MAX_VALUE); - Assert.inRange(height, 0, Integer.MAX_VALUE); - - final UpdateMessagePartSizeAction action = new UpdateMessagePartSizeAction( - partId, width, height); - action.start(); - } - - private static final String KEY_PART_ID = "part_id"; - private static final String KEY_WIDTH = "width"; - private static final String KEY_HEIGHT = "height"; - - private UpdateMessagePartSizeAction(final String partId, final int width, final int height) { - actionParameters.putString(KEY_PART_ID, partId); - actionParameters.putInt(KEY_WIDTH, width); - actionParameters.putInt(KEY_HEIGHT, height); - } - - @Override - protected Object executeAction() { - final String partId = actionParameters.getString(KEY_PART_ID); - final int width = actionParameters.getInt(KEY_WIDTH); - final int height = actionParameters.getInt(KEY_HEIGHT); - - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - - values.put(PartColumns.WIDTH, width); - values.put(PartColumns.HEIGHT, height); - - // Part may have been deleted so allow update to fail without asserting - BugleDatabaseOperations.updateRowIfExists(db, DatabaseHelper.PARTS_TABLE, - PartColumns._ID, partId, values); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - return null; - } - - private UpdateMessagePartSizeAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public UpdateMessagePartSizeAction createFromParcel(final Parcel in) { - return new UpdateMessagePartSizeAction(in); - } - - @Override - public UpdateMessagePartSizeAction[] newArray(final int size) { - return new UpdateMessagePartSizeAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java b/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java deleted file mode 100644 index c1f39e1..0000000 --- a/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.LogUtil; - -public class WriteDraftMessageAction extends Action implements Parcelable { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - - /** - * Set draft message (no listener) - */ - public static void writeDraftMessage(final String conversationId, final MessageData message) { - final WriteDraftMessageAction action = new WriteDraftMessageAction(conversationId, message); - action.start(); - } - - private static final String KEY_CONVERSATION_ID = "conversationId"; - private static final String KEY_MESSAGE = "message"; - - private WriteDraftMessageAction(final String conversationId, final MessageData message) { - actionParameters.putString(KEY_CONVERSATION_ID, conversationId); - actionParameters.putParcelable(KEY_MESSAGE, message); - } - - @Override - protected Object executeAction() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); - final MessageData message = actionParameters.getParcelable(KEY_MESSAGE); - if (message.getSelfId() == null || message.getParticipantId() == null) { - // This could happen when this occurs before the draft message is loaded - // In this case, we just use the conversation's current self id as draft's - // self id and/or participant id - final ConversationListItemData conversation = - ConversationListItemData.getExistingConversation(db, conversationId); - if (conversation != null) { - final String senderAndSelf = conversation.getSelfId(); - if (message.getSelfId() == null) { - message.bindSelfId(senderAndSelf); - } - if (message.getParticipantId() == null) { - message.bindParticipantId(senderAndSelf); - } - } else { - LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId + - "already deleted before saving draft message " + - message.getMessageId() + ". Aborting WriteDraftMessageAction."); - return null; - } - } - // Drafts are only kept in the local DB... - final String messageId = BugleDatabaseOperations.updateDraftMessageData( - db, conversationId, message, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - MessagingContentProvider.notifyConversationListChanged(); - MessagingContentProvider.notifyConversationMetadataChanged(conversationId); - return messageId; - } - - private WriteDraftMessageAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public WriteDraftMessageAction createFromParcel(final Parcel in) { - return new WriteDraftMessageAction(in); - } - - @Override - public WriteDraftMessageAction[] newArray(final int size) { - return new WriteDraftMessageAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } -} diff --git a/src/com/android/messaging/datamodel/binding/BindableData.java b/src/com/android/messaging/datamodel/binding/BindableData.java deleted file mode 100644 index 5446098..0000000 --- a/src/com/android/messaging/datamodel/binding/BindableData.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.binding; - -/** - * Base class for data objects that will be bound to a piece of the UI - */ -public abstract class BindableData { - /** - * Called by Binding during unbind to allow data to proactively unregister callbacks - * Data instance should release all listeners that may call back to the host UI - */ - protected abstract void unregisterListeners(); - - /** - * Key used to identify the piece of UI that the data is currently bound to - */ - private String mBindingId; - - /** - * Bind this data to the ui host - checks data is currently unbound - */ - public void bind(final String bindingId) { - if (isBound() || bindingId == null) { - throw new IllegalStateException(); - } - mBindingId = bindingId; - } - - /** - * Unbind this data from the ui host - checks that the data is currently bound to specified id - */ - public void unbind(final String bindingId) { - if (!isBound(bindingId)) { - throw new IllegalStateException(); - } - unregisterListeners(); - mBindingId = null; - } - - /** - * Check to see if the data is bound to anything - * - * TODO: This should be package private because it's supposed to only be used by Binding, - * however, several classes call this directly. We want the classes to track what they are - * bound to. - */ - protected boolean isBound() { - return (mBindingId != null); - } - - /** - * Check to see if data is still bound with specified bindingId before calling over to ui - */ - public boolean isBound(final String bindingId) { - return (bindingId.equals(mBindingId)); - } -} diff --git a/src/com/android/messaging/datamodel/binding/BindableOnceData.java b/src/com/android/messaging/datamodel/binding/BindableOnceData.java deleted file mode 100644 index 08e11da..0000000 --- a/src/com/android/messaging/datamodel/binding/BindableOnceData.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.binding; - -/** - * A BindableData that's only used to be bound once. If the client needs to rebind, it needs - * to create a new instance of the BindableOnceData. - */ -public abstract class BindableOnceData extends BindableData { - private boolean boundOnce = false; - - @Override - public void bind(final String bindingId) { - // Ensures that we can't re-bind again after the first binding. - if (boundOnce) { - throw new IllegalStateException(); - } - super.bind(bindingId); - boundOnce = true; - } - - /** - * Checks if the instance is bound to anything. - */ - @Override - public boolean isBound() { - return super.isBound(); - } -} diff --git a/src/com/android/messaging/datamodel/binding/Binding.java b/src/com/android/messaging/datamodel/binding/Binding.java deleted file mode 100644 index 3ec01dd..0000000 --- a/src/com/android/messaging/datamodel/binding/Binding.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.binding; - -import java.util.concurrent.atomic.AtomicLong; - -public class Binding extends BindingBase { - private static AtomicLong sBindingIdx = new AtomicLong(System.currentTimeMillis() * 1000); - - private String mBindingId; - private T mData; - private final Object mOwner; - private boolean mWasBound; - - /** - * Initialize a binding instance - the owner is typically the containing class - */ - Binding(final Object owner) { - mOwner = owner; - } - - @Override - public T getData() { - ensureBound(); - return mData; - } - - @Override - public boolean isBound() { - return (mData != null && mData.isBound(mBindingId)); - } - - @Override - public boolean isBound(final T data) { - return (isBound() && data == mData); - } - - @Override - public void ensureBound() { - if (!isBound()) { - throw new IllegalStateException("not bound; wasBound = " + mWasBound); - } - } - - @Override - public void ensureBound(final T data) { - if (!isBound()) { - throw new IllegalStateException("not bound; wasBound = " + mWasBound); - } else if (data != mData) { - throw new IllegalStateException("not bound to correct data " + data + " vs " + mData); - } - } - - @Override - public String getBindingId() { - return mBindingId; - } - - public void bind(final T data) { - // Check both this binding and the data not already bound - if (mData != null || data.isBound()) { - throw new IllegalStateException("already bound when binding to " + data); - } - // Generate a unique identifier for this bind call - mBindingId = Long.toHexString(sBindingIdx.getAndIncrement()); - data.bind(mBindingId); - mData = data; - mWasBound = true; - } - - public void unbind() { - // Check this binding is bound and that data is bound to this binding - if (mData == null || !mData.isBound(mBindingId)) { - throw new IllegalStateException("not bound when unbind"); - } - mData.unbind(mBindingId); - mData = null; - mBindingId = null; - } -} diff --git a/src/com/android/messaging/datamodel/binding/BindingBase.java b/src/com/android/messaging/datamodel/binding/BindingBase.java deleted file mode 100644 index 3d6da9b..0000000 --- a/src/com/android/messaging/datamodel/binding/BindingBase.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.binding; - -/** - * The binding class keeps track of a binding between a ui component and an item of BindableData - * It allows each side to ensure that when it communicates with the other they are still bound - * together. - * NOTE: Ensure that the UI component uses the same binding instance for it's whole lifetime - * (DO NOT CREATE A NEW BINDING EACH TIME A NEW PIECE OF DATA IS BOUND)... - * - * The ui component owns the binding instance. - * It can use it [isBound(data)] to see if the binding still binds to the right piece of data - * - * Upon binding the data is informed of a unique binding key generated in this class and can use - * that to ensure that it is still issuing callbacks to the right piece of ui. - */ -public abstract class BindingBase { - /** - * Creates a new exclusively owned binding for the owner object. - */ - public static Binding createBinding(final Object owner) { - return new Binding(owner); - } - - /** - * Creates a new read-only binding referencing the source binding object. - * TODO: We may want to refcount the Binding references, so that when the binding owner - * calls unbind() when there's still outstanding references we can catch it. - */ - public static ImmutableBindingRef createBindingReference( - final BindingBase srcBinding) { - return new ImmutableBindingRef(srcBinding); - } - - /** - * Creates a detachable binding for the owner object. Use this if your owner object is a UI - * component that may undergo a "detached from window" -> "re-attached to window" transition. - */ - public static DetachableBinding createDetachableBinding( - final Object owner) { - return new DetachableBinding(owner); - } - - public abstract T getData(); - - /** - * Check if binding connects to the specified data instance - */ - public abstract boolean isBound(); - - /** - * Check if binding connects to the specified data instance - */ - public abstract boolean isBound(final T data); - - /** - * Throw if binding connects to the specified data instance - */ - public abstract void ensureBound(); - - /** - * Throw if binding connects to the specified data instance - */ - public abstract void ensureBound(final T data); - - /** - * Return the binding id for this binding (will be null if not bound) - */ - public abstract String getBindingId(); -} diff --git a/src/com/android/messaging/datamodel/binding/DetachableBinding.java b/src/com/android/messaging/datamodel/binding/DetachableBinding.java deleted file mode 100644 index a414c3b..0000000 --- a/src/com/android/messaging/datamodel/binding/DetachableBinding.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.binding; - -import com.android.messaging.util.Assert; - -/** - * An extension on {@link Binding} that allows for temporary data detachment from the UI component. - * This is used when, instead of destruction or data rebinding, the owning UI undergoes a - * "detached from window" -> "re-attached to window" transition, in which case we want to - * temporarily unbind the data and remember it so that it can be rebound when the UI is re-attached - * to window later. - */ -public class DetachableBinding extends Binding { - private T mDetachedData; - - DetachableBinding(Object owner) { - super(owner); - } - - @Override - public void bind(T data) { - super.bind(data); - // Rebinding before re-attaching. Pre-emptively throw away the detached data because - // it's now stale. - mDetachedData = null; - } - - public void detach() { - Assert.isNull(mDetachedData); - Assert.isTrue(isBound()); - mDetachedData = getData(); - unbind(); - } - - public void reAttachIfPossible() { - if (mDetachedData != null) { - Assert.isFalse(isBound()); - bind(mDetachedData); - mDetachedData = null; - } - } -} diff --git a/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java b/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java deleted file mode 100644 index 9a0a3d6..0000000 --- a/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.binding; - -import com.android.messaging.util.Assert; - -/** - * A immutable wrapper around a Binding object. Callers can only access readonly methods like - * getData(), isBound() and ensureBound() but not bind() and unbind(). This is used for MVC pattern - * where both the View and the Controller needs access to a centrally bound Model object. The View - * is the one that owns the bind/unbind logic of the Binding, whereas controller only serves as a - * consumer. - */ -public class ImmutableBindingRef extends BindingBase { - /** - * The referenced, read-only binding object. - */ - private final BindingBase mBinding; - - /** - * Hidden ctor. - */ - ImmutableBindingRef(final BindingBase binding) { - mBinding = resolveBinding(binding); - } - - @Override - public T getData() { - return mBinding.getData(); - } - - @Override - public boolean isBound() { - return mBinding.isBound(); - } - - @Override - public boolean isBound(final T data) { - return mBinding.isBound(data); - } - - @Override - public void ensureBound() { - mBinding.ensureBound(); - } - - @Override - public void ensureBound(final T data) { - mBinding.ensureBound(data); - } - - @Override - public String getBindingId() { - return mBinding.getBindingId(); - } - - /** - * Resolve the source binding to the real BindingImpl it's referencing. This avoids the - * redundancy of multiple wrapper calls when creating a binding reference from an existing - * binding reference. - */ - private BindingBase resolveBinding(final BindingBase binding) { - BindingBase resolvedBinding = binding; - while (resolvedBinding instanceof ImmutableBindingRef) { - resolvedBinding = ((ImmutableBindingRef) resolvedBinding).mBinding; - } - Assert.isTrue(resolvedBinding instanceof Binding); - return resolvedBinding; - } -} diff --git a/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java b/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java deleted file mode 100644 index 4e94ee1..0000000 --- a/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; - -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.util.Assert; - -/** - * Services data needs for BlockedParticipantsFragment - */ -public class BlockedParticipantsData extends BindableData implements - LoaderManager.LoaderCallbacks { - public interface BlockedParticipantsDataListener { - public void onBlockedParticipantsCursorUpdated(final Cursor cursor); - } - private static final String BINDING_ID = "bindingId"; - private static final int BLOCKED_PARTICIPANTS_LOADER = 1; - private final Context mContext; - private LoaderManager mLoaderManager; - private BlockedParticipantsDataListener mListener; - - public BlockedParticipantsData(final Context context, - final BlockedParticipantsDataListener listener) { - mContext = context; - mListener = listener; - } - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.isTrue(id == BLOCKED_PARTICIPANTS_LOADER); - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - final Uri uri = MessagingContentProvider.PARTICIPANTS_URI; - return new BoundCursorLoader(bindingId, mContext, uri, - ParticipantData.ParticipantsQuery.PROJECTION, - ParticipantColumns.BLOCKED + "=1", null, null); - } - return null; - } - - @Override - public void onLoadFinished(final Loader loader, final Cursor cursor) { - Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER); - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - Assert.isTrue(isBound(cursorLoader.getBindingId())); - mListener.onBlockedParticipantsCursorUpdated(cursor); - } - - @Override - public void onLoaderReset(final Loader loader) { - Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER); - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - Assert.isTrue(isBound(cursorLoader.getBindingId())); - mListener.onBlockedParticipantsCursorUpdated(null); - } - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - final Bundle args = new Bundle(); - args.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_LOADER, args, this); - } - - @Override - protected void unregisterListeners() { - mListener = null; - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_LOADER); - mLoaderManager = null; - } - } - - public ParticipantListItemData createParticipantListItemData(Cursor cursor) { - return new ParticipantListItemData(ParticipantData.getFromCursor(cursor)); - } -} diff --git a/src/com/android/messaging/datamodel/data/ContactListItemData.java b/src/com/android/messaging/datamodel/data/ContactListItemData.java deleted file mode 100644 index dcc7e20..0000000 --- a/src/com/android/messaging/datamodel/data/ContactListItemData.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract.DisplayNameSources; - -import com.android.ex.chips.RecipientEntry; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactRecipientEntryUtils; -import com.android.messaging.util.ContactUtil; - -/** - * Data model object used to power ContactListItemViews, which may be displayed either in - * our contact list, or in the chips UI search drop down presented by ContactDropdownLayouter. - */ -public class ContactListItemData { - // Keeps the contact data in the form of RecipientEntry that RecipientEditTextView can - // directly use. - private RecipientEntry mRecipientEntry; - - private CharSequence mStyledName; - private CharSequence mStyledDestination; - - // If this contact is the first in the list for its first letter, then this will be the - // first letter, otherwise this is null. - private String mAlphabetHeader; - - // Is the contact the only item in the list (happens when the user clicks on an - // existing chip for which we show full contact detail for the selected contact). - private boolean mSingleRecipient; - - /** - * Bind to a contact cursor in the contact list. - */ - public void bind(final Cursor cursor, final String alphabetHeader) { - final long dataId = cursor.getLong(ContactUtil.INDEX_DATA_ID); - final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID); - final String lookupKey = cursor.getString(ContactUtil.INDEX_LOOKUP_KEY); - final String displayName = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME); - final String photoThumbnailUri = cursor.getString(ContactUtil.INDEX_PHOTO_URI); - final String destination = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL); - final int destinationType = cursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE); - final String destinationLabel = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL); - mStyledName = null; - mStyledDestination = null; - mAlphabetHeader = alphabetHeader; - mSingleRecipient = false; - - // Check whether this contact is first level (i.e. whether it's the first entry of this - // contact in the contact list). - boolean isFirstLevel = true; - if (!cursor.isFirst() && cursor.moveToPrevious()) { - final long contactIdPrevious = cursor.getLong(ContactUtil.INDEX_CONTACT_ID); - if (contactId == contactIdPrevious) { - isFirstLevel = false; - } - cursor.moveToNext(); - } - - mRecipientEntry = ContactUtil.createRecipientEntry(displayName, - DisplayNameSources.STRUCTURED_NAME, destination, destinationType, destinationLabel, - contactId, lookupKey, dataId, photoThumbnailUri, isFirstLevel); - } - - /** - * Bind to a RecipientEntry produced by the chips text view in the search drop down, plus - * optional styled name & destination for showing bold search match. - */ - public void bind(final RecipientEntry entry, final CharSequence styledName, - final CharSequence styledDestination, final boolean singleRecipient) { - Assert.isTrue(entry.isValid()); - mRecipientEntry = entry; - mStyledName = styledName; - mStyledDestination = styledDestination; - mAlphabetHeader = null; - mSingleRecipient = singleRecipient; - } - - public CharSequence getDisplayName() { - final CharSequence displayName = mStyledName != null ? mStyledName : - ContactRecipientEntryUtils.getDisplayNameForContactList(mRecipientEntry); - return displayName == null ? "" : displayName; - } - - public Uri getPhotoThumbnailUri() { - return mRecipientEntry.getPhotoThumbnailUri() == null ? null : - mRecipientEntry.getPhotoThumbnailUri(); - } - - public CharSequence getDestination() { - final CharSequence destination = mStyledDestination != null ? - mStyledDestination : ContactRecipientEntryUtils.formatDestination(mRecipientEntry); - return destination == null ? "" : destination; - } - - public int getDestinationType() { - return mRecipientEntry.getDestinationType(); - } - - public String getDestinationLabel() { - return mRecipientEntry.getDestinationLabel(); - } - - public long getContactId() { - return mRecipientEntry.getContactId(); - } - - public String getLookupKey() { - return mRecipientEntry.getLookupKey(); - } - - /** - * Returns if this item is "first-level," i.e. whether it's the first entry of the contact - * that it represents in the list. For example, if John Smith has 3 different phone numbers, - * then the first number is considered first-level, while the other two are considered - * second-level. - */ - public boolean getIsFirstLevel() { - // Treat the item as first level if it's a top-level recipient entry, or if it's the only - // item in the list. - return mRecipientEntry.isFirstLevel() || mSingleRecipient; - } - - /** - * Returns if this item is simple, i.e. it has only avatar and a display name with phone number - * embedded so we can hide everything else. - */ - public boolean getIsSimpleContactItem() { - return ContactRecipientEntryUtils.isAvatarAndNumberOnlyContact(mRecipientEntry) || - ContactRecipientEntryUtils.isSendToDestinationContact(mRecipientEntry); - } - - public String getAlphabetHeader() { - return mAlphabetHeader; - } - - /** - * Returns a RecipientEntry instance readily usable by the RecipientEditTextView. - */ - public RecipientEntry getRecipientEntry() { - return mRecipientEntry; - } -} diff --git a/src/com/android/messaging/datamodel/data/ContactPickerData.java b/src/com/android/messaging/datamodel/data/ContactPickerData.java deleted file mode 100644 index fd6fca0..0000000 --- a/src/com/android/messaging/datamodel/data/ContactPickerData.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.os.Bundle; - -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.FrequentContactsCursorBuilder; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.LogUtil; - -/** - * Class to access phone contacts. - * The caller is responsible for ensuring that the app has READ_CONTACTS permission (see - * {@link ContactUtil#hasReadContactsPermission()}) before instantiating this class. - */ -public class ContactPickerData extends BindableData implements - LoaderManager.LoaderCallbacks { - public interface ContactPickerDataListener { - void onAllContactsCursorUpdated(Cursor data); - void onFrequentContactsCursorUpdated(Cursor data); - void onContactCustomColorLoaded(ContactPickerData data); - } - - private static final String BINDING_ID = "bindingId"; - private final Context mContext; - private LoaderManager mLoaderManager; - private ContactPickerDataListener mListener; - private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder; - - public ContactPickerData(final Context context, final ContactPickerDataListener listener) { - mListener = listener; - mContext = context; - mFrequentContactsCursorBuilder = new FrequentContactsCursorBuilder(); - } - - private static final int ALL_CONTACTS_LOADER = 1; - private static final int FREQUENT_CONTACTS_LOADER = 2; - private static final int PARTICIPANT_LOADER = 3; - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - switch (id) { - case ALL_CONTACTS_LOADER: - return ContactUtil.getPhones(mContext) - .createBoundCursorLoader(bindingId); - case FREQUENT_CONTACTS_LOADER: - return ContactUtil.getFrequentContacts(mContext) - .createBoundCursorLoader(bindingId); - case PARTICIPANT_LOADER: - return new BoundCursorLoader(bindingId, mContext, - MessagingContentProvider.PARTICIPANTS_URI, - ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); - default: - Assert.fail("Unknown loader id for contact picker!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the contacts list"); - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoadFinished(final Loader loader, final Cursor data) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case ALL_CONTACTS_LOADER: - mListener.onAllContactsCursorUpdated(data); - mFrequentContactsCursorBuilder.setAllContacts(data); - break; - case FREQUENT_CONTACTS_LOADER: - mFrequentContactsCursorBuilder.setFrequents(data); - break; - case PARTICIPANT_LOADER: - mListener.onContactCustomColorLoaded(this); - break; - default: - Assert.fail("Unknown loader id for contact picker!"); - break; - } - - if (loader.getId() != PARTICIPANT_LOADER) { - // The frequent contacts cursor to be used in the UI depends on results from both - // all contacts and frequent contacts loader, and we don't know which will finish - // first. Therefore, try to build the cursor and notify the listener if it's - // successfully built. - final Cursor frequentContactsCursor = mFrequentContactsCursorBuilder.build(); - if (frequentContactsCursor != null) { - mListener.onFrequentContactsCursorUpdated(frequentContactsCursor); - } - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the contacts list"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoaderReset(final Loader loader) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case ALL_CONTACTS_LOADER: - mListener.onAllContactsCursorUpdated(null); - mFrequentContactsCursorBuilder.setAllContacts(null); - break; - case FREQUENT_CONTACTS_LOADER: - mListener.onFrequentContactsCursorUpdated(null); - mFrequentContactsCursorBuilder.setFrequents(null); - break; - case PARTICIPANT_LOADER: - mListener.onContactCustomColorLoaded(this); - break; - default: - Assert.fail("Unknown loader id for contact picker!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the contacts list"); - } - } - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - final Bundle args = new Bundle(); - args.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(ALL_CONTACTS_LOADER, args, this); - mLoaderManager.initLoader(FREQUENT_CONTACTS_LOADER, args, this); - mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this); - } - - @Override - protected void unregisterListeners() { - mListener = null; - - - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(ALL_CONTACTS_LOADER); - mLoaderManager.destroyLoader(FREQUENT_CONTACTS_LOADER); - mLoaderManager.destroyLoader(PARTICIPANT_LOADER); - mLoaderManager = null; - } - mFrequentContactsCursorBuilder.resetBuilder(); - } - - public static boolean isTooManyParticipants(final int participantCount) { - // When creating a conversation, the conversation will be created using the system's - // default SIM, so use the default MmsConfig's recipient limit. - return (participantCount > MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID) - .getRecipientLimit()); - } - - public static boolean getCanAddMoreParticipants(final int participantCount) { - // When creating a conversation, the conversation will be created using the system's - // default SIM, so use the default MmsConfig's recipient limit. - return (participantCount < MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID) - .getRecipientLimit()); - } -} diff --git a/src/com/android/messaging/datamodel/data/ConversationData.java b/src/com/android/messaging/datamodel/data/ConversationData.java deleted file mode 100644 index d504928..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationData.java +++ /dev/null @@ -1,849 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.database.CursorWrapper; -import android.database.sqlite.SQLiteFullException; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.common.contacts.DataUsageStatUpdater; -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.DeleteConversationAction; -import com.android.messaging.datamodel.action.DeleteMessageAction; -import com.android.messaging.datamodel.action.InsertNewMessageAction; -import com.android.messaging.datamodel.action.RedownloadMmsAction; -import com.android.messaging.datamodel.action.ResendMessageAction; -import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.widget.WidgetConversationProvider; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class ConversationData extends BindableData { - - private static final String TAG = "bugle_datamodel"; - private static final String BINDING_ID = "bindingId"; - private static final long LAST_MESSAGE_TIMESTAMP_NaN = -1; - private static final int MESSAGE_COUNT_NaN = -1; - - /** - * Takes a conversation id and a list of message ids and computes the positions - * for each message. - */ - public List getPositions(final String conversationId, final List ids) { - final ArrayList result = new ArrayList(); - - if (ids.isEmpty()) { - return result; - } - - final Cursor c = new ConversationData.ReversedCursor( - DataModel.get().getDatabase().rawQuery( - ConversationMessageData.getConversationMessageIdsQuerySql(), - new String [] { conversationId })); - if (c != null) { - try { - final Set idsSet = new HashSet(ids); - if (c.moveToLast()) { - do { - final long messageId = c.getLong(0); - if (idsSet.contains(messageId)) { - result.add(c.getPosition()); - } - } while (c.moveToPrevious()); - } - } finally { - c.close(); - } - } - Collections.sort(result); - return result; - } - - public interface ConversationDataListener { - public void onConversationMessagesCursorUpdated(ConversationData data, Cursor cursor, - @Nullable ConversationMessageData newestMessage, boolean isSync); - public void onConversationMetadataUpdated(ConversationData data); - public void closeConversation(String conversationId); - public void onConversationParticipantDataLoaded(ConversationData data); - public void onSubscriptionListDataLoaded(ConversationData data); - } - - private static class ReversedCursor extends CursorWrapper { - final int mCount; - - public ReversedCursor(final Cursor cursor) { - super(cursor); - mCount = cursor.getCount(); - } - - @Override - public boolean moveToPosition(final int position) { - return super.moveToPosition(mCount - position - 1); - } - - @Override - public int getPosition() { - return mCount - super.getPosition() - 1; - } - - @Override - public boolean isAfterLast() { - return super.isBeforeFirst(); - } - - @Override - public boolean isBeforeFirst() { - return super.isAfterLast(); - } - - @Override - public boolean isFirst() { - return super.isLast(); - } - - @Override - public boolean isLast() { - return super.isFirst(); - } - - @Override - public boolean move(final int offset) { - return super.move(-offset); - } - - @Override - public boolean moveToFirst() { - return super.moveToLast(); - } - - @Override - public boolean moveToLast() { - return super.moveToFirst(); - } - - @Override - public boolean moveToNext() { - return super.moveToPrevious(); - } - - @Override - public boolean moveToPrevious() { - return super.moveToNext(); - } - } - - /** - * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times. - */ - private class MetadataLoaderCallbacks implements LoaderManager.LoaderCallbacks { - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.equals(CONVERSATION_META_DATA_LOADER, id); - Loader loader = null; - - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - final Uri uri = - MessagingContentProvider.buildConversationMetadataUri(mConversationId); - loader = new BoundCursorLoader(bindingId, mContext, uri, - ConversationListItemData.PROJECTION, null, null, null); - } else { - LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " + - mConversationId); - } - return loader; - } - - @Override - public void onLoadFinished(final Loader generic, final Cursor data) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - if (data.moveToNext()) { - Assert.isTrue(data.getCount() == 1); - mConversationMetadata.bind(data); - mListeners.onConversationMetadataUpdated(ConversationData.this); - } else { - // Close the conversation, no meta data means conversation was deleted - LogUtil.w(TAG, "Meta data loader returned nothing for mConversationId = " + - mConversationId); - mListeners.closeConversation(mConversationId); - // Notify the widget the conversation is deleted so it can go into its - // configure state. - WidgetConversationProvider.notifyConversationDeleted( - Factory.get().getApplicationContext(), - mConversationId); - } - } else { - LogUtil.w(TAG, "Meta data loader finished after unbinding mConversationId = " + - mConversationId); - } - } - - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - // Clear the conversation meta data - mConversationMetadata = new ConversationListItemData(); - mListeners.onConversationMetadataUpdated(ConversationData.this); - } else { - LogUtil.w(TAG, "Meta data loader reset after unbinding mConversationId = " + - mConversationId); - } - } - } - - /** - * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times. - */ - private class MessagesLoaderCallbacks implements LoaderManager.LoaderCallbacks { - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.equals(CONVERSATION_MESSAGES_LOADER, id); - Loader loader = null; - - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - final Uri uri = - MessagingContentProvider.buildConversationMessagesUri(mConversationId); - loader = new BoundCursorLoader(bindingId, mContext, uri, - ConversationMessageData.getProjection(), null, null, null); - mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN; - mMessageCount = MESSAGE_COUNT_NaN; - } else { - LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " + - mConversationId); - } - return loader; - } - - @Override - public void onLoadFinished(final Loader generic, final Cursor rawData) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - // Check if we have a new message, or if we had a message sync. - ConversationMessageData newMessage = null; - boolean isSync = false; - Cursor data = null; - if (rawData != null) { - // Note that the cursor is sorted DESC so here we reverse it. - // This is a performance issue (improvement) for large cursors. - data = new ReversedCursor(rawData); - - final int messageCountOld = mMessageCount; - mMessageCount = data.getCount(); - final ConversationMessageData lastMessage = getLastMessage(data); - if (lastMessage != null) { - final long lastMessageTimestampOld = mLastMessageTimestamp; - mLastMessageTimestamp = lastMessage.getReceivedTimeStamp(); - final String lastMessageIdOld = mLastMessageId; - mLastMessageId = lastMessage.getMessageId(); - if (TextUtils.equals(lastMessageIdOld, mLastMessageId) && - messageCountOld < mMessageCount) { - // Last message stays the same (no incoming message) but message - // count increased, which means there has been a message sync. - isSync = true; - } else if (messageCountOld != MESSAGE_COUNT_NaN && // Ignore initial load - mLastMessageTimestamp != LAST_MESSAGE_TIMESTAMP_NaN && - mLastMessageTimestamp > lastMessageTimestampOld) { - newMessage = lastMessage; - } - } else { - mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN; - } - } else { - mMessageCount = MESSAGE_COUNT_NaN; - } - - mListeners.onConversationMessagesCursorUpdated(ConversationData.this, data, - newMessage, isSync); - } else { - LogUtil.w(TAG, "Messages loader finished after unbinding mConversationId = " + - mConversationId); - } - } - - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mListeners.onConversationMessagesCursorUpdated(ConversationData.this, null, null, - false); - mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN; - mMessageCount = MESSAGE_COUNT_NaN; - } else { - LogUtil.w(TAG, "Messages loader reset after unbinding mConversationId = " + - mConversationId); - } - } - - private ConversationMessageData getLastMessage(final Cursor cursor) { - if (cursor != null && cursor.getCount() > 0) { - final int position = cursor.getPosition(); - if (cursor.moveToLast()) { - final ConversationMessageData messageData = new ConversationMessageData(); - messageData.bind(cursor); - cursor.move(position); - return messageData; - } - } - return null; - } - } - - /** - * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times. - */ - private class ParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks { - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.equals(PARTICIPANT_LOADER, id); - Loader loader = null; - - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - final Uri uri = - MessagingContentProvider.buildConversationParticipantsUri(mConversationId); - loader = new BoundCursorLoader(bindingId, mContext, uri, - ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); - } else { - LogUtil.w(TAG, "Creating participant loader after unbinding mConversationId = " + - mConversationId); - } - return loader; - } - - @Override - public void onLoadFinished(final Loader generic, final Cursor data) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mParticipantData.bind(data); - mListeners.onConversationParticipantDataLoaded(ConversationData.this); - } else { - LogUtil.w(TAG, "Participant loader finished after unbinding mConversationId = " + - mConversationId); - } - } - - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mParticipantData.bind(null); - } else { - LogUtil.w(TAG, "Participant loader reset after unbinding mConversationId = " + - mConversationId); - } - } - } - - /** - * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times. - */ - private class SelfParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks { - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.equals(SELF_PARTICIPANT_LOADER, id); - Loader loader = null; - - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - loader = new BoundCursorLoader(bindingId, mContext, - MessagingContentProvider.PARTICIPANTS_URI, - ParticipantData.ParticipantsQuery.PROJECTION, - ParticipantColumns.SUB_ID + " <> ?", - new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) }, - null); - } else { - LogUtil.w(TAG, "Creating self loader after unbinding mConversationId = " + - mConversationId); - } - return loader; - } - - @Override - public void onLoadFinished(final Loader generic, final Cursor data) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mSelfParticipantsData.bind(data); - mSubscriptionListData.bind(mSelfParticipantsData.getSelfParticipants(true)); - mListeners.onSubscriptionListDataLoaded(ConversationData.this); - } else { - LogUtil.w(TAG, "Self loader finished after unbinding mConversationId = " + - mConversationId); - } - } - - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mSelfParticipantsData.bind(null); - } else { - LogUtil.w(TAG, "Self loader reset after unbinding mConversationId = " + - mConversationId); - } - } - } - - private final ConversationDataEventDispatcher mListeners; - private final MetadataLoaderCallbacks mMetadataLoaderCallbacks; - private final MessagesLoaderCallbacks mMessagesLoaderCallbacks; - private final ParticipantLoaderCallbacks mParticipantsLoaderCallbacks; - private final SelfParticipantLoaderCallbacks mSelfParticipantLoaderCallbacks; - private final Context mContext; - private final String mConversationId; - private final ConversationParticipantsData mParticipantData; - private final SelfParticipantsData mSelfParticipantsData; - private ConversationListItemData mConversationMetadata; - private final SubscriptionListData mSubscriptionListData; - private LoaderManager mLoaderManager; - private long mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN; - private int mMessageCount = MESSAGE_COUNT_NaN; - private String mLastMessageId; - - public ConversationData(final Context context, final ConversationDataListener listener, - final String conversationId) { - Assert.isTrue(conversationId != null); - mContext = context; - mConversationId = conversationId; - mMetadataLoaderCallbacks = new MetadataLoaderCallbacks(); - mMessagesLoaderCallbacks = new MessagesLoaderCallbacks(); - mParticipantsLoaderCallbacks = new ParticipantLoaderCallbacks(); - mSelfParticipantLoaderCallbacks = new SelfParticipantLoaderCallbacks(); - mParticipantData = new ConversationParticipantsData(); - mConversationMetadata = new ConversationListItemData(); - mSelfParticipantsData = new SelfParticipantsData(); - mSubscriptionListData = new SubscriptionListData(context); - - mListeners = new ConversationDataEventDispatcher(); - mListeners.add(listener); - } - - @RunsOnMainThread - public void addConversationDataListener(final ConversationDataListener listener) { - Assert.isMainThread(); - mListeners.add(listener); - } - - public String getConversationName() { - return mConversationMetadata.getName(); - } - - public boolean getIsArchived() { - return mConversationMetadata.getIsArchived(); - } - - public String getIcon() { - return mConversationMetadata.getIcon(); - } - - public String getConversationId() { - return mConversationId; - } - - public void setFocus() { - DataModel.get().setFocusedConversation(mConversationId); - // As we are loading the conversation assume the user has read the messages... - // Do this late though so that it doesn't get in the way of other actions - BugleNotifications.markMessagesAsRead(mConversationId); - } - - public void unsetFocus() { - DataModel.get().setFocusedConversation(null); - } - - public boolean isFocused() { - return isBound() && DataModel.get().isFocusedConversation(mConversationId); - } - - private static final int CONVERSATION_META_DATA_LOADER = 1; - private static final int CONVERSATION_MESSAGES_LOADER = 2; - private static final int PARTICIPANT_LOADER = 3; - private static final int SELF_PARTICIPANT_LOADER = 4; - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - // Remember the binding id so that loader callbacks can check if data is still bound - // to same ui component - final Bundle args = new Bundle(); - args.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(CONVERSATION_META_DATA_LOADER, args, mMetadataLoaderCallbacks); - mLoaderManager.initLoader(CONVERSATION_MESSAGES_LOADER, args, mMessagesLoaderCallbacks); - mLoaderManager.initLoader(PARTICIPANT_LOADER, args, mParticipantsLoaderCallbacks); - mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, mSelfParticipantLoaderCallbacks); - } - - @Override - protected void unregisterListeners() { - mListeners.clear(); - // Make sure focus has moved away from this conversation - // TODO: May false trigger if destroy happens after "new" conversation is focused. - // Assert.isTrue(!DataModel.get().isFocusedConversation(mConversationId)); - - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(CONVERSATION_META_DATA_LOADER); - mLoaderManager.destroyLoader(CONVERSATION_MESSAGES_LOADER); - mLoaderManager.destroyLoader(PARTICIPANT_LOADER); - mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER); - mLoaderManager = null; - } - } - - /** - * Gets the default self participant in the participant table (NOT the conversation's self). - * This is available as soon as self participant data is loaded. - */ - public ParticipantData getDefaultSelfParticipant() { - return mSelfParticipantsData.getDefaultSelfParticipant(); - } - - public List getSelfParticipants(final boolean activeOnly) { - return mSelfParticipantsData.getSelfParticipants(activeOnly); - } - - public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) { - return mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(activeOnly); - } - - public ParticipantData getSelfParticipantById(final String selfId) { - return mSelfParticipantsData.getSelfParticipantById(selfId); - } - - /** - * For a 1:1 conversation return the other (not self) participant (else null) - */ - public ParticipantData getOtherParticipant() { - return mParticipantData.getOtherParticipant(); - } - - /** - * Return true once the participants are loaded - */ - public boolean getParticipantsLoaded() { - return mParticipantData.isLoaded(); - } - - public void sendMessage(final BindingBase binding, - final MessageData message) { - Assert.isTrue(TextUtils.equals(mConversationId, message.getConversationId())); - Assert.isTrue(binding.getData() == this); - - if (!OsUtil.isAtLeastL_MR1() || message.getSelfId() == null) { - InsertNewMessageAction.insertNewMessage(message); - } else { - final int systemDefaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId(); - if (systemDefaultSubId != ParticipantData.DEFAULT_SELF_SUB_ID && - mSelfParticipantsData.isDefaultSelf(message.getSelfId())) { - // Lock the sub selection to the system default SIM as soon as the user clicks on - // the send button to avoid races between this and when InsertNewMessageAction is - // actually executed on the data model thread, during which the user can potentially - // change the system default SIM in Settings. - InsertNewMessageAction.insertNewMessage(message, systemDefaultSubId); - } else { - InsertNewMessageAction.insertNewMessage(message); - } - } - // Update contacts so Frequents will reflect messaging activity. - if (!getParticipantsLoaded()) { - return; // oh well, not critical - } - final ArrayList phones = new ArrayList<>(); - final ArrayList emails = new ArrayList<>(); - for (final ParticipantData participant : mParticipantData) { - if (!participant.isSelf()) { - if (participant.isEmail()) { - emails.add(participant.getSendDestination()); - } else { - phones.add(participant.getSendDestination()); - } - } - } - - if (ContactUtil.hasReadContactsPermission()) { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - final DataUsageStatUpdater updater = new DataUsageStatUpdater( - Factory.get().getApplicationContext()); - try { - if (!phones.isEmpty()) { - updater.updateWithPhoneNumber(phones); - } - if (!emails.isEmpty()) { - updater.updateWithAddress(emails); - } - } catch (final SQLiteFullException ex) { - LogUtil.w(TAG, "Unable to update contact", ex); - } - } - }); - } - } - - public void downloadMessage(final BindingBase binding, - final String messageId) { - Assert.isTrue(binding.getData() == this); - Assert.notNull(messageId); - RedownloadMmsAction.redownloadMessage(messageId); - } - - public void resendMessage(final BindingBase binding, final String messageId) { - Assert.isTrue(binding.getData() == this); - Assert.notNull(messageId); - ResendMessageAction.resendMessage(messageId); - } - - public void deleteMessage(final BindingBase binding, final String messageId) { - Assert.isTrue(binding.getData() == this); - Assert.notNull(messageId); - DeleteMessageAction.deleteMessage(messageId); - } - - public void deleteConversation(final Binding binding) { - Assert.isTrue(binding.getData() == this); - // If possible use timestamp of last message shown to delete only messages user is aware of - if (mConversationMetadata == null) { - DeleteConversationAction.deleteConversation(mConversationId, - System.currentTimeMillis()); - } else { - mConversationMetadata.deleteConversation(); - } - } - - public void archiveConversation(final BindingBase binding) { - Assert.isTrue(binding.getData() == this); - UpdateConversationArchiveStatusAction.archiveConversation(mConversationId); - } - - public void unarchiveConversation(final BindingBase binding) { - Assert.isTrue(binding.getData() == this); - UpdateConversationArchiveStatusAction.unarchiveConversation(mConversationId); - } - - public ConversationParticipantsData getParticipants() { - return mParticipantData; - } - - /** - * Returns a dialable phone number for the participant if we are in a 1-1 conversation. - * @return the participant phone number, or null if the phone number is not valid or if there - * are more than one participant. - */ - public String getParticipantPhoneNumber() { - final ParticipantData participant = this.getOtherParticipant(); - if (participant != null) { - final String phoneNumber = participant.getSendDestination(); - if (!TextUtils.isEmpty(phoneNumber) && MmsSmsUtils.isPhoneNumber(phoneNumber)) { - return phoneNumber; - } - } - return null; - } - - /** - * Create a message to be forwarded from an existing message. - */ - public MessageData createForwardedMessage(final ConversationMessageData message) { - final MessageData forwardedMessage = new MessageData(); - - final String originalSubject = - MmsUtils.cleanseMmsSubject(mContext.getResources(), message.getMmsSubject()); - if (!TextUtils.isEmpty(originalSubject)) { - forwardedMessage.setMmsSubject( - mContext.getResources().getString(R.string.message_fwd, originalSubject)); - } - - for (final MessagePartData part : message.getParts()) { - MessagePartData forwardedPart; - - // Depending on the part type, if it is text, we can directly create a text part; - // if it is attachment, then we need to create a pending attachment data out of it, so - // that we may persist the attachment locally in the scratch folder when the user picks - // a conversation to forward to. - if (part.isText()) { - forwardedPart = MessagePartData.createTextMessagePart(part.getText()); - } else { - final PendingAttachmentData pendingAttachmentData = PendingAttachmentData - .createPendingAttachmentData(part.getContentType(), part.getContentUri()); - forwardedPart = pendingAttachmentData; - } - forwardedMessage.addPart(forwardedPart); - } - return forwardedMessage; - } - - public int getNumberOfParticipantsExcludingSelf() { - return mParticipantData.getNumberOfParticipantsExcludingSelf(); - } - - /** - * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData - * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info - * (icon, name etc.) for multi-SIM. - */ - public SubscriptionListEntry getSubscriptionEntryForSelfParticipant( - final String selfParticipantId, final boolean excludeDefault) { - return getSubscriptionEntryForSelfParticipant(selfParticipantId, excludeDefault, - mSubscriptionListData, mSelfParticipantsData); - } - - /** - * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData - * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info - * (icon, name etc.) for multi-SIM. - */ - public static SubscriptionListEntry getSubscriptionEntryForSelfParticipant( - final String selfParticipantId, final boolean excludeDefault, - final SubscriptionListData subscriptionListData, - final SelfParticipantsData selfParticipantsData) { - // SIM indicators are shown in the UI only if: - // 1. Framework has MSIM support AND - // 2. The device has had multiple *active* subscriptions. AND - // 3. The message's subscription is active. - if (OsUtil.isAtLeastL_MR1() && - selfParticipantsData.getSelfParticipantsCountExcludingDefault(true) > 1) { - return subscriptionListData.getActiveSubscriptionEntryBySelfId(selfParticipantId, - excludeDefault); - } - return null; - } - - public SubscriptionListData getSubscriptionListData() { - return mSubscriptionListData; - } - - /** - * A dummy implementation of {@link ConversationDataListener} so that subclasses may opt to - * implement some, but not all, of the interface methods. - */ - public static class SimpleConversationDataListener implements ConversationDataListener { - - @Override - public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor, - @Nullable - final - ConversationMessageData newestMessage, final boolean isSync) {} - - @Override - public void onConversationMetadataUpdated(final ConversationData data) {} - - @Override - public void closeConversation(final String conversationId) {} - - @Override - public void onConversationParticipantDataLoaded(final ConversationData data) {} - - @Override - public void onSubscriptionListDataLoaded(final ConversationData data) {} - - } - - private class ConversationDataEventDispatcher - extends ArrayList - implements ConversationDataListener { - - @Override - public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor, - @Nullable - final ConversationMessageData newestMessage, final boolean isSync) { - for (final ConversationDataListener listener : this) { - listener.onConversationMessagesCursorUpdated(data, cursor, newestMessage, isSync); - } - } - - @Override - public void onConversationMetadataUpdated(final ConversationData data) { - for (final ConversationDataListener listener : this) { - listener.onConversationMetadataUpdated(data); - } - } - - @Override - public void closeConversation(final String conversationId) { - for (final ConversationDataListener listener : this) { - listener.closeConversation(conversationId); - } - } - - @Override - public void onConversationParticipantDataLoaded(final ConversationData data) { - for (final ConversationDataListener listener : this) { - listener.onConversationParticipantDataLoaded(data); - } - } - - @Override - public void onSubscriptionListDataLoaded(final ConversationData data) { - for (final ConversationDataListener listener : this) { - listener.onSubscriptionListDataLoaded(data); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/data/ConversationListData.java b/src/com/android/messaging/datamodel/data/ConversationListData.java deleted file mode 100644 index 3d27ecd..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationListData.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.os.Bundle; - -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns; -import com.android.messaging.receiver.SmsReceiver; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.HashSet; - -public class ConversationListData extends BindableData - implements LoaderManager.LoaderCallbacks { - - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final String BINDING_ID = "bindingId"; - public static final String SORT_ORDER = - ConversationListViewColumns.SORT_TIMESTAMP + " DESC"; - - private static final String WHERE_ARCHIVED = - "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 1)"; - public static final String WHERE_NOT_ARCHIVED = - "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 0)"; - - public interface ConversationListDataListener { - public void onConversationListCursorUpdated(ConversationListData data, Cursor cursor); - public void setBlockedParticipantsAvailable(boolean blockedAvailable); - } - - private ConversationListDataListener mListener; - private final Context mContext; - private final boolean mArchivedMode; - private LoaderManager mLoaderManager; - - public ConversationListData(final Context context, final ConversationListDataListener listener, - final boolean archivedMode) { - mListener = listener; - mContext = context; - mArchivedMode = archivedMode; - } - - private static final int CONVERSATION_LIST_LOADER = 1; - private static final int BLOCKED_PARTICIPANTS_AVAILABLE_LOADER = 2; - - private static final String[] BLOCKED_PARTICIPANTS_PROJECTION = new String[] { - ParticipantColumns._ID, - ParticipantColumns.NORMALIZED_DESTINATION, - }; - private static final int INDEX_BLOCKED_PARTICIPANTS_ID = 0; - private static final int INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION = 1; - - // all blocked participants - private final HashSet mBlockedParticipants = new HashSet(); - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - final String bindingId = args.getString(BINDING_ID); - Loader loader = null; - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - switch (id) { - case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER: - loader = new BoundCursorLoader(bindingId, mContext, - MessagingContentProvider.PARTICIPANTS_URI, - BLOCKED_PARTICIPANTS_PROJECTION, - ParticipantColumns.BLOCKED + "=1", null, null); - break; - case CONVERSATION_LIST_LOADER: - loader = new BoundCursorLoader(bindingId, mContext, - MessagingContentProvider.CONVERSATIONS_URI, - ConversationListItemData.PROJECTION, - mArchivedMode ? WHERE_ARCHIVED : WHERE_NOT_ARCHIVED, - null, // selection args - SORT_ORDER); - break; - default: - Assert.fail("Unknown loader id"); - break; - } - } else { - LogUtil.w(TAG, "Creating loader after unbinding list"); - } - return loader; - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoadFinished(final Loader generic, final Cursor data) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - if (isBound(loader.getBindingId())) { - switch (loader.getId()) { - case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER: - mBlockedParticipants.clear(); - for (int i = 0; i < data.getCount(); i++) { - data.moveToPosition(i); - mBlockedParticipants.add(data.getString( - INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION)); - } - mListener.setBlockedParticipantsAvailable(data != null && data.getCount() > 0); - break; - case CONVERSATION_LIST_LOADER: - mListener.onConversationListCursorUpdated(this, data); - break; - default: - Assert.fail("Unknown loader id"); - break; - } - } else { - LogUtil.w(TAG, "Loader finished after unbinding list"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - if (isBound(loader.getBindingId())) { - switch (loader.getId()) { - case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER: - mListener.setBlockedParticipantsAvailable(false); - break; - case CONVERSATION_LIST_LOADER: - mListener.onConversationListCursorUpdated(this, null); - break; - default: - Assert.fail("Unknown loader id"); - break; - } - } else { - LogUtil.w(TAG, "Loader reset after unbinding list"); - } - } - - private Bundle mArgs; - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - mArgs = new Bundle(); - mArgs.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(CONVERSATION_LIST_LOADER, mArgs, this); - mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER, mArgs, this); - } - - public void handleMessagesSeen() { - BugleNotifications.markAllMessagesAsSeen(); - - SmsReceiver.cancelSecondaryUserNotification(); - } - - @Override - protected void unregisterListeners() { - mListener = null; - - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(CONVERSATION_LIST_LOADER); - mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER); - mLoaderManager = null; - } - } - - public boolean getHasFirstSyncCompleted() { - final SyncManager syncManager = DataModel.get().getSyncManager(); - return syncManager.getHasFirstSyncCompleted(); - } - - public void setScrolledToNewestConversation(boolean scrolledToNewestConversation) { - DataModel.get().setConversationListScrolledToNewestConversation( - scrolledToNewestConversation); - if (scrolledToNewestConversation) { - handleMessagesSeen(); - } - } - - public HashSet getBlockedParticipants() { - return mBlockedParticipants; - } -} diff --git a/src/com/android/messaging/datamodel/data/ConversationListItemData.java b/src/com/android/messaging/datamodel/data/ConversationListItemData.java deleted file mode 100644 index b2e6e1c..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationListItemData.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.text.TextUtils; - -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.action.DeleteConversationAction; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Dates; -import com.google.common.base.Joiner; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class wrapping the conversation list view used to display each item in conversation list - */ -public class ConversationListItemData { - private String mConversationId; - private String mName; - private String mIcon; - private boolean mIsRead; - private long mTimestamp; - private String mSnippetText; - private Uri mPreviewUri; - private String mPreviewContentType; - private long mParticipantContactId; - private String mParticipantLookupKey; - private String mOtherParticipantNormalizedDestination; - private String mSelfId; - private int mParticipantCount; - private boolean mNotificationEnabled; - private String mNotificationSoundUri; - private boolean mNotificationVibrate; - private boolean mIncludeEmailAddress; - private int mMessageStatus; - private int mMessageRawTelephonyStatus; - private boolean mShowDraft; - private Uri mDraftPreviewUri; - private String mDraftPreviewContentType; - private String mDraftSnippetText; - private boolean mIsArchived; - private String mSubject; - private String mDraftSubject; - private String mSnippetSenderFirstName; - private String mSnippetSenderDisplayDestination; - - public ConversationListItemData() { - } - - public void bind(final Cursor cursor) { - bind(cursor, false); - } - - public void bind(final Cursor cursor, final boolean ignoreDraft) { - mConversationId = cursor.getString(INDEX_ID); - mName = cursor.getString(INDEX_CONVERSATION_NAME); - mIcon = cursor.getString(INDEX_CONVERSATION_ICON); - mSnippetText = cursor.getString(INDEX_SNIPPET_TEXT); - mTimestamp = cursor.getLong(INDEX_SORT_TIMESTAMP); - mIsRead = cursor.getInt(INDEX_READ) == 1; - final String previewUriString = cursor.getString(INDEX_PREVIEW_URI); - mPreviewUri = TextUtils.isEmpty(previewUriString) ? null : Uri.parse(previewUriString); - mPreviewContentType = cursor.getString(INDEX_PREVIEW_CONTENT_TYPE); - mParticipantContactId = cursor.getLong(INDEX_PARTICIPANT_CONTACT_ID); - mParticipantLookupKey = cursor.getString(INDEX_PARTICIPANT_LOOKUP_KEY); - mOtherParticipantNormalizedDestination = cursor.getString( - INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION); - mSelfId = cursor.getString(INDEX_SELF_ID); - mParticipantCount = cursor.getInt(INDEX_PARTICIPANT_COUNT); - mNotificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1; - mNotificationSoundUri = cursor.getString(INDEX_NOTIFICATION_SOUND_URI); - mNotificationVibrate = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1; - mIncludeEmailAddress = cursor.getInt(INDEX_INCLUDE_EMAIL_ADDRESS) == 1; - mMessageStatus = cursor.getInt(INDEX_MESSAGE_STATUS); - mMessageRawTelephonyStatus = cursor.getInt(INDEX_MESSAGE_RAW_TELEPHONY_STATUS); - if (!ignoreDraft) { - mShowDraft = cursor.getInt(INDEX_SHOW_DRAFT) == 1; - final String draftPreviewUriString = cursor.getString(INDEX_DRAFT_PREVIEW_URI); - mDraftPreviewUri = TextUtils.isEmpty(draftPreviewUriString) ? - null : Uri.parse(draftPreviewUriString); - mDraftPreviewContentType = cursor.getString(INDEX_DRAFT_PREVIEW_CONTENT_TYPE); - mDraftSnippetText = cursor.getString(INDEX_DRAFT_SNIPPET_TEXT); - mDraftSubject = cursor.getString(INDEX_DRAFT_SUBJECT_TEXT); - } else { - mShowDraft = false; - mDraftPreviewUri = null; - mDraftPreviewContentType = null; - mDraftSnippetText = null; - mDraftSubject = null; - } - - mIsArchived = cursor.getInt(INDEX_ARCHIVE_STATUS) == 1; - mSubject = cursor.getString(INDEX_SUBJECT_TEXT); - mSnippetSenderFirstName = cursor.getString(INDEX_SNIPPET_SENDER_FIRST_NAME); - mSnippetSenderDisplayDestination = - cursor.getString(INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION); - } - - public String getConversationId() { - return mConversationId; - } - - public String getName() { - return mName; - } - - public String getIcon() { - return mIcon; - } - - public boolean getIsRead() { - return mIsRead; - } - - public String getFormattedTimestamp() { - return Dates.getConversationTimeString(mTimestamp).toString(); - } - - public long getTimestamp() { - return mTimestamp; - } - - public String getSnippetText() { - return mSnippetText; - } - - public Uri getPreviewUri() { - return mPreviewUri; - } - - public String getPreviewContentType() { - return mPreviewContentType; - } - - public long getParticipantContactId() { - return mParticipantContactId; - } - - public String getParticipantLookupKey() { - return mParticipantLookupKey; - } - - public String getOtherParticipantNormalizedDestination() { - return mOtherParticipantNormalizedDestination; - } - - public String getSelfId() { - return mSelfId; - } - - public int getParticipantCount() { - return mParticipantCount; - } - - public boolean getIsGroup() { - // Participant count excludes self - return (mParticipantCount > 1); - } - - public boolean getIncludeEmailAddress() { - return mIncludeEmailAddress; - } - - public boolean getNotificationEnabled() { - return mNotificationEnabled; - } - - public String getNotificationSoundUri() { - return mNotificationSoundUri; - } - - public boolean getNotifiationVibrate() { - return mNotificationVibrate; - } - - public final boolean getIsFailedStatus() { - return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED || - mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER || - mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || - mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE); - } - - public final boolean getIsSendRequested() { - return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND || - mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY || - mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_SENDING || - mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_RESENDING); - } - - public boolean getIsMessageTypeOutgoing() { - return !MessageData.getIsIncoming(mMessageStatus); - } - - public int getMessageRawTelephonyStatus() { - return mMessageRawTelephonyStatus; - } - - public int getMessageStatus() { - return mMessageStatus; - } - - public boolean getShowDraft() { - return mShowDraft; - } - - public String getDraftSnippetText() { - return mDraftSnippetText; - } - - public Uri getDraftPreviewUri() { - return mDraftPreviewUri; - } - - public String getDraftPreviewContentType() { - return mDraftPreviewContentType; - } - - public boolean getIsArchived() { - return mIsArchived; - } - - public String getSubject() { - return mSubject; - } - - public String getDraftSubject() { - return mDraftSubject; - } - - public String getSnippetSenderName() { - if (!TextUtils.isEmpty(mSnippetSenderFirstName)) { - return mSnippetSenderFirstName; - } - return mSnippetSenderDisplayDestination; - } - - public void deleteConversation() { - DeleteConversationAction.deleteConversation(mConversationId, mTimestamp); - } - - /** - * Get the name of the view for this data item - */ - public static final String getConversationListView() { - return CONVERSATION_LIST_VIEW; - } - - public static final String getConversationListViewSql() { - return CONVERSATION_LIST_VIEW_SQL; - } - - private static final String CONVERSATION_LIST_VIEW = "conversation_list_view"; - - private static final String CONVERSATION_LIST_VIEW_PROJECTION = - DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns._ID - + " as " + ConversationListViewColumns._ID + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NAME - + " as " + ConversationListViewColumns.NAME + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.CURRENT_SELF_ID - + " as " + ConversationListViewColumns.CURRENT_SELF_ID + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ARCHIVE_STATUS - + " as " + ConversationListViewColumns.ARCHIVE_STATUS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ - + " as " + ConversationListViewColumns.READ + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ICON - + " as " + ConversationListViewColumns.ICON + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_CONTACT_ID - + " as " + ConversationListViewColumns.PARTICIPANT_CONTACT_ID + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_LOOKUP_KEY - + " as " + ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' - + ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION - + " as " + ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SORT_TIMESTAMP - + " as " + ConversationListViewColumns.SORT_TIMESTAMP + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SHOW_DRAFT - + " as " + ConversationListViewColumns.SHOW_DRAFT + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SNIPPET_TEXT - + " as " + ConversationListViewColumns.DRAFT_SNIPPET_TEXT + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_PREVIEW_URI - + " as " + ConversationListViewColumns.DRAFT_PREVIEW_URI + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SUBJECT_TEXT - + " as " + ConversationListViewColumns.DRAFT_SUBJECT_TEXT + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' - + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE - + " as " + ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_URI - + " as " + ConversationListViewColumns.PREVIEW_URI + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_CONTENT_TYPE - + " as " + ConversationListViewColumns.PREVIEW_CONTENT_TYPE + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_COUNT - + " as " + ConversationListViewColumns.PARTICIPANT_COUNT + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_ENABLED - + " as " + ConversationListViewColumns.NOTIFICATION_ENABLED + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_SOUND_URI - + " as " + ConversationListViewColumns.NOTIFICATION_SOUND_URI + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_VIBRATION - + " as " + ConversationListViewColumns.NOTIFICATION_VIBRATION + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + - ConversationColumns.INCLUDE_EMAIL_ADDRESS - + " as " + ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS - + " as " + ConversationListViewColumns.MESSAGE_STATUS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS - + " as " + ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID - + " as " + ConversationListViewColumns.MESSAGE_ID + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME - + " as " + ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION - + " as " + ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION; - - private static final String JOIN_PARTICIPANTS = - " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON (" - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID - + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + DatabaseHelper.ParticipantColumns._ID - + ") "; - - // View that makes latest message read flag available with rest of conversation data. - private static final String CONVERSATION_LIST_VIEW_SQL = "CREATE VIEW " + - CONVERSATION_LIST_VIEW + " AS SELECT " - + CONVERSATION_LIST_VIEW_PROJECTION + ", " - // Snippet not part of the base projection shared with search view - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SNIPPET_TEXT - + " as " + ConversationListViewColumns.SNIPPET_TEXT + ", " - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SUBJECT_TEXT - + " as " + ConversationListViewColumns.SUBJECT_TEXT + " " - + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE - + " LEFT JOIN " + DatabaseHelper.MESSAGES_TABLE + " ON (" - + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.LATEST_MESSAGE_ID - + '=' + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID + ") " - + JOIN_PARTICIPANTS - + "ORDER BY " + DatabaseHelper.CONVERSATIONS_TABLE + '.' - + ConversationColumns.SORT_TIMESTAMP + " DESC"; - - public static class ConversationListViewColumns implements BaseColumns { - public static final String _ID = ConversationColumns._ID; - static final String NAME = ConversationColumns.NAME; - static final String ARCHIVE_STATUS = ConversationColumns.ARCHIVE_STATUS; - static final String READ = MessageColumns.READ; - static final String SORT_TIMESTAMP = ConversationColumns.SORT_TIMESTAMP; - static final String PREVIEW_URI = ConversationColumns.PREVIEW_URI; - static final String PREVIEW_CONTENT_TYPE = ConversationColumns.PREVIEW_CONTENT_TYPE; - static final String SNIPPET_TEXT = ConversationColumns.SNIPPET_TEXT; - static final String SUBJECT_TEXT = ConversationColumns.SUBJECT_TEXT; - static final String ICON = ConversationColumns.ICON; - static final String SHOW_DRAFT = ConversationColumns.SHOW_DRAFT; - static final String DRAFT_SUBJECT_TEXT = ConversationColumns.DRAFT_SUBJECT_TEXT; - static final String DRAFT_PREVIEW_URI = ConversationColumns.DRAFT_PREVIEW_URI; - static final String DRAFT_PREVIEW_CONTENT_TYPE = - ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE; - static final String DRAFT_SNIPPET_TEXT = ConversationColumns.DRAFT_SNIPPET_TEXT; - static final String PARTICIPANT_CONTACT_ID = ConversationColumns.PARTICIPANT_CONTACT_ID; - static final String PARTICIPANT_LOOKUP_KEY = ConversationColumns.PARTICIPANT_LOOKUP_KEY; - static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION = - ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION; - static final String CURRENT_SELF_ID = ConversationColumns.CURRENT_SELF_ID; - static final String PARTICIPANT_COUNT = ConversationColumns.PARTICIPANT_COUNT; - static final String NOTIFICATION_ENABLED = ConversationColumns.NOTIFICATION_ENABLED; - static final String NOTIFICATION_SOUND_URI = ConversationColumns.NOTIFICATION_SOUND_URI; - static final String NOTIFICATION_VIBRATION = ConversationColumns.NOTIFICATION_VIBRATION; - static final String INCLUDE_EMAIL_ADDRESS = - ConversationColumns.INCLUDE_EMAIL_ADDRESS; - static final String MESSAGE_STATUS = MessageColumns.STATUS; - static final String MESSAGE_RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS; - static final String MESSAGE_ID = "message_id"; - static final String SNIPPET_SENDER_FIRST_NAME = "snippet_sender_first_name"; - static final String SNIPPET_SENDER_DISPLAY_DESTINATION = - "snippet_sender_display_destination"; - } - - public static final String[] PROJECTION = { - ConversationListViewColumns._ID, - ConversationListViewColumns.NAME, - ConversationListViewColumns.ICON, - ConversationListViewColumns.SNIPPET_TEXT, - ConversationListViewColumns.SORT_TIMESTAMP, - ConversationListViewColumns.READ, - ConversationListViewColumns.PREVIEW_URI, - ConversationListViewColumns.PREVIEW_CONTENT_TYPE, - ConversationListViewColumns.PARTICIPANT_CONTACT_ID, - ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY, - ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION, - ConversationListViewColumns.PARTICIPANT_COUNT, - ConversationListViewColumns.CURRENT_SELF_ID, - ConversationListViewColumns.NOTIFICATION_ENABLED, - ConversationListViewColumns.NOTIFICATION_SOUND_URI, - ConversationListViewColumns.NOTIFICATION_VIBRATION, - ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS, - ConversationListViewColumns.MESSAGE_STATUS, - ConversationListViewColumns.SHOW_DRAFT, - ConversationListViewColumns.DRAFT_PREVIEW_URI, - ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE, - ConversationListViewColumns.DRAFT_SNIPPET_TEXT, - ConversationListViewColumns.ARCHIVE_STATUS, - ConversationListViewColumns.MESSAGE_ID, - ConversationListViewColumns.SUBJECT_TEXT, - ConversationListViewColumns.DRAFT_SUBJECT_TEXT, - ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS, - ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME, - ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION, - }; - - private static final int INDEX_ID = 0; - private static final int INDEX_CONVERSATION_NAME = 1; - private static final int INDEX_CONVERSATION_ICON = 2; - private static final int INDEX_SNIPPET_TEXT = 3; - private static final int INDEX_SORT_TIMESTAMP = 4; - private static final int INDEX_READ = 5; - private static final int INDEX_PREVIEW_URI = 6; - private static final int INDEX_PREVIEW_CONTENT_TYPE = 7; - private static final int INDEX_PARTICIPANT_CONTACT_ID = 8; - private static final int INDEX_PARTICIPANT_LOOKUP_KEY = 9; - private static final int INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION = 10; - private static final int INDEX_PARTICIPANT_COUNT = 11; - private static final int INDEX_SELF_ID = 12; - private static final int INDEX_NOTIFICATION_ENABLED = 13; - private static final int INDEX_NOTIFICATION_SOUND_URI = 14; - private static final int INDEX_NOTIFICATION_VIBRATION = 15; - private static final int INDEX_INCLUDE_EMAIL_ADDRESS = 16; - private static final int INDEX_MESSAGE_STATUS = 17; - private static final int INDEX_SHOW_DRAFT = 18; - private static final int INDEX_DRAFT_PREVIEW_URI = 19; - private static final int INDEX_DRAFT_PREVIEW_CONTENT_TYPE = 20; - private static final int INDEX_DRAFT_SNIPPET_TEXT = 21; - private static final int INDEX_ARCHIVE_STATUS = 22; - private static final int INDEX_MESSAGE_ID = 23; - private static final int INDEX_SUBJECT_TEXT = 24; - private static final int INDEX_DRAFT_SUBJECT_TEXT = 25; - private static final int INDEX_MESSAGE_RAW_TELEPHONY_STATUS = 26; - private static final int INDEX_SNIPPET_SENDER_FIRST_NAME = 27; - private static final int INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION = 28; - - private static final String DIVIDER_TEXT = ", "; - - /** - * Get a conversation from the local DB based on the conversation id. - * - * @param dbWrapper The database - * @param conversationId The conversation Id to read - * @return The existing conversation or null - */ - public static ConversationListItemData getExistingConversation(final DatabaseWrapper dbWrapper, - final String conversationId) { - ConversationListItemData conversation = null; - - // Look for an existing conversation in the db with this conversation id - Cursor cursor = null; - try { - // TODO: Should we be able to read a row from just the conversation table? - cursor = dbWrapper.query(getConversationListView(), - PROJECTION, - ConversationColumns._ID + "=?", - new String[] { conversationId }, - null, null, null); - Assert.inRange(cursor.getCount(), 0, 1); - if (cursor.moveToFirst()) { - conversation = new ConversationListItemData(); - conversation.bind(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return conversation; - } - - public static String generateConversationName(final List - participants) { - if (participants.size() == 1) { - // Prefer full name over first name for 1:1 conversation - return participants.get(0).getDisplayName(true); - } - - final ArrayList participantNames = new ArrayList(); - for (final ParticipantData participant : participants) { - // Prefer first name over full name for group conversation - participantNames.add(participant.getDisplayName(false)); - } - - final Joiner joiner = Joiner.on(DIVIDER_TEXT).skipNulls(); - return joiner.join(participantNames); - } - -} diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java b/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java deleted file mode 100644 index f329f46..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.text.TextUtils; - -/** - * Holds data for conversation message bubble which keeps track of whether it's been bound to - * a new message. - */ -public class ConversationMessageBubbleData { - private String mMessageId; - - /** - * Binds to ConversationMessageData instance. - * @return true if we are binding to a different message, false if we are binding to the - * same message (e.g. in order to update the status text) - */ - public boolean bind(final ConversationMessageData data) { - final boolean changed = !TextUtils.equals(mMessageId, data.getMessageId()); - mMessageId = data.getMessageId(); - return changed; - } -} diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageData.java b/src/com/android/messaging/datamodel/data/ConversationMessageData.java deleted file mode 100644 index 19e1b97..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationMessageData.java +++ /dev/null @@ -1,917 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.ContactsContract; -import android.text.TextUtils; -import android.text.format.DateUtils; - -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.Dates; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Predicate; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * Class representing a message within a conversation sequence. The message parts - * are available via the getParts() method. - * - * TODO: See if we can delegate to MessageData for the logic that this class duplicates - * (e.g. getIsMms). - */ -public class ConversationMessageData { - private static final String TAG = LogUtil.BUGLE_TAG; - - private String mMessageId; - private String mConversationId; - private String mParticipantId; - private int mPartsCount; - private List mParts; - private long mSentTimestamp; - private long mReceivedTimestamp; - private boolean mSeen; - private boolean mRead; - private int mProtocol; - private int mStatus; - private String mSmsMessageUri; - private int mSmsPriority; - private int mSmsMessageSize; - private String mMmsSubject; - private long mMmsExpiry; - private int mRawTelephonyStatus; - private String mSenderFullName; - private String mSenderFirstName; - private String mSenderDisplayDestination; - private String mSenderNormalizedDestination; - private String mSenderProfilePhotoUri; - private long mSenderContactId; - private String mSenderContactLookupKey; - private String mSelfParticipantId; - - /** Are we similar enough to the previous/next messages that we can cluster them? */ - private boolean mCanClusterWithPreviousMessage; - private boolean mCanClusterWithNextMessage; - - public ConversationMessageData() { - } - - public void bind(final Cursor cursor) { - mMessageId = cursor.getString(INDEX_MESSAGE_ID); - mConversationId = cursor.getString(INDEX_CONVERSATION_ID); - mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID); - mPartsCount = cursor.getInt(INDEX_PARTS_COUNT); - - mParts = makeParts( - cursor.getString(INDEX_PARTS_IDS), - cursor.getString(INDEX_PARTS_CONTENT_TYPES), - cursor.getString(INDEX_PARTS_CONTENT_URIS), - cursor.getString(INDEX_PARTS_WIDTHS), - cursor.getString(INDEX_PARTS_HEIGHTS), - cursor.getString(INDEX_PARTS_TEXTS), - mPartsCount, - mMessageId); - - mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP); - mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP); - mSeen = (cursor.getInt(INDEX_SEEN) != 0); - mRead = (cursor.getInt(INDEX_READ) != 0); - mProtocol = cursor.getInt(INDEX_PROTOCOL); - mStatus = cursor.getInt(INDEX_STATUS); - mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI); - mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY); - mSmsMessageSize = cursor.getInt(INDEX_SMS_MESSAGE_SIZE); - mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT); - mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY); - mRawTelephonyStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS); - mSenderFullName = cursor.getString(INDEX_SENDER_FULL_NAME); - mSenderFirstName = cursor.getString(INDEX_SENDER_FIRST_NAME); - mSenderDisplayDestination = cursor.getString(INDEX_SENDER_DISPLAY_DESTINATION); - mSenderNormalizedDestination = cursor.getString(INDEX_SENDER_NORMALIZED_DESTINATION); - mSenderProfilePhotoUri = cursor.getString(INDEX_SENDER_PROFILE_PHOTO_URI); - mSenderContactId = cursor.getLong(INDEX_SENDER_CONTACT_ID); - mSenderContactLookupKey = cursor.getString(INDEX_SENDER_CONTACT_LOOKUP_KEY); - mSelfParticipantId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID); - - if (!cursor.isFirst() && cursor.moveToPrevious()) { - mCanClusterWithPreviousMessage = canClusterWithMessage(cursor); - cursor.moveToNext(); - } else { - mCanClusterWithPreviousMessage = false; - } - if (!cursor.isLast() && cursor.moveToNext()) { - mCanClusterWithNextMessage = canClusterWithMessage(cursor); - cursor.moveToPrevious(); - } else { - mCanClusterWithNextMessage = false; - } - } - - private boolean canClusterWithMessage(final Cursor cursor) { - final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID); - if (!TextUtils.equals(getParticipantId(), otherParticipantId)) { - return false; - } - final int otherStatus = cursor.getInt(INDEX_STATUS); - final boolean otherIsIncoming = (otherStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING); - if (getIsIncoming() != otherIsIncoming) { - return false; - } - final long otherReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP); - final long timestampDeltaMillis = Math.abs(mReceivedTimestamp - otherReceivedTimestamp); - if (timestampDeltaMillis > DateUtils.MINUTE_IN_MILLIS) { - return false; - } - final String otherSelfId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID); - if (!TextUtils.equals(getSelfParticipantId(), otherSelfId)) { - return false; - } - return true; - } - - private static final Character QUOTE_CHAR = '\''; - private static final char DIVIDER = '|'; - - // statics to avoid unnecessary object allocation - private static final StringBuilder sUnquoteStringBuilder = new StringBuilder(); - private static final ArrayList sUnquoteResults = new ArrayList(); - - // this lock is used to guard access to the above statics - private static final Object sUnquoteLock = new Object(); - - private static void addResult(final ArrayList results, final StringBuilder value) { - if (value.length() > 0) { - results.add(value.toString()); - } else { - results.add(EMPTY_STRING); - } - } - - @VisibleForTesting - static String[] splitUnquotedString(final String inputString) { - if (TextUtils.isEmpty(inputString)) { - return new String[0]; - } - - return inputString.split("\\" + DIVIDER); - } - - /** - * Takes a group-concated and quoted string and decomposes it into its constituent - * parts. A quoted string starts and ends with a single quote. Actual single quotes - * within the string are escaped using a second single quote. So, for example, an - * input string with 3 constituent parts might look like this: - * - * 'now is the time'|'I can''t do it'|'foo' - * - * This would be returned as an array of 3 strings as follows: - * now is the time - * I can't do it - * foo - * - * This is achieved by walking through the inputString, character by character, - * ignoring the outer quotes and the divider and replacing any pair of consecutive - * single quotes with a single single quote. - * - * @param inputString - * @return array of constituent strings - */ - @VisibleForTesting - static String[] splitQuotedString(final String inputString) { - if (TextUtils.isEmpty(inputString)) { - return new String[0]; - } - - // this method can be called from multiple threads but it uses a static - // string builder - synchronized (sUnquoteLock) { - final int length = inputString.length(); - final ArrayList results = sUnquoteResults; - results.clear(); - - int characterPos = -1; - while (++characterPos < length) { - final char mustBeQuote = inputString.charAt(characterPos); - Assert.isTrue(QUOTE_CHAR == mustBeQuote); - while (++characterPos < length) { - final char currentChar = inputString.charAt(characterPos); - if (currentChar == QUOTE_CHAR) { - final char peekAhead = characterPos < length - 1 - ? inputString.charAt(characterPos + 1) : 0; - - if (peekAhead == QUOTE_CHAR) { - characterPos += 1; // skip the second quote - } else { - addResult(results, sUnquoteStringBuilder); - sUnquoteStringBuilder.setLength(0); - - Assert.isTrue((peekAhead == DIVIDER) || (peekAhead == (char) 0)); - characterPos += 1; // skip the divider - break; - } - } - sUnquoteStringBuilder.append(currentChar); - } - } - return results.toArray(new String[results.size()]); - } - } - - static MessagePartData makePartData( - final String partId, - final String contentType, - final String contentUriString, - final String contentWidth, - final String contentHeight, - final String text, - final String messageId) { - if (ContentType.isTextType(contentType)) { - final MessagePartData textPart = MessagePartData.createTextMessagePart(text); - textPart.updatePartId(partId); - textPart.updateMessageId(messageId); - return textPart; - } else { - final Uri contentUri = Uri.parse(contentUriString); - final int width = Integer.parseInt(contentWidth); - final int height = Integer.parseInt(contentHeight); - final MessagePartData attachmentPart = MessagePartData.createMediaMessagePart( - contentType, contentUri, width, height); - attachmentPart.updatePartId(partId); - attachmentPart.updateMessageId(messageId); - return attachmentPart; - } - } - - @VisibleForTesting - static List makeParts( - final String rawIds, - final String rawContentTypes, - final String rawContentUris, - final String rawWidths, - final String rawHeights, - final String rawTexts, - final int partsCount, - final String messageId) { - final List parts = new LinkedList(); - if (partsCount == 1) { - parts.add(makePartData( - rawIds, - rawContentTypes, - rawContentUris, - rawWidths, - rawHeights, - rawTexts, - messageId)); - } else { - unpackMessageParts( - parts, - splitUnquotedString(rawIds), - splitQuotedString(rawContentTypes), - splitQuotedString(rawContentUris), - splitUnquotedString(rawWidths), - splitUnquotedString(rawHeights), - splitQuotedString(rawTexts), - partsCount, - messageId); - } - return parts; - } - - @VisibleForTesting - static void unpackMessageParts( - final List parts, - final String[] ids, - final String[] contentTypes, - final String[] contentUris, - final String[] contentWidths, - final String[] contentHeights, - final String[] texts, - final int partsCount, - final String messageId) { - - Assert.equals(partsCount, ids.length); - Assert.equals(partsCount, contentTypes.length); - Assert.equals(partsCount, contentUris.length); - Assert.equals(partsCount, contentWidths.length); - Assert.equals(partsCount, contentHeights.length); - Assert.equals(partsCount, texts.length); - - for (int i = 0; i < partsCount; i++) { - parts.add(makePartData( - ids[i], - contentTypes[i], - contentUris[i], - contentWidths[i], - contentHeights[i], - texts[i], - messageId)); - } - - if (parts.size() != partsCount) { - LogUtil.wtf(TAG, "Only unpacked " + parts.size() + " parts from message (id=" - + messageId + "), expected " + partsCount + " parts"); - } - } - - public final String getMessageId() { - return mMessageId; - } - - public final String getConversationId() { - return mConversationId; - } - - public final String getParticipantId() { - return mParticipantId; - } - - public List getParts() { - return mParts; - } - - public boolean hasText() { - for (final MessagePartData part : mParts) { - if (part.isText()) { - return true; - } - } - return false; - } - - /** - * Get a concatenation of all text parts - * - * @return the text that is a concatenation of all text parts - */ - public String getText() { - // This is optimized for single text part case, which is the majority - - // For single text part, we just return the part without creating the StringBuilder - String firstTextPart = null; - boolean foundText = false; - // For multiple text parts, we need the StringBuilder and the separator for concatenation - StringBuilder sb = null; - String separator = null; - for (final MessagePartData part : mParts) { - if (part.isText()) { - if (!foundText) { - // First text part - firstTextPart = part.getText(); - foundText = true; - } else { - // Second and beyond - if (sb == null) { - // Need the StringBuilder and the separator starting from 2nd text part - sb = new StringBuilder(); - if (!TextUtils.isEmpty(firstTextPart)) { - sb.append(firstTextPart); - } - separator = BugleGservices.get().getString( - BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR, - BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT); - } - final String partText = part.getText(); - if (!TextUtils.isEmpty(partText)) { - if (!TextUtils.isEmpty(separator) && sb.length() > 0) { - sb.append(separator); - } - sb.append(partText); - } - } - } - } - if (sb == null) { - // Only one text part - return firstTextPart; - } else { - // More than one - return sb.toString(); - } - } - - public boolean hasAttachments() { - for (final MessagePartData part : mParts) { - if (part.isAttachment()) { - return true; - } - } - return false; - } - - public List getAttachments() { - return getAttachments(null); - } - - public List getAttachments(final Predicate filter) { - if (mParts.isEmpty()) { - return Collections.emptyList(); - } - final List attachmentParts = new LinkedList<>(); - for (final MessagePartData part : mParts) { - if (part.isAttachment()) { - if (filter == null || filter.apply(part)) { - attachmentParts.add(part); - } - } - } - return attachmentParts; - } - - public final long getSentTimeStamp() { - return mSentTimestamp; - } - - public final long getReceivedTimeStamp() { - return mReceivedTimestamp; - } - - public final String getFormattedReceivedTimeStamp() { - return Dates.getMessageTimeString(mReceivedTimestamp).toString(); - } - - public final boolean getIsSeen() { - return mSeen; - } - - public final boolean getIsRead() { - return mRead; - } - - public final boolean getIsMms() { - return (mProtocol == MessageData.PROTOCOL_MMS || - mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION); - } - - public final boolean getIsMmsNotification() { - return (mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION); - } - - public final boolean getIsSms() { - return mProtocol == (MessageData.PROTOCOL_SMS); - } - - final int getProtocol() { - return mProtocol; - } - - public final int getStatus() { - return mStatus; - } - - public final String getSmsMessageUri() { - return mSmsMessageUri; - } - - public final int getSmsPriority() { - return mSmsPriority; - } - - public final int getSmsMessageSize() { - return mSmsMessageSize; - } - - public final String getMmsSubject() { - return mMmsSubject; - } - - public final long getMmsExpiry() { - return mMmsExpiry; - } - - public final int getRawTelephonyStatus() { - return mRawTelephonyStatus; - } - - public final String getSelfParticipantId() { - return mSelfParticipantId; - } - - public boolean getIsIncoming() { - return (mStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING); - } - - public boolean hasIncomingErrorStatus() { - return (mStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE || - mStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED); - } - - public boolean getIsSendComplete() { - return mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE; - } - - public String getSenderFullName() { - return mSenderFullName; - } - - public String getSenderFirstName() { - return mSenderFirstName; - } - - public String getSenderDisplayDestination() { - return mSenderDisplayDestination; - } - - public String getSenderNormalizedDestination() { - return mSenderNormalizedDestination; - } - - public Uri getSenderProfilePhotoUri() { - return mSenderProfilePhotoUri == null ? null : Uri.parse(mSenderProfilePhotoUri); - } - - public long getSenderContactId() { - return mSenderContactId; - } - - public String getSenderDisplayName() { - if (!TextUtils.isEmpty(mSenderFullName)) { - return mSenderFullName; - } - if (!TextUtils.isEmpty(mSenderFirstName)) { - return mSenderFirstName; - } - return mSenderDisplayDestination; - } - - public String getSenderContactLookupKey() { - return mSenderContactLookupKey; - } - - public boolean getShowDownloadMessage() { - return MessageData.getShowDownloadMessage(mStatus); - } - - public boolean getShowResendMessage() { - return MessageData.getShowResendMessage(mStatus); - } - - public boolean getCanForwardMessage() { - // Even for outgoing messages, we only allow forwarding if the message has finished sending - // as media often has issues when send isn't complete - return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE || - mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE); - } - - public boolean getCanCopyMessageToClipboard() { - return (hasText() && - (!getIsIncoming() || mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE)); - } - - public boolean getOneClickResendMessage() { - return MessageData.getOneClickResendMessage(mStatus, mRawTelephonyStatus); - } - - /** - * Get sender's lookup uri. - * This method doesn't support corp contacts. - * - * @return Lookup uri of sender's contact - */ - public Uri getSenderContactLookupUri() { - if (mSenderContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED - && !TextUtils.isEmpty(mSenderContactLookupKey)) { - return ContactsContract.Contacts.getLookupUri(mSenderContactId, - mSenderContactLookupKey); - } - return null; - } - - public boolean getCanClusterWithPreviousMessage() { - return mCanClusterWithPreviousMessage; - } - - public boolean getCanClusterWithNextMessage() { - return mCanClusterWithNextMessage; - } - - @Override - public String toString() { - return MessageData.toString(mMessageId, mParts); - } - - // Data definitions - - public static final String getConversationMessagesQuerySql() { - return CONVERSATION_MESSAGES_QUERY_SQL - + " AND " - // Inject the conversation id - + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)" - + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY; - } - - static final String getConversationMessageIdsQuerySql() { - return CONVERSATION_MESSAGES_IDS_QUERY_SQL - + " AND " - // Inject the conversation id - + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)" - + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY; - } - - public static final String getNotificationQuerySql() { - return CONVERSATION_MESSAGES_QUERY_SQL - + " AND " - + "(" + DatabaseHelper.MessageColumns.STATUS + " in (" - + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", " - + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")" - + " AND " - + DatabaseHelper.MessageColumns.SEEN + " = 0)" - + ")" - + NOTIFICATION_QUERY_SQL_GROUP_BY; - } - - public static final String getWearableQuerySql() { - return CONVERSATION_MESSAGES_QUERY_SQL - + " AND " - + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?" - + " AND " - + DatabaseHelper.MessageColumns.STATUS + " IN (" - + MessageData.BUGLE_STATUS_OUTGOING_DELIVERED + ", " - + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE + ", " - + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ", " - + MessageData.BUGLE_STATUS_OUTGOING_SENDING + ", " - + MessageData.BUGLE_STATUS_OUTGOING_RESENDING + ", " - + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ", " - + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", " - + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")" - + ")" - + NOTIFICATION_QUERY_SQL_GROUP_BY; - } - - /* - * Generate a sqlite snippet to call the quote function on the columnName argument. - * The columnName doesn't strictly have to be a column name (e.g. it could be an - * expression). - */ - private static String quote(final String columnName) { - return "quote(" + columnName + ")"; - } - - private static String makeGroupConcatString(final String column) { - return "group_concat(" + column + ", '" + DIVIDER + "')"; - } - - private static String makeIfNullString(final String column) { - return "ifnull(" + column + "," + "''" + ")"; - } - - private static String makePartsTableColumnString(final String column) { - return DatabaseHelper.PARTS_TABLE + '.' + column; - } - - private static String makeCaseWhenString(final String column, - final boolean quote, - final String asColumn) { - final String fullColumn = makeIfNullString(makePartsTableColumnString(column)); - final String groupConcatTerm = quote - ? makeGroupConcatString(quote(fullColumn)) - : makeGroupConcatString(fullColumn); - return "CASE WHEN (" + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT + ">1) THEN " + groupConcatTerm - + " ELSE " + makePartsTableColumnString(column) + " END AS " + asColumn; - } - - private static final String CONVERSATION_MESSAGE_VIEW_PARTS_COUNT = - "count(" + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + ")"; - - private static final String EMPTY_STRING = ""; - - private static final String CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL = - DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID - + " as " + ConversationMessageViewColumns._ID + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID - + " as " + ConversationMessageViewColumns.CONVERSATION_ID + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID - + " as " + ConversationMessageViewColumns.PARTICIPANT_ID + ", " - - + makeCaseWhenString(PartColumns._ID, false, - ConversationMessageViewColumns.PARTS_IDS) + ", " - + makeCaseWhenString(PartColumns.CONTENT_TYPE, true, - ConversationMessageViewColumns.PARTS_CONTENT_TYPES) + ", " - + makeCaseWhenString(PartColumns.CONTENT_URI, true, - ConversationMessageViewColumns.PARTS_CONTENT_URIS) + ", " - + makeCaseWhenString(PartColumns.WIDTH, false, - ConversationMessageViewColumns.PARTS_WIDTHS) + ", " - + makeCaseWhenString(PartColumns.HEIGHT, false, - ConversationMessageViewColumns.PARTS_HEIGHTS) + ", " - + makeCaseWhenString(PartColumns.TEXT, true, - ConversationMessageViewColumns.PARTS_TEXTS) + ", " - - + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT - + " as " + ConversationMessageViewColumns.PARTS_COUNT + ", " - - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENT_TIMESTAMP - + " as " + ConversationMessageViewColumns.SENT_TIMESTAMP + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP - + " as " + ConversationMessageViewColumns.RECEIVED_TIMESTAMP + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SEEN - + " as " + ConversationMessageViewColumns.SEEN + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ - + " as " + ConversationMessageViewColumns.READ + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROTOCOL - + " as " + ConversationMessageViewColumns.PROTOCOL + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS - + " as " + ConversationMessageViewColumns.STATUS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_URI - + " as " + ConversationMessageViewColumns.SMS_MESSAGE_URI + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_PRIORITY - + " as " + ConversationMessageViewColumns.SMS_PRIORITY + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_SIZE - + " as " + ConversationMessageViewColumns.SMS_MESSAGE_SIZE + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_SUBJECT - + " as " + ConversationMessageViewColumns.MMS_SUBJECT + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_EXPIRY - + " as " + ConversationMessageViewColumns.MMS_EXPIRY + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS - + " as " + ConversationMessageViewColumns.RAW_TELEPHONY_STATUS + ", " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SELF_PARTICIPANT_ID - + " as " + ConversationMessageViewColumns.SELF_PARTICIPANT_ID + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME - + " as " + ConversationMessageViewColumns.SENDER_FULL_NAME + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME - + " as " + ConversationMessageViewColumns.SENDER_FIRST_NAME + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION - + " as " + ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.NORMALIZED_DESTINATION - + " as " + ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.PROFILE_PHOTO_URI - + " as " + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID - + " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", " - + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY - + " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " "; - - private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL = - " FROM " + DatabaseHelper.MESSAGES_TABLE - + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE - + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID - + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") " - + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE - + " ON (" + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID - + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")" - // Exclude draft messages from main view - + " WHERE (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.STATUS - + " <> " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT; - - // This query is mostly static, except for the injection of conversation id. This is for - // performance reasons, to ensure that the query uses indices and does not trigger full scans - // of the messages table. See b/17160946 for more details. - private static final String CONVERSATION_MESSAGES_QUERY_SQL = "SELECT " - + CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL - + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL; - - private static final String CONVERSATION_MESSAGE_IDS_PROJECTION_SQL = - DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID - + " as " + ConversationMessageViewColumns._ID + " "; - - private static final String CONVERSATION_MESSAGES_IDS_QUERY_SQL = "SELECT " - + CONVERSATION_MESSAGE_IDS_PROJECTION_SQL - + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL; - - // Note that we sort DESC and ConversationData reverses the cursor. This is a performance - // issue (improvement) for large cursors. - private static final String CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY = - " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID - + " ORDER BY " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC"; - - private static final String NOTIFICATION_QUERY_SQL_GROUP_BY = - " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID - + " ORDER BY " - + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC"; - - interface ConversationMessageViewColumns extends BaseColumns { - static final String _ID = MessageColumns._ID; - static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID; - static final String PARTICIPANT_ID = MessageColumns.SENDER_PARTICIPANT_ID; - static final String PARTS_COUNT = "parts_count"; - static final String SENT_TIMESTAMP = MessageColumns.SENT_TIMESTAMP; - static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP; - static final String SEEN = MessageColumns.SEEN; - static final String READ = MessageColumns.READ; - static final String PROTOCOL = MessageColumns.PROTOCOL; - static final String STATUS = MessageColumns.STATUS; - static final String SMS_MESSAGE_URI = MessageColumns.SMS_MESSAGE_URI; - static final String SMS_PRIORITY = MessageColumns.SMS_PRIORITY; - static final String SMS_MESSAGE_SIZE = MessageColumns.SMS_MESSAGE_SIZE; - static final String MMS_SUBJECT = MessageColumns.MMS_SUBJECT; - static final String MMS_EXPIRY = MessageColumns.MMS_EXPIRY; - static final String RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS; - static final String SELF_PARTICIPANT_ID = MessageColumns.SELF_PARTICIPANT_ID; - static final String SENDER_FULL_NAME = ParticipantColumns.FULL_NAME; - static final String SENDER_FIRST_NAME = ParticipantColumns.FIRST_NAME; - static final String SENDER_DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION; - static final String SENDER_NORMALIZED_DESTINATION = - ParticipantColumns.NORMALIZED_DESTINATION; - static final String SENDER_PROFILE_PHOTO_URI = ParticipantColumns.PROFILE_PHOTO_URI; - static final String SENDER_CONTACT_ID = ParticipantColumns.CONTACT_ID; - static final String SENDER_CONTACT_LOOKUP_KEY = ParticipantColumns.LOOKUP_KEY; - static final String PARTS_IDS = "parts_ids"; - static final String PARTS_CONTENT_TYPES = "parts_content_types"; - static final String PARTS_CONTENT_URIS = "parts_content_uris"; - static final String PARTS_WIDTHS = "parts_widths"; - static final String PARTS_HEIGHTS = "parts_heights"; - static final String PARTS_TEXTS = "parts_texts"; - } - - private static int sIndexIncrementer = 0; - - private static final int INDEX_MESSAGE_ID = sIndexIncrementer++; - private static final int INDEX_CONVERSATION_ID = sIndexIncrementer++; - private static final int INDEX_PARTICIPANT_ID = sIndexIncrementer++; - - private static final int INDEX_PARTS_IDS = sIndexIncrementer++; - private static final int INDEX_PARTS_CONTENT_TYPES = sIndexIncrementer++; - private static final int INDEX_PARTS_CONTENT_URIS = sIndexIncrementer++; - private static final int INDEX_PARTS_WIDTHS = sIndexIncrementer++; - private static final int INDEX_PARTS_HEIGHTS = sIndexIncrementer++; - private static final int INDEX_PARTS_TEXTS = sIndexIncrementer++; - - private static final int INDEX_PARTS_COUNT = sIndexIncrementer++; - - private static final int INDEX_SENT_TIMESTAMP = sIndexIncrementer++; - private static final int INDEX_RECEIVED_TIMESTAMP = sIndexIncrementer++; - private static final int INDEX_SEEN = sIndexIncrementer++; - private static final int INDEX_READ = sIndexIncrementer++; - private static final int INDEX_PROTOCOL = sIndexIncrementer++; - private static final int INDEX_STATUS = sIndexIncrementer++; - private static final int INDEX_SMS_MESSAGE_URI = sIndexIncrementer++; - private static final int INDEX_SMS_PRIORITY = sIndexIncrementer++; - private static final int INDEX_SMS_MESSAGE_SIZE = sIndexIncrementer++; - private static final int INDEX_MMS_SUBJECT = sIndexIncrementer++; - private static final int INDEX_MMS_EXPIRY = sIndexIncrementer++; - private static final int INDEX_RAW_TELEPHONY_STATUS = sIndexIncrementer++; - private static final int INDEX_SELF_PARTICIPIANT_ID = sIndexIncrementer++; - private static final int INDEX_SENDER_FULL_NAME = sIndexIncrementer++; - private static final int INDEX_SENDER_FIRST_NAME = sIndexIncrementer++; - private static final int INDEX_SENDER_DISPLAY_DESTINATION = sIndexIncrementer++; - private static final int INDEX_SENDER_NORMALIZED_DESTINATION = sIndexIncrementer++; - private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++; - private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++; - private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++; - - - private static String[] sProjection = { - ConversationMessageViewColumns._ID, - ConversationMessageViewColumns.CONVERSATION_ID, - ConversationMessageViewColumns.PARTICIPANT_ID, - - ConversationMessageViewColumns.PARTS_IDS, - ConversationMessageViewColumns.PARTS_CONTENT_TYPES, - ConversationMessageViewColumns.PARTS_CONTENT_URIS, - ConversationMessageViewColumns.PARTS_WIDTHS, - ConversationMessageViewColumns.PARTS_HEIGHTS, - ConversationMessageViewColumns.PARTS_TEXTS, - - ConversationMessageViewColumns.PARTS_COUNT, - ConversationMessageViewColumns.SENT_TIMESTAMP, - ConversationMessageViewColumns.RECEIVED_TIMESTAMP, - ConversationMessageViewColumns.SEEN, - ConversationMessageViewColumns.READ, - ConversationMessageViewColumns.PROTOCOL, - ConversationMessageViewColumns.STATUS, - ConversationMessageViewColumns.SMS_MESSAGE_URI, - ConversationMessageViewColumns.SMS_PRIORITY, - ConversationMessageViewColumns.SMS_MESSAGE_SIZE, - ConversationMessageViewColumns.MMS_SUBJECT, - ConversationMessageViewColumns.MMS_EXPIRY, - ConversationMessageViewColumns.RAW_TELEPHONY_STATUS, - ConversationMessageViewColumns.SELF_PARTICIPANT_ID, - ConversationMessageViewColumns.SENDER_FULL_NAME, - ConversationMessageViewColumns.SENDER_FIRST_NAME, - ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION, - ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION, - ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, - ConversationMessageViewColumns.SENDER_CONTACT_ID, - ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY, - }; - - public static String[] getProjection() { - return sProjection; - } -} diff --git a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java deleted file mode 100644 index 0b5ef51..0000000 --- a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.support.v4.util.SimpleArrayMap; - -import com.google.common.annotations.VisibleForTesting; - -import junit.framework.Assert; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * A class that contains the list of all participants potentially involved in a conversation. - * Includes both the participant records for each participant referenced in conversation - * participants table (i.e. "other" phone numbers) plus all participants representing self - * (i.e. one per sim recorded in the subscription manager db). - */ -public class ConversationParticipantsData implements Iterable { - // A map from a participant id to a participant - private final SimpleArrayMap mConversationParticipantsMap; - private int mParticipantCountExcludingSelf = 0; - - public ConversationParticipantsData() { - mConversationParticipantsMap = new SimpleArrayMap(); - } - - public void bind(final Cursor cursor) { - mConversationParticipantsMap.clear(); - mParticipantCountExcludingSelf = 0; - if (cursor != null) { - while (cursor.moveToNext()) { - final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor); - if (!newParticipant.isSelf()) { - mParticipantCountExcludingSelf++; - } - mConversationParticipantsMap.put(newParticipant.getId(), newParticipant); - } - } - } - - @VisibleForTesting - ParticipantData getParticipantById(final String participantId) { - return mConversationParticipantsMap.get(participantId); - } - - ArrayList getParticipantListExcludingSelf() { - final ArrayList retList = - new ArrayList(mConversationParticipantsMap.size()); - for (int i = 0; i < mConversationParticipantsMap.size(); i++) { - final ParticipantData participant = mConversationParticipantsMap.valueAt(i); - if (!participant.isSelf()) { - retList.add(participant); - } - } - return retList; - } - - /** - * For a 1:1 conversation return the other (not self) participant - */ - public ParticipantData getOtherParticipant() { - if (mParticipantCountExcludingSelf == 1) { - for (int i = 0; i < mConversationParticipantsMap.size(); i++) { - final ParticipantData participant = mConversationParticipantsMap.valueAt(i); - if (!participant.isSelf()) { - return participant; - } - } - Assert.fail(); - } - return null; - } - - public int getNumberOfParticipantsExcludingSelf() { - return mParticipantCountExcludingSelf; - } - - public boolean isLoaded() { - return !mConversationParticipantsMap.isEmpty(); - } - - @Override - public Iterator iterator() { - return new Iterator() { - private int mCurrentIndex = -1; - - @Override - public boolean hasNext() { - return mCurrentIndex < mConversationParticipantsMap.size() - 1; - } - - @Override - public ParticipantData next() { - mCurrentIndex++; - if (mCurrentIndex >= mConversationParticipantsMap.size()) { - throw new NoSuchElementException(); - } - return mConversationParticipantsMap.valueAt(mCurrentIndex); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } -} diff --git a/src/com/android/messaging/datamodel/data/DraftMessageData.java b/src/com/android/messaging/datamodel/data/DraftMessageData.java deleted file mode 100644 index 7a7199a..0000000 --- a/src/com/android/messaging/datamodel/data/DraftMessageData.java +++ /dev/null @@ -1,855 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.net.Uri; -import android.text.TextUtils; - -import com.android.messaging.datamodel.MessageTextStats; -import com.android.messaging.datamodel.action.ReadDraftDataAction; -import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener; -import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionMonitor; -import com.android.messaging.datamodel.action.WriteDraftMessageAction; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -public class DraftMessageData extends BindableData implements ReadDraftDataActionListener { - - /** - * Interface for DraftMessageData listeners - */ - public interface DraftMessageDataListener { - @RunsOnMainThread - void onDraftChanged(DraftMessageData data, int changeFlags); - - @RunsOnMainThread - void onDraftAttachmentLimitReached(DraftMessageData data); - - @RunsOnMainThread - void onDraftAttachmentLoadFailed(); - } - - /** - * Interface for providing subscription-related data to DraftMessageData - */ - public interface DraftMessageSubscriptionDataProvider { - int getConversationSelfSubId(); - } - - // Flags sent to onDraftChanged to help the receiver limit the amount of work done - public static int ATTACHMENTS_CHANGED = 0x0001; - public static int MESSAGE_TEXT_CHANGED = 0x0002; - public static int MESSAGE_SUBJECT_CHANGED = 0x0004; - // Whether the self participant data has been loaded - public static int SELF_CHANGED = 0x0008; - public static int ALL_CHANGED = 0x00FF; - // ALL_CHANGED intentionally doesn't include WIDGET_CHANGED. ConversationFragment needs to - // be notified if the draft it is looking at is changed externally (by a desktop widget) so it - // can reload the draft. - public static int WIDGET_CHANGED = 0x0100; - - private final String mConversationId; - private ReadDraftDataActionMonitor mMonitor; - private final DraftMessageDataEventDispatcher mListeners; - private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider; - - private boolean mIncludeEmailAddress; - private boolean mIsGroupConversation; - private String mMessageText; - private String mMessageSubject; - private String mSelfId; - private MessageTextStats mMessageTextStats; - private boolean mSending; - - /** Keeps track of completed attachments in the message draft. This data is persisted to db */ - private final List mAttachments; - - /** A read-only wrapper on mAttachments surfaced to the UI layer for rendering */ - private final List mReadOnlyAttachments; - - /** Keeps track of pending attachments that are being loaded. The pending attachments are - * transient, because they are not persisted to the database and are dropped once we go - * to the background (after the UI calls saveToStorage) */ - private final List mPendingAttachments; - - /** A read-only wrapper on mPendingAttachments surfaced to the UI layer for rendering */ - private final List mReadOnlyPendingAttachments; - - /** Is the current draft a cached copy of what's been saved to the database. If so, we - * may skip loading from database if we are still bound */ - private boolean mIsDraftCachedCopy; - - /** Whether we are currently asynchronously validating the draft before sending. */ - private CheckDraftForSendTask mCheckDraftForSendTask; - - public DraftMessageData(final String conversationId) { - mConversationId = conversationId; - mAttachments = new ArrayList(); - mReadOnlyAttachments = Collections.unmodifiableList(mAttachments); - mPendingAttachments = new ArrayList(); - mReadOnlyPendingAttachments = Collections.unmodifiableList(mPendingAttachments); - mListeners = new DraftMessageDataEventDispatcher(); - mMessageTextStats = new MessageTextStats(); - } - - public void addListener(final DraftMessageDataListener listener) { - mListeners.add(listener); - } - - public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) { - mSubscriptionDataProvider = provider; - } - - public void updateFromMessageData(final MessageData message, final String bindingId) { - // New attachments have arrived - only update if the user hasn't already edited - Assert.notNull(bindingId); - // The draft is now synced with actual MessageData and no longer a cached copy. - mIsDraftCachedCopy = false; - // Do not use the loaded draft if the user began composing a message before the draft loaded - // During config changes (orientation), the text fields preserve their data, so allow them - // to be the same and still consider the draft unchanged by the user - if (isDraftEmpty() || (TextUtils.equals(mMessageText, message.getMessageText()) && - TextUtils.equals(mMessageSubject, message.getMmsSubject()) && - mAttachments.isEmpty())) { - // No need to clear as just checked it was empty or a subset - setMessageText(message.getMessageText(), false /* notify */); - setMessageSubject(message.getMmsSubject(), false /* notify */); - for (final MessagePartData part : message.getParts()) { - if (part.isAttachment() && getAttachmentCount() >= getAttachmentLimit()) { - dispatchAttachmentLimitReached(); - break; - } - - if (part instanceof PendingAttachmentData) { - // This is a pending attachment data from share intent (e.g. an shared image - // that we need to persist locally). - final PendingAttachmentData data = (PendingAttachmentData) part; - Assert.equals(PendingAttachmentData.STATE_PENDING, data.getCurrentState()); - addOnePendingAttachmentNoNotify(data, bindingId); - } else if (part.isAttachment()) { - addOneAttachmentNoNotify(part); - } - } - dispatchChanged(ALL_CHANGED); - } else { - // The user has started a new message so we throw out the draft message data if there - // is one but we also loaded the self metadata and need to let our listeners know. - dispatchChanged(SELF_CHANGED); - } - } - - /** - * Create a MessageData object containing a copy of all the parts in this DraftMessageData. - * - * @param clearLocalCopy whether we should clear out the in-memory copy of the parts. If we - * are simply pausing/resuming and not sending the message, then we can keep - * @return the MessageData for the draft, null if self id is not set - */ - public MessageData createMessageWithCurrentAttachments(final boolean clearLocalCopy) { - MessageData message = null; - if (getIsMms()) { - message = MessageData.createDraftMmsMessage(mConversationId, mSelfId, - mMessageText, mMessageSubject); - for (final MessagePartData attachment : mAttachments) { - message.addPart(attachment); - } - } else { - message = MessageData.createDraftSmsMessage(mConversationId, mSelfId, - mMessageText); - } - - if (clearLocalCopy) { - // The message now owns all the attachments and the text... - clearLocalDraftCopy(); - dispatchChanged(ALL_CHANGED); - } else { - // The draft message becomes a cached copy for UI. - mIsDraftCachedCopy = true; - } - return message; - } - - private void clearLocalDraftCopy() { - mIsDraftCachedCopy = false; - mAttachments.clear(); - setMessageText(""); - setMessageSubject(""); - } - - public String getConversationId() { - return mConversationId; - } - - public String getMessageText() { - return mMessageText; - } - - public String getMessageSubject() { - return mMessageSubject; - } - - public boolean getIsMms() { - final int selfSubId = getSelfSubId(); - return MmsSmsUtils.getRequireMmsForEmailAddress(mIncludeEmailAddress, selfSubId) || - (mIsGroupConversation && MmsUtils.groupMmsEnabled(selfSubId)) || - mMessageTextStats.getMessageLengthRequiresMms() || !mAttachments.isEmpty() || - !TextUtils.isEmpty(mMessageSubject); - } - - public boolean getIsGroupMmsConversation() { - return getIsMms() && mIsGroupConversation; - } - - public String getSelfId() { - return mSelfId; - } - - public int getNumMessagesToBeSent() { - return mMessageTextStats.getNumMessagesToBeSent(); - } - - public int getCodePointsRemainingInCurrentMessage() { - return mMessageTextStats.getCodePointsRemainingInCurrentMessage(); - } - - public int getSelfSubId() { - return mSubscriptionDataProvider == null ? ParticipantData.DEFAULT_SELF_SUB_ID : - mSubscriptionDataProvider.getConversationSelfSubId(); - } - - private void setMessageText(final String messageText, final boolean notify) { - mMessageText = messageText; - mMessageTextStats.updateMessageTextStats(getSelfSubId(), mMessageText); - if (notify) { - dispatchChanged(MESSAGE_TEXT_CHANGED); - } - } - - private void setMessageSubject(final String subject, final boolean notify) { - mMessageSubject = subject; - if (notify) { - dispatchChanged(MESSAGE_SUBJECT_CHANGED); - } - } - - public void setMessageText(final String messageText) { - setMessageText(messageText, false); - } - - public void setMessageSubject(final String subject) { - setMessageSubject(subject, false); - } - - public void addAttachments(final Collection attachments) { - // If the incoming attachments contains a single-only attachment, we need to clear - // the existing attachments. - for (final MessagePartData data : attachments) { - if (data.isSinglePartOnly()) { - // clear any existing attachments because the attachment we're adding can only - // exist by itself. - destroyAttachments(); - break; - } - } - // If the existing attachments contain a single-only attachment, we need to clear the - // existing attachments to make room for the incoming attachment. - for (final MessagePartData data : mAttachments) { - if (data.isSinglePartOnly()) { - // clear any existing attachments because the single attachment can only exist - // by itself - destroyAttachments(); - break; - } - } - // If any of the pending attachments contain a single-only attachment, we need to clear the - // existing attachments to make room for the incoming attachment. - for (final MessagePartData data : mPendingAttachments) { - if (data.isSinglePartOnly()) { - // clear any existing attachments because the single attachment can only exist - // by itself - destroyAttachments(); - break; - } - } - - boolean reachedLimit = false; - for (final MessagePartData data : attachments) { - // Don't break out of loop even if limit has been reached so we can destroy all - // of the over-limit attachments. - reachedLimit |= addOneAttachmentNoNotify(data); - } - if (reachedLimit) { - dispatchAttachmentLimitReached(); - } - dispatchChanged(ATTACHMENTS_CHANGED); - } - - public boolean containsAttachment(final Uri contentUri) { - for (final MessagePartData existingAttachment : mAttachments) { - if (existingAttachment.getContentUri().equals(contentUri)) { - return true; - } - } - - for (final PendingAttachmentData pendingAttachment : mPendingAttachments) { - if (pendingAttachment.getContentUri().equals(contentUri)) { - return true; - } - } - return false; - } - - /** - * Try to add one attachment to the attachment list, while guarding against duplicates and - * going over the limit. - * @return true if the attachment limit was reached, false otherwise - */ - private boolean addOneAttachmentNoNotify(final MessagePartData attachment) { - Assert.isTrue(attachment.isAttachment()); - final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit(); - if (reachedLimit || containsAttachment(attachment.getContentUri())) { - // Never go over the limit. Never add duplicated attachments. - attachment.destroyAsync(); - return reachedLimit; - } else { - addAttachment(attachment, null /*pendingAttachment*/); - return false; - } - } - - private void addAttachment(final MessagePartData attachment, - final PendingAttachmentData pendingAttachment) { - if (attachment != null && attachment.isSinglePartOnly()) { - // clear any existing attachments because the attachment we're adding can only - // exist by itself. - destroyAttachments(); - } - if (pendingAttachment != null && pendingAttachment.isSinglePartOnly()) { - // clear any existing attachments because the attachment we're adding can only - // exist by itself. - destroyAttachments(); - } - // If the existing attachments contain a single-only attachment, we need to clear the - // existing attachments to make room for the incoming attachment. - for (final MessagePartData data : mAttachments) { - if (data.isSinglePartOnly()) { - // clear any existing attachments because the single attachment can only exist - // by itself - destroyAttachments(); - break; - } - } - // If any of the pending attachments contain a single-only attachment, we need to clear the - // existing attachments to make room for the incoming attachment. - for (final MessagePartData data : mPendingAttachments) { - if (data.isSinglePartOnly()) { - // clear any existing attachments because the single attachment can only exist - // by itself - destroyAttachments(); - break; - } - } - if (attachment != null) { - mAttachments.add(attachment); - } else if (pendingAttachment != null) { - mPendingAttachments.add(pendingAttachment); - } - } - - public void addPendingAttachment(final PendingAttachmentData pendingAttachment, - final BindingBase binding) { - final boolean reachedLimit = addOnePendingAttachmentNoNotify(pendingAttachment, - binding.getBindingId()); - if (reachedLimit) { - dispatchAttachmentLimitReached(); - } - dispatchChanged(ATTACHMENTS_CHANGED); - } - - /** - * Try to add one pending attachment, while guarding against duplicates and - * going over the limit. - * @return true if the attachment limit was reached, false otherwise - */ - private boolean addOnePendingAttachmentNoNotify(final PendingAttachmentData pendingAttachment, - final String bindingId) { - final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit(); - if (reachedLimit || containsAttachment(pendingAttachment.getContentUri())) { - // Never go over the limit. Never add duplicated attachments. - pendingAttachment.destroyAsync(); - return reachedLimit; - } else { - Assert.isTrue(!mPendingAttachments.contains(pendingAttachment)); - Assert.equals(PendingAttachmentData.STATE_PENDING, pendingAttachment.getCurrentState()); - addAttachment(null /*attachment*/, pendingAttachment); - - pendingAttachment.loadAttachmentForDraft(this, bindingId); - return false; - } - } - - public void setSelfId(final String selfId, final boolean notify) { - LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: set selfId=" + selfId - + " for conversationId=" + mConversationId); - mSelfId = selfId; - if (notify) { - dispatchChanged(SELF_CHANGED); - } - } - - public boolean hasAttachments() { - return !mAttachments.isEmpty(); - } - - public boolean hasPendingAttachments() { - return !mPendingAttachments.isEmpty(); - } - - private int getAttachmentCount() { - return mAttachments.size() + mPendingAttachments.size(); - } - - private int getVideoAttachmentCount() { - int count = 0; - for (MessagePartData part : mAttachments) { - if (part.isVideo()) { - count++; - } - } - for (MessagePartData part : mPendingAttachments) { - if (part.isVideo()) { - count++; - } - } - return count; - } - - private int getAttachmentLimit() { - return BugleGservices.get().getInt( - BugleGservicesKeys.MMS_ATTACHMENT_LIMIT, - BugleGservicesKeys.MMS_ATTACHMENT_LIMIT_DEFAULT); - } - - public void removeAttachment(final MessagePartData attachment) { - for (final MessagePartData existingAttachment : mAttachments) { - if (existingAttachment.getContentUri().equals(attachment.getContentUri())) { - mAttachments.remove(existingAttachment); - existingAttachment.destroyAsync(); - dispatchChanged(ATTACHMENTS_CHANGED); - break; - } - } - } - - public void removeExistingAttachments(final Set attachmentsToRemove) { - boolean removed = false; - final Iterator iterator = mAttachments.iterator(); - while (iterator.hasNext()) { - final MessagePartData existingAttachment = iterator.next(); - if (attachmentsToRemove.contains(existingAttachment)) { - iterator.remove(); - existingAttachment.destroyAsync(); - removed = true; - } - } - - if (removed) { - dispatchChanged(ATTACHMENTS_CHANGED); - } - } - - public void removePendingAttachment(final PendingAttachmentData pendingAttachment) { - for (final PendingAttachmentData existingAttachment : mPendingAttachments) { - if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) { - mPendingAttachments.remove(pendingAttachment); - pendingAttachment.destroyAsync(); - dispatchChanged(ATTACHMENTS_CHANGED); - break; - } - } - } - - public void updatePendingAttachment(final MessagePartData updatedAttachment, - final PendingAttachmentData pendingAttachment) { - for (final PendingAttachmentData existingAttachment : mPendingAttachments) { - if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) { - mPendingAttachments.remove(pendingAttachment); - if (pendingAttachment.isSinglePartOnly()) { - updatedAttachment.setSinglePartOnly(true); - } - mAttachments.add(updatedAttachment); - dispatchChanged(ATTACHMENTS_CHANGED); - return; - } - } - - // If we are here, this means the pending attachment has been dropped before the task - // to load it was completed. In this case destroy the temporarily staged file since it - // is no longer needed. - updatedAttachment.destroyAsync(); - } - - /** - * Remove the attachments from the draft and notify any listeners. - * @param flags typically this will be ATTACHMENTS_CHANGED. When attachments are cleared in a - * widget, flags will also contain WIDGET_CHANGED. - */ - public void clearAttachments(final int flags) { - destroyAttachments(); - dispatchChanged(flags); - } - - public List getReadOnlyAttachments() { - return mReadOnlyAttachments; - } - - public List getReadOnlyPendingAttachments() { - return mReadOnlyPendingAttachments; - } - - public boolean loadFromStorage(final BindingBase binding, - final MessageData optionalIncomingDraft, boolean clearLocalDraft) { - LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: " - + (optionalIncomingDraft == null ? "loading" : "setting") - + " for conversationId=" + mConversationId); - if (clearLocalDraft) { - clearLocalDraftCopy(); - } - final boolean isDraftCachedCopy = mIsDraftCachedCopy; - mIsDraftCachedCopy = false; - // Before reading message from db ensure the caller is bound to us (and knows the id) - if (mMonitor == null && !isDraftCachedCopy && isBound(binding.getBindingId())) { - mMonitor = ReadDraftDataAction.readDraftData(mConversationId, - optionalIncomingDraft, binding.getBindingId(), this); - return true; - } - return false; - } - - /** - * Saves the current draft to db. This will save the draft and drop any pending attachments - * we have. The UI typically goes into the background when this is called, and instead of - * trying to persist the state of the pending attachments (the app may be killed, the activity - * may be destroyed), we simply drop the pending attachments for consistency. - */ - public void saveToStorage(final BindingBase binding) { - saveToStorageInternal(binding); - dropPendingAttachments(); - } - - private void saveToStorageInternal(final BindingBase binding) { - // Create MessageData to store to db, but don't clear the in-memory copy so UI will - // continue to display it. - // If self id is null then we'll not attempt to change the conversation's self id. - final MessageData message = createMessageWithCurrentAttachments(false /* clearLocalCopy */); - // Before writing message to db ensure the caller is bound to us (and knows the id) - if (isBound(binding.getBindingId())){ - WriteDraftMessageAction.writeDraftMessage(mConversationId, message); - } - } - - /** - * Called when we are ready to send the message. This will assemble/return the MessageData for - * sending and clear the local draft data, both from memory and from DB. This will also bind - * the message data with a self Id through which the message will be sent. - * - * @param binding the binding object from our consumer. We need to make sure we are still bound - * to that binding before saving to storage. - */ - public MessageData prepareMessageForSending(final BindingBase binding) { - // We can't send the message while there's still stuff pending. - Assert.isTrue(!hasPendingAttachments()); - mSending = true; - // Assembles the message to send and empty working draft data. - // If self id is null then message is sent with conversation's self id. - final MessageData messageToSend = - createMessageWithCurrentAttachments(true /* clearLocalCopy */); - // Note sending message will empty the draft data in DB. - mSending = false; - return messageToSend; - } - - public boolean isSending() { - return mSending; - } - - @Override // ReadDraftMessageActionListener.onReadDraftMessageSucceeded - public void onReadDraftDataSucceeded(final ReadDraftDataAction action, final Object data, - final MessageData message, final ConversationListItemData conversation) { - final String bindingId = (String) data; - - // Before passing draft message on to ui ensure the data is bound to the same bindingid - if (isBound(bindingId)) { - mSelfId = message.getSelfId(); - mIsGroupConversation = conversation.getIsGroup(); - mIncludeEmailAddress = conversation.getIncludeEmailAddress(); - updateFromMessageData(message, bindingId); - LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded. " - + "conversationId=" + mConversationId + " selfId=" + mSelfId); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded but not bound. " - + "conversationId=" + mConversationId); - } - mMonitor = null; - } - - @Override // ReadDraftMessageActionListener.onReadDraftDataFailed - public void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data) { - LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft not loaded. " - + "conversationId=" + mConversationId); - // The draft is now synced with actual MessageData and no longer a cached copy. - mIsDraftCachedCopy = false; - // Just clear the monitor - no update to draft data - mMonitor = null; - } - - /** - * Check if Bugle is default sms app - * @return - */ - public boolean getIsDefaultSmsApp() { - return PhoneUtils.getDefault().isDefaultSmsApp(); - } - - @Override //BindableData.unregisterListeners - protected void unregisterListeners() { - if (mMonitor != null) { - mMonitor.unregister(); - } - mMonitor = null; - mListeners.clear(); - } - - private void destroyAttachments() { - for (final MessagePartData attachment : mAttachments) { - attachment.destroyAsync(); - } - mAttachments.clear(); - mPendingAttachments.clear(); - } - - private void dispatchChanged(final int changeFlags) { - // No change is expected to be made to the draft if it is in cached copy state. - if (mIsDraftCachedCopy) { - return; - } - // Any change in the draft will cancel any pending draft checking task, since the - // size/status of the draft may have changed. - if (mCheckDraftForSendTask != null) { - mCheckDraftForSendTask.cancel(true /* mayInterruptIfRunning */); - mCheckDraftForSendTask = null; - } - mListeners.onDraftChanged(this, changeFlags); - } - - private void dispatchAttachmentLimitReached() { - mListeners.onDraftAttachmentLimitReached(this); - } - - /** - * Drop any pending attachments that haven't finished. This is called after the UI goes to - * the background and we persist the draft data to the database. - */ - private void dropPendingAttachments() { - mPendingAttachments.clear(); - } - - private boolean isDraftEmpty() { - return TextUtils.isEmpty(mMessageText) && mAttachments.isEmpty() && - TextUtils.isEmpty(mMessageSubject); - } - - public boolean isCheckingDraft() { - return mCheckDraftForSendTask != null && !mCheckDraftForSendTask.isCancelled(); - } - - public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId, - final CheckDraftTaskCallback callback, final Binding binding) { - new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding) - .executeOnThreadPool((Void) null); - } - - /** - * Allows us to have multiple data listeners for DraftMessageData - */ - private class DraftMessageDataEventDispatcher - extends ArrayList - implements DraftMessageDataListener { - - @Override - @RunsOnMainThread - public void onDraftChanged(DraftMessageData data, int changeFlags) { - Assert.isMainThread(); - for (final DraftMessageDataListener listener : this) { - listener.onDraftChanged(data, changeFlags); - } - } - - @Override - @RunsOnMainThread - public void onDraftAttachmentLimitReached(DraftMessageData data) { - Assert.isMainThread(); - for (final DraftMessageDataListener listener : this) { - listener.onDraftAttachmentLimitReached(data); - } - } - - @Override - @RunsOnMainThread - public void onDraftAttachmentLoadFailed() { - Assert.isMainThread(); - for (final DraftMessageDataListener listener : this) { - listener.onDraftAttachmentLoadFailed(); - } - } - } - - public interface CheckDraftTaskCallback { - void onDraftChecked(DraftMessageData data, int result); - } - - public class CheckDraftForSendTask extends SafeAsyncTask { - public static final int RESULT_PASSED = 0; - public static final int RESULT_HAS_PENDING_ATTACHMENTS = 1; - public static final int RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS = 2; - public static final int RESULT_MESSAGE_OVER_LIMIT = 3; - public static final int RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED = 4; - public static final int RESULT_SIM_NOT_READY = 5; - private final boolean mCheckMessageSize; - private final int mSelfSubId; - private final CheckDraftTaskCallback mCallback; - private final String mBindingId; - private final List mAttachmentsCopy; - private int mPreExecuteResult = RESULT_PASSED; - - public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId, - final CheckDraftTaskCallback callback, final Binding binding) { - mCheckMessageSize = checkMessageSize; - mSelfSubId = selfSubId; - mCallback = callback; - mBindingId = binding.getBindingId(); - // Obtain an immutable copy of the attachment list so we can operate on it in the - // background thread. - mAttachmentsCopy = new ArrayList(mAttachments); - - mCheckDraftForSendTask = this; - } - - @Override - protected void onPreExecute() { - // Perform checking work that can happen on the main thread. - if (hasPendingAttachments()) { - mPreExecuteResult = RESULT_HAS_PENDING_ATTACHMENTS; - return; - } - if (getIsGroupMmsConversation()) { - try { - if (TextUtils.isEmpty(PhoneUtils.get(mSelfSubId).getSelfRawNumber(true))) { - mPreExecuteResult = RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS; - return; - } - } catch (IllegalStateException e) { - // This happens when there is no active subscription, e.g. on Nova - // when the phone switches carrier. - mPreExecuteResult = RESULT_SIM_NOT_READY; - return; - } - } - if (getVideoAttachmentCount() > MmsUtils.MAX_VIDEO_ATTACHMENT_COUNT) { - mPreExecuteResult = RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED; - return; - } - } - - @Override - protected Integer doInBackgroundTimed(Void... params) { - if (mPreExecuteResult != RESULT_PASSED) { - return mPreExecuteResult; - } - - if (mCheckMessageSize && getIsMessageOverLimit()) { - return RESULT_MESSAGE_OVER_LIMIT; - } - return RESULT_PASSED; - } - - @Override - protected void onPostExecute(Integer result) { - mCheckDraftForSendTask = null; - // Only call back if we are bound to the original binding. - if (isBound(mBindingId) && !isCancelled()) { - mCallback.onDraftChecked(DraftMessageData.this, result); - } else { - if (!isBound(mBindingId)) { - LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound"); - } - if (isCancelled()) { - LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft is cancelled"); - } - } - } - - @Override - protected void onCancelled() { - mCheckDraftForSendTask = null; - } - - /** - * 1. Check if the draft message contains too many attachments to send - * 2. Computes the minimum size that this message could be compressed/downsampled/encoded - * before sending and check if it meets the carrier max size for sending. - * @see MessagePartData#getMinimumSizeInBytesForSending() - */ - @DoesNotRunOnMainThread - private boolean getIsMessageOverLimit() { - Assert.isNotMainThread(); - if (mAttachmentsCopy.size() > getAttachmentLimit()) { - return true; - } - - // Aggregate the size from all the attachments. - long totalSize = 0; - for (final MessagePartData attachment : mAttachmentsCopy) { - totalSize += attachment.getMinimumSizeInBytesForSending(); - } - return totalSize > MmsConfig.get(mSelfSubId).getMaxMessageSize(); - } - } - - public void onPendingAttachmentLoadFailed(PendingAttachmentData data) { - mListeners.onDraftAttachmentLoadFailed(); - } -} diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java deleted file mode 100644 index 6649757..0000000 --- a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.graphics.Rect; -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.MediaStore.Images.Media; -import android.text.TextUtils; - -import com.android.messaging.datamodel.media.FileImageRequestDescriptor; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.util.Assert; - -/** - * Provides data for GalleryGridItemView - */ -public class GalleryGridItemData { - public static final String[] IMAGE_PROJECTION = new String[] { - Media._ID, - Media.DATA, - Media.WIDTH, - Media.HEIGHT, - Media.MIME_TYPE, - Media.DATE_MODIFIED}; - - public static final String[] SPECIAL_ITEM_COLUMNS = new String[] { - BaseColumns._ID - }; - - private static final int INDEX_ID = 0; - - // For local image gallery. - private static final int INDEX_DATA_PATH = 1; - private static final int INDEX_WIDTH = 2; - private static final int INDEX_HEIGHT = 3; - private static final int INDEX_MIME_TYPE = 4; - private static final int INDEX_DATE_MODIFIED = 5; - - /** A special item's id for picking images from document picker */ - public static final String ID_DOCUMENT_PICKER_ITEM = "-1"; - - private UriImageRequestDescriptor mImageData; - private String mContentType; - private boolean mIsDocumentPickerItem; - private long mDateSeconds; - - public GalleryGridItemData() { - } - - public void bind(final Cursor cursor, final int desiredWidth, final int desiredHeight) { - mIsDocumentPickerItem = TextUtils.equals(cursor.getString(INDEX_ID), - ID_DOCUMENT_PICKER_ITEM); - if (mIsDocumentPickerItem) { - mImageData = null; - mContentType = null; - } else { - int sourceWidth = cursor.getInt(INDEX_WIDTH); - int sourceHeight = cursor.getInt(INDEX_HEIGHT); - - // Guard against bad data - if (sourceWidth <= 0) { - sourceWidth = ImageRequest.UNSPECIFIED_SIZE; - } - if (sourceHeight <= 0) { - sourceHeight = ImageRequest.UNSPECIFIED_SIZE; - } - - mContentType = cursor.getString(INDEX_MIME_TYPE); - final String dateModified = cursor.getString(INDEX_DATE_MODIFIED); - mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1; - mImageData = new FileImageRequestDescriptor( - cursor.getString(INDEX_DATA_PATH), - desiredWidth, - desiredHeight, - sourceWidth, - sourceHeight, - true /* canUseThumbnail */, - true /* allowCompression */, - true /* isStatic */); - } - } - - public boolean isDocumentPickerItem() { - return mIsDocumentPickerItem; - } - - public Uri getImageUri() { - return mImageData.uri; - } - - public UriImageRequestDescriptor getImageRequestDescriptor() { - return mImageData; - } - - public MessagePartData constructMessagePartData(final Rect startRect) { - Assert.isTrue(!mIsDocumentPickerItem); - return new MediaPickerMessagePartData(startRect, mContentType, - mImageData.uri, mImageData.sourceWidth, mImageData.sourceHeight); - } - - /** - * @return The date in seconds. This can be negative if we could not retreive date info - */ - public long getDateSeconds() { - return mDateSeconds; - } - - public String getContentType() { - return mContentType; - } -} diff --git a/src/com/android/messaging/datamodel/data/LaunchConversationData.java b/src/com/android/messaging/datamodel/data/LaunchConversationData.java deleted file mode 100644 index 7eea580..0000000 --- a/src/com/android/messaging/datamodel/data/LaunchConversationData.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import com.android.messaging.datamodel.action.ActionMonitor; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.LogUtil; - -public class LaunchConversationData extends BindableData implements - GetOrCreateConversationActionListener { - public interface LaunchConversationDataListener { - void onGetOrCreateNewConversation(String conversationId); - void onGetOrCreateNewConversationFailed(); - } - - private LaunchConversationDataListener mListener; - private GetOrCreateConversationActionMonitor mMonitor; - - public LaunchConversationData(final LaunchConversationDataListener listener) { - mListener = listener; - } - - @Override - protected void unregisterListeners() { - mListener = null; - if (mMonitor != null) { - mMonitor.unregister(); - } - mMonitor = null; - } - - public void getOrCreateConversation(final BindingBase binding, - final String[] recipients) { - final String bindingId = binding.getBindingId(); - - // Start a new conversation from the list of contacts. - if (isBound(bindingId) && mMonitor == null) { - mMonitor = GetOrCreateConversationAction.getOrCreateConversation(recipients, - bindingId, this); - } - } - - @Override - @RunsOnMainThread - public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor, - final Object data, final String conversationId) { - Assert.isTrue(monitor == mMonitor); - Assert.isTrue(conversationId != null); - - final String bindingId = (String) data; - if (isBound(bindingId) && mListener != null) { - mListener.onGetOrCreateNewConversation(conversationId); - } - - mMonitor = null; - } - - @Override - @RunsOnMainThread - public void onGetOrCreateConversationFailed(final ActionMonitor monitor, - final Object data) { - Assert.isTrue(monitor == mMonitor); - final String bindingId = (String) data; - if (isBound(bindingId) && mListener != null) { - mListener.onGetOrCreateNewConversationFailed(); - } - LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed"); - mMonitor = null; - } -} diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java deleted file mode 100644 index b0c8bf7..0000000 --- a/src/com/android/messaging/datamodel/data/MediaPickerData.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.os.Bundle; -import android.support.annotation.Nullable; - -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.GalleryBoundCursorLoader; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.LogUtil; - -/** - * Services data needs for MediaPicker. - */ -public class MediaPickerData extends BindableData { - public interface MediaPickerDataListener { - void onMediaPickerDataUpdated(MediaPickerData mediaPickerData, Object data, int loaderId); - } - - private static final String BINDING_ID = "bindingId"; - private final Context mContext; - private LoaderManager mLoaderManager; - private final GalleryLoaderCallbacks mGalleryLoaderCallbacks; - private MediaPickerDataListener mListener; - - public MediaPickerData(final Context context) { - mContext = context; - mGalleryLoaderCallbacks = new GalleryLoaderCallbacks(); - } - - public static final int GALLERY_IMAGE_LOADER = 1; - - /** - * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times. - */ - private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks { - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - switch (id) { - case GALLERY_IMAGE_LOADER: - return new GalleryBoundCursorLoader(bindingId, mContext); - - default: - Assert.fail("Unknown loader id for gallery picker!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the media picker"); - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoadFinished(final Loader loader, final Cursor data) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case GALLERY_IMAGE_LOADER: - mListener.onMediaPickerDataUpdated(MediaPickerData.this, data, - GALLERY_IMAGE_LOADER); - break; - - default: - Assert.fail("Unknown loader id for gallery picker!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the media picker"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoaderReset(final Loader loader) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case GALLERY_IMAGE_LOADER: - mListener.onMediaPickerDataUpdated(MediaPickerData.this, null, - GALLERY_IMAGE_LOADER); - break; - - default: - Assert.fail("Unknown loader id for media picker!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the media picker"); - } - } - } - - - - public void startLoader(final int loaderId, final BindingBase binding, - @Nullable Bundle args, final MediaPickerDataListener listener) { - if (args == null) { - args = new Bundle(); - } - args.putString(BINDING_ID, binding.getBindingId()); - if (loaderId == GALLERY_IMAGE_LOADER) { - mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad(); - } else { - Assert.fail("Unsupported loader id for media picker!"); - } - mListener = listener; - } - - public void destroyLoader(final int loaderId) { - mLoaderManager.destroyLoader(loaderId); - } - - public void init(final LoaderManager loaderManager) { - mLoaderManager = loaderManager; - } - - @Override - protected void unregisterListeners() { - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER); - mLoaderManager = null; - } - } - - /** - * Gets the last selected chooser index, or -1 if no selection has been saved. - */ - public int getSelectedChooserIndex() { - return BuglePrefs.getApplicationPrefs().getInt( - BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX, - BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT); - } - - /** - * Saves the selected media chooser index. - * @param selectedIndex the selected media chooser index. - */ - public void saveSelectedChooserIndex(final int selectedIndex) { - BuglePrefs.getApplicationPrefs().putInt(BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX, - selectedIndex); - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java b/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java deleted file mode 100644 index 7de9166..0000000 --- a/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.graphics.Rect; -import android.net.Uri; - -public class MediaPickerMessagePartData extends MessagePartData { - private final Rect mStartRect; - - public MediaPickerMessagePartData(final Rect startRect, final String contentType, - final Uri contentUri, final int width, final int height) { - this(startRect, null /* messageText */, contentType, contentUri, width, height); - } - - public MediaPickerMessagePartData(final Rect startRect, final String messageText, - final String contentType, final Uri contentUri, final int width, final int height) { - this(startRect, messageText, contentType, contentUri, width, height, - false /*onlySingleAttachment*/); - } - - public MediaPickerMessagePartData(final Rect startRect, final String contentType, - final Uri contentUri, final int width, final int height, - final boolean onlySingleAttachment) { - this(startRect, null /* messageText */, contentType, contentUri, width, height, - onlySingleAttachment); - } - - public MediaPickerMessagePartData(final Rect startRect, final String messageText, - final String contentType, final Uri contentUri, final int width, final int height, - final boolean onlySingleAttachment) { - super(messageText, contentType, contentUri, width, height, onlySingleAttachment); - mStartRect = startRect; - } - - /** - * @return The starting rect to animate the attachment preview from in order to perform a smooth - * transition - */ - public Rect getStartRect() { - return mStartRect; - } - - /** - * Modify the start rect of the attachment. - */ - public void setStartRect(final Rect startRect) { - mStartRect.set(startRect); - } -} diff --git a/src/com/android/messaging/datamodel/data/MessageData.java b/src/com/android/messaging/datamodel/data/MessageData.java deleted file mode 100644 index a3698a9..0000000 --- a/src/com/android/messaging/datamodel/data/MessageData.java +++ /dev/null @@ -1,922 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteStatement; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.Dates; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.OsUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class MessageData implements Parcelable { - private static final String[] sProjection = { - MessageColumns._ID, - MessageColumns.CONVERSATION_ID, - MessageColumns.SENDER_PARTICIPANT_ID, - MessageColumns.SELF_PARTICIPANT_ID, - MessageColumns.SENT_TIMESTAMP, - MessageColumns.RECEIVED_TIMESTAMP, - MessageColumns.SEEN, - MessageColumns.READ, - MessageColumns.PROTOCOL, - MessageColumns.STATUS, - MessageColumns.SMS_MESSAGE_URI, - MessageColumns.SMS_PRIORITY, - MessageColumns.SMS_MESSAGE_SIZE, - MessageColumns.MMS_SUBJECT, - MessageColumns.MMS_TRANSACTION_ID, - MessageColumns.MMS_CONTENT_LOCATION, - MessageColumns.MMS_EXPIRY, - MessageColumns.RAW_TELEPHONY_STATUS, - MessageColumns.RETRY_START_TIMESTAMP, - }; - - private static final int INDEX_ID = 0; - private static final int INDEX_CONVERSATION_ID = 1; - private static final int INDEX_PARTICIPANT_ID = 2; - private static final int INDEX_SELF_ID = 3; - private static final int INDEX_SENT_TIMESTAMP = 4; - private static final int INDEX_RECEIVED_TIMESTAMP = 5; - private static final int INDEX_SEEN = 6; - private static final int INDEX_READ = 7; - private static final int INDEX_PROTOCOL = 8; - private static final int INDEX_BUGLE_STATUS = 9; - private static final int INDEX_SMS_MESSAGE_URI = 10; - private static final int INDEX_SMS_PRIORITY = 11; - private static final int INDEX_SMS_MESSAGE_SIZE = 12; - private static final int INDEX_MMS_SUBJECT = 13; - private static final int INDEX_MMS_TRANSACTION_ID = 14; - private static final int INDEX_MMS_CONTENT_LOCATION = 15; - private static final int INDEX_MMS_EXPIRY = 16; - private static final int INDEX_RAW_TELEPHONY_STATUS = 17; - private static final int INDEX_RETRY_START_TIMESTAMP = 18; - - // SQL statement to insert a "complete" message row (columns based on the projection above). - private static final String INSERT_MESSAGE_SQL = - "INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( " - + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1, - INDEX_RETRY_START_TIMESTAMP + 1)) - + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - private String mMessageId; - private String mConversationId; - private String mParticipantId; - private String mSelfId; - private long mSentTimestamp; - private long mReceivedTimestamp; - private boolean mSeen; - private boolean mRead; - private int mProtocol; - private Uri mSmsMessageUri; - private int mSmsPriority; - private long mSmsMessageSize; - private String mMmsSubject; - private String mMmsTransactionId; - private String mMmsContentLocation; - private long mMmsExpiry; - private int mRawStatus; - private int mStatus; - private final ArrayList mParts; - private long mRetryStartTimestamp; - - // PROTOCOL Values - public static final int PROTOCOL_UNKNOWN = -1; // Unknown type - public static final int PROTOCOL_SMS = 0; // SMS message - public static final int PROTOCOL_MMS = 1; // MMS message - public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification - - // Bugle STATUS Values - public static final int BUGLE_STATUS_UNKNOWN = 0; - - // Outgoing - public static final int BUGLE_STATUS_OUTGOING_COMPLETE = 1; - public static final int BUGLE_STATUS_OUTGOING_DELIVERED = 2; - // Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments. - public static final int BUGLE_STATUS_OUTGOING_DRAFT = 3; - public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND = 4; - public static final int BUGLE_STATUS_OUTGOING_SENDING = 5; - public static final int BUGLE_STATUS_OUTGOING_RESENDING = 6; - public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY = 7; - public static final int BUGLE_STATUS_OUTGOING_FAILED = 8; - public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9; - - // Incoming - public static final int BUGLE_STATUS_INCOMING_COMPLETE = 100; - public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD = 101; - public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD = 102; - public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING = 103; - public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD = 104; - public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING = 105; - public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED = 106; - public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE = 107; - - public static final String getStatusDescription(int status) { - switch (status) { - case BUGLE_STATUS_UNKNOWN: - return "UNKNOWN"; - case BUGLE_STATUS_OUTGOING_COMPLETE: - return "OUTGOING_COMPLETE"; - case BUGLE_STATUS_OUTGOING_DELIVERED: - return "OUTGOING_DELIVERED"; - case BUGLE_STATUS_OUTGOING_DRAFT: - return "OUTGOING_DRAFT"; - case BUGLE_STATUS_OUTGOING_YET_TO_SEND: - return "OUTGOING_YET_TO_SEND"; - case BUGLE_STATUS_OUTGOING_SENDING: - return "OUTGOING_SENDING"; - case BUGLE_STATUS_OUTGOING_RESENDING: - return "OUTGOING_RESENDING"; - case BUGLE_STATUS_OUTGOING_AWAITING_RETRY: - return "OUTGOING_AWAITING_RETRY"; - case BUGLE_STATUS_OUTGOING_FAILED: - return "OUTGOING_FAILED"; - case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER: - return "OUTGOING_FAILED_EMERGENCY_NUMBER"; - case BUGLE_STATUS_INCOMING_COMPLETE: - return "INCOMING_COMPLETE"; - case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD: - return "INCOMING_YET_TO_MANUAL_DOWNLOAD"; - case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - return "INCOMING_RETRYING_MANUAL_DOWNLOAD"; - case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING: - return "INCOMING_MANUAL_DOWNLOADING"; - case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - return "INCOMING_RETRYING_AUTO_DOWNLOAD"; - case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING: - return "INCOMING_AUTO_DOWNLOADING"; - case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED: - return "INCOMING_DOWNLOAD_FAILED"; - case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE: - return "INCOMING_EXPIRED_OR_NOT_AVAILABLE"; - default: - return String.valueOf(status) + " (check MessageData)"; - } - } - - // All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING - public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE; - - // Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are - // defined here instead. These are never returned in the MMS HTTP response, but are used - // internally. The values here must not conflict with any of the existing PduHeader values. - public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED; - public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000; - - // Unknown result code for MMS sending/downloading. This is used as the default value - // for result code returned from platform MMS API. - public static final int UNKNOWN_RESULT_CODE = 0; - - /** - * Create an "empty" message - */ - public MessageData() { - mParts = new ArrayList(); - } - - public static String[] getProjection() { - return sProjection; - } - - /** - * Create a draft message for a particular conversation based on supplied content - */ - public static MessageData createDraftMessage(final String conversationId, - final String selfId, final MessageData content) { - final MessageData message = new MessageData(); - message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT; - message.mProtocol = PROTOCOL_UNKNOWN; - message.mConversationId = conversationId; - message.mParticipantId = selfId; - message.mReceivedTimestamp = System.currentTimeMillis(); - if (content == null) { - message.mParts.add(MessagePartData.createTextMessagePart("")); - } else { - if (!TextUtils.isEmpty(content.mParticipantId)) { - message.mParticipantId = content.mParticipantId; - } - if (!TextUtils.isEmpty(content.mMmsSubject)) { - message.mMmsSubject = content.mMmsSubject; - } - for (final MessagePartData part : content.getParts()) { - message.mParts.add(part); - } - } - message.mSelfId = selfId; - return message; - } - - /** - * Create a draft sms message for a particular conversation - */ - public static MessageData createDraftSmsMessage(final String conversationId, - final String selfId, final String messageText) { - final MessageData message = new MessageData(); - message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT; - message.mProtocol = PROTOCOL_SMS; - message.mConversationId = conversationId; - message.mParticipantId = selfId; - message.mSelfId = selfId; - message.mParts.add(MessagePartData.createTextMessagePart(messageText)); - message.mReceivedTimestamp = System.currentTimeMillis(); - return message; - } - - /** - * Create a draft mms message for a particular conversation - */ - public static MessageData createDraftMmsMessage(final String conversationId, - final String selfId, final String messageText, final String subjectText) { - final MessageData message = new MessageData(); - message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT; - message.mProtocol = PROTOCOL_MMS; - message.mConversationId = conversationId; - message.mParticipantId = selfId; - message.mSelfId = selfId; - message.mMmsSubject = subjectText; - message.mReceivedTimestamp = System.currentTimeMillis(); - if (!TextUtils.isEmpty(messageText)) { - message.mParts.add(MessagePartData.createTextMessagePart(messageText)); - } - return message; - } - - /** - * Create a message received from a particular number in a particular conversation - */ - public static MessageData createReceivedSmsMessage(final Uri uri, final String conversationId, - final String participantId, final String selfId, final String messageText, - final String subject, final long sent, final long recieved, - final boolean seen, final boolean read) { - final MessageData message = new MessageData(); - message.mSmsMessageUri = uri; - message.mConversationId = conversationId; - message.mParticipantId = participantId; - message.mSelfId = selfId; - message.mProtocol = PROTOCOL_SMS; - message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE; - message.mMmsSubject = subject; - message.mReceivedTimestamp = recieved; - message.mSentTimestamp = sent; - message.mParts.add(MessagePartData.createTextMessagePart(messageText)); - message.mSeen = seen; - message.mRead = read; - return message; - } - - /** - * Create a message not yet associated with a particular conversation - */ - public static MessageData createSharedMessage(final String messageText) { - final MessageData message = new MessageData(); - message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT; - if (!TextUtils.isEmpty(messageText)) { - message.mParts.add(MessagePartData.createTextMessagePart(messageText)); - } - return message; - } - - /** - * Create a message from Sms table fields - */ - public static MessageData createSmsMessage(final String messageUri, final String participantId, - final String selfId, final String conversationId, final int bugleStatus, - final boolean seen, final boolean read, final long sent, - final long recieved, final String messageText) { - final MessageData message = new MessageData(); - message.mParticipantId = participantId; - message.mSelfId = selfId; - message.mConversationId = conversationId; - message.mSentTimestamp = sent; - message.mReceivedTimestamp = recieved; - message.mSeen = seen; - message.mRead = read; - message.mProtocol = PROTOCOL_SMS; - message.mStatus = bugleStatus; - message.mSmsMessageUri = Uri.parse(messageUri); - message.mParts.add(MessagePartData.createTextMessagePart(messageText)); - return message; - } - - /** - * Create a message from Mms table fields - */ - public static MessageData createMmsMessage(final String messageUri, final String participantId, - final String selfId, final String conversationId, final boolean isNotification, - final int bugleStatus, final String contentLocation, final String transactionId, - final int smsPriority, final String subject, final boolean seen, final boolean read, - final long size, final int rawStatus, final long expiry, final long sent, - final long received) { - final MessageData message = new MessageData(); - message.mParticipantId = participantId; - message.mSelfId = selfId; - message.mConversationId = conversationId; - message.mSentTimestamp = sent; - message.mReceivedTimestamp = received; - message.mMmsContentLocation = contentLocation; - message.mMmsTransactionId = transactionId; - message.mSeen = seen; - message.mRead = read; - message.mStatus = bugleStatus; - message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS); - message.mSmsMessageUri = Uri.parse(messageUri); - message.mSmsPriority = smsPriority; - message.mSmsMessageSize = size; - message.mMmsSubject = subject; - message.mMmsExpiry = expiry; - message.mRawStatus = rawStatus; - if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD || - bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) { - // Set the retry start timestamp if this message is already in process of retrying - // Either as autodownload is starting or sending already in progress (MMS update) - message.mRetryStartTimestamp = received; - } - return message; - } - - public void addPart(final MessagePartData part) { - if (part instanceof PendingAttachmentData) { - // Pending attachments may only be added to shared message data that's not associated - // with any particular conversation, in order to store shared images. - Assert.isTrue(mConversationId == null); - } - mParts.add(part); - } - - public Iterable getParts() { - return mParts; - } - - public void bind(final Cursor cursor) { - mMessageId = cursor.getString(INDEX_ID); - mConversationId = cursor.getString(INDEX_CONVERSATION_ID); - mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID); - mSelfId = cursor.getString(INDEX_SELF_ID); - mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP); - mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP); - mSeen = (cursor.getInt(INDEX_SEEN) != 0); - mRead = (cursor.getInt(INDEX_READ) != 0); - mProtocol = cursor.getInt(INDEX_PROTOCOL); - mStatus = cursor.getInt(INDEX_BUGLE_STATUS); - final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI); - mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri); - mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY); - mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE); - mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY); - mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS); - mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT); - mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID); - mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION); - mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP); - } - - /** - * Bind to the draft message data for a conversation. The conversation's self id is used as - * the draft's self id. - */ - public void bindDraft(final Cursor cursor, final String conversationSelfId) { - bind(cursor); - mSelfId = conversationSelfId; - } - - protected static String getParticipantId(final Cursor cursor) { - return cursor.getString(INDEX_PARTICIPANT_ID); - } - - public void populate(final ContentValues values) { - values.put(MessageColumns.CONVERSATION_ID, mConversationId); - values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId); - values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId); - values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp); - values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp); - values.put(MessageColumns.SEEN, mSeen ? 1 : 0); - values.put(MessageColumns.READ, mRead ? 1 : 0); - values.put(MessageColumns.PROTOCOL, mProtocol); - values.put(MessageColumns.STATUS, mStatus); - final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString()); - values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri); - values.put(MessageColumns.SMS_PRIORITY, mSmsPriority); - values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize); - values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry); - values.put(MessageColumns.MMS_SUBJECT, mMmsSubject); - values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId); - values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation); - values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus); - values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp); - } - - /** - * Note this is not thread safe so callers need to make sure they own the wrapper + statements - * while they call this and use the returned value. - */ - public SQLiteStatement getInsertStatement(final DatabaseWrapper db) { - final SQLiteStatement insert = db.getStatementInTransaction( - DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL); - insert.clearBindings(); - insert.bindString(INDEX_CONVERSATION_ID, mConversationId); - insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId); - insert.bindString(INDEX_SELF_ID, mSelfId); - insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp); - insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp); - insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0); - insert.bindLong(INDEX_READ, mRead ? 1 : 0); - insert.bindLong(INDEX_PROTOCOL, mProtocol); - insert.bindLong(INDEX_BUGLE_STATUS, mStatus); - if (mSmsMessageUri != null) { - insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString()); - } - insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority); - insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize); - insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry); - if (mMmsSubject != null) { - insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject); - } - if (mMmsTransactionId != null) { - insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId); - } - if (mMmsContentLocation != null) { - insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation); - } - insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus); - insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp); - return insert; - } - - public final String getMessageId() { - return mMessageId; - } - - public final String getConversationId() { - return mConversationId; - } - - public final String getParticipantId() { - return mParticipantId; - } - - public final String getSelfId() { - return mSelfId; - } - - public final long getSentTimeStamp() { - return mSentTimestamp; - } - - public final long getReceivedTimeStamp() { - return mReceivedTimestamp; - } - - public final String getFormattedReceivedTimeStamp() { - return Dates.getMessageTimeString(mReceivedTimestamp).toString(); - } - - public final int getProtocol() { - return mProtocol; - } - - public final int getStatus() { - return mStatus; - } - - public final Uri getSmsMessageUri() { - return mSmsMessageUri; - } - - public final int getSmsPriority() { - return mSmsPriority; - } - - public final long getSmsMessageSize() { - return mSmsMessageSize; - } - - public final String getMmsSubject() { - return mMmsSubject; - } - - public final void setMmsSubject(final String subject) { - mMmsSubject = subject; - } - - public final String getMmsContentLocation() { - return mMmsContentLocation; - } - - public final String getMmsTransactionId() { - return mMmsTransactionId; - } - - public final boolean getMessageSeen() { - return mSeen; - } - - /** - * For incoming MMS messages this returns the retrieve-status value - * For sent MMS messages this returns the response-status value - * See PduHeaders.java for possible values - * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED - */ - public final int getRawTelephonyStatus() { - return mRawStatus; - } - - public final void setMessageSeen(final boolean hasSeen) { - mSeen = hasSeen; - } - - public final boolean getInResendWindow(final long now) { - final long maxAgeToResend = BugleGservices.get().getLong( - BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS, - BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT); - final long age = now - mRetryStartTimestamp; - return age < maxAgeToResend; - } - - public final boolean getInDownloadWindow(final long now) { - final long maxAgeToRedownload = BugleGservices.get().getLong( - BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS, - BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT); - final long age = now - mRetryStartTimestamp; - return age < maxAgeToRedownload; - } - - static boolean getShowDownloadMessage(final int status) { - if (OsUtil.isSecondaryUser()) { - // Secondary users can't download mms's. Mms's are downloaded by bugle running as the - // primary user. - return false; - } - // Should show option for manual download iff status is manual download or failed - return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || - status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || - // If debug is enabled, allow to download an expired or unavailable message. - (DebugUtils.isDebugEnabled() - && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); - } - - public boolean canDownloadMessage() { - if (OsUtil.isSecondaryUser()) { - // Secondary users can't download mms's. Mms's are downloaded by bugle running as the - // primary user. - return false; - } - // Can download iff status is retrying auto/manual downloading - return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD || - mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD); - } - - public boolean canRedownloadMessage() { - if (OsUtil.isSecondaryUser()) { - // Secondary users can't download mms's. Mms's are downloaded by bugle running as the - // primary user. - return false; - } - // Can redownload iff status is manual download not started or download failed - return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED || - mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD || - // If debug is enabled, allow to download an expired or unavailable message. - (DebugUtils.isDebugEnabled() - && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE)); - } - - static boolean getShowResendMessage(final int status) { - // Should show option to resend iff status is failed - return (status == BUGLE_STATUS_OUTGOING_FAILED); - } - - static boolean getOneClickResendMessage(final int status, final int rawStatus) { - // Should show option to resend iff status is failed - return (status == BUGLE_STATUS_OUTGOING_FAILED - && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED); - } - - public boolean canResendMessage() { - // Manual retry allowed only from failed - return (mStatus == BUGLE_STATUS_OUTGOING_FAILED); - } - - public boolean canSendMessage() { - // Sending messages must be in yet_to_send or awaiting_retry state - return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND || - mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY); - } - - public final boolean getYetToSend() { - return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND); - } - - public final boolean getIsMms() { - return mProtocol == MessageData.PROTOCOL_MMS - || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION; - } - - public static final boolean getIsMmsNotification(final int protocol) { - return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION); - } - - public final boolean getIsMmsNotification() { - return getIsMmsNotification(mProtocol); - } - - public static final boolean getIsSms(final int protocol) { - return protocol == (MessageData.PROTOCOL_SMS); - } - - public final boolean getIsSms() { - return getIsSms(mProtocol); - } - - public static boolean getIsIncoming(final int status) { - return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING); - } - - public boolean getIsIncoming() { - return getIsIncoming(mStatus); - } - - public long getRetryStartTimestamp() { - return mRetryStartTimestamp; - } - - public final String getMessageText() { - final String separator = System.getProperty("line.separator"); - final StringBuilder text = new StringBuilder(); - for (final MessagePartData part : mParts) { - if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) { - if (text.length() > 0) { - text.append(separator); - } - text.append(part.getText()); - } - } - return text.toString(); - } - - /** - * Takes all captions from attachments and adds them as a prefix to the first text part or - * appends a text part - */ - public final void consolidateText() { - final String separator = System.getProperty("line.separator"); - final StringBuilder captionText = new StringBuilder(); - MessagePartData firstTextPart = null; - int firstTextPartIndex = -1; - for (int i = 0; i < mParts.size(); i++) { - final MessagePartData part = mParts.get(i); - if (firstTextPart == null && !part.isAttachment()) { - firstTextPart = part; - firstTextPartIndex = i; - } - if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) { - if (captionText.length() > 0) { - captionText.append(separator); - } - captionText.append(part.getText()); - } - } - - if (captionText.length() == 0) { - // Nothing to consolidate - return; - } - - if (firstTextPart == null) { - addPart(MessagePartData.createTextMessagePart(captionText.toString())); - } else { - final String partText = firstTextPart.getText(); - if (partText.length() > 0) { - captionText.append(separator); - captionText.append(partText); - } - mParts.set(firstTextPartIndex, - MessagePartData.createTextMessagePart(captionText.toString())); - } - } - - public final MessagePartData getFirstAttachment() { - for (final MessagePartData part : mParts) { - if (part.isAttachment()) { - return part; - } - } - return null; - } - - /** - * Updates the messageId for this message. - * Can be used to reset the messageId prior to persisting (which will assign a new messageId) - * or can be called on a message that does not yet have a valid messageId to set it. - */ - public void updateMessageId(final String messageId) { - Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId)); - mMessageId = messageId; - - // TODO : This should probably also call updateMessageId on the message parts. We - // may also want to make messages effectively immutable once they have a valid message id. - } - - public final void updateSendingMessage(final String conversationId, final Uri messageUri, - final long timestamp) { - mConversationId = conversationId; - mSmsMessageUri = messageUri; - mRead = true; - mSeen = true; - mReceivedTimestamp = timestamp; - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND; - mRetryStartTimestamp = timestamp; - } - - public final void markMessageManualResend(final long timestamp) { - // Manual send updates timestamp and transitions back to initial sending status. - mReceivedTimestamp = timestamp; - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_SENDING; - } - - public final void markMessageSending(final long timestamp) { - // Initial send - mStatus = BUGLE_STATUS_OUTGOING_SENDING; - mSentTimestamp = timestamp; - } - - public final void markMessageResending(final long timestamp) { - // Auto resend of message - mStatus = BUGLE_STATUS_OUTGOING_RESENDING; - mSentTimestamp = timestamp; - } - - public final void markMessageSent(final long timestamp) { - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_COMPLETE; - } - - public final void markMessageFailed(final long timestamp) { - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_FAILED; - } - - public final void markMessageFailedEmergencyNumber(final long timestamp) { - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER; - } - - public final void markMessageNotSent(final long timestamp) { - mSentTimestamp = timestamp; - mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY; - } - - public final void updateSizesForImageParts() { - for (final MessagePartData part : getParts()) { - part.decodeAndSaveSizeIfImage(false /* saveToStorage */); - } - } - - public final void setRetryStartTimestamp(final long timestamp) { - mRetryStartTimestamp = timestamp; - } - - public final void setRawTelephonyStatus(final int rawStatus) { - mRawStatus = rawStatus; - } - - public boolean hasContent() { - return !TextUtils.isEmpty(mMmsSubject) || - getFirstAttachment() != null || - !TextUtils.isEmpty(getMessageText()); - } - - public final void bindSelfId(final String selfId) { - mSelfId = selfId; - } - - public final void bindParticipantId(final String participantId) { - mParticipantId = participantId; - } - - protected MessageData(final Parcel in) { - mMessageId = in.readString(); - mConversationId = in.readString(); - mParticipantId = in.readString(); - mSelfId = in.readString(); - mSentTimestamp = in.readLong(); - mReceivedTimestamp = in.readLong(); - mSeen = (in.readInt() != 0); - mRead = (in.readInt() != 0); - mProtocol = in.readInt(); - mStatus = in.readInt(); - final String smsMessageUri = in.readString(); - mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri)); - mSmsPriority = in.readInt(); - mSmsMessageSize = in.readLong(); - mMmsExpiry = in.readLong(); - mMmsSubject = in.readString(); - mMmsTransactionId = in.readString(); - mMmsContentLocation = in.readString(); - mRawStatus = in.readInt(); - mRetryStartTimestamp = in.readLong(); - - // Read parts - mParts = new ArrayList(); - final int partCount = in.readInt(); - for (int i = 0; i < partCount; i++) { - mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader())); - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeString(mMessageId); - dest.writeString(mConversationId); - dest.writeString(mParticipantId); - dest.writeString(mSelfId); - dest.writeLong(mSentTimestamp); - dest.writeLong(mReceivedTimestamp); - dest.writeInt(mRead ? 1 : 0); - dest.writeInt(mSeen ? 1 : 0); - dest.writeInt(mProtocol); - dest.writeInt(mStatus); - final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString(); - dest.writeString(smsMessageUri); - dest.writeInt(mSmsPriority); - dest.writeLong(mSmsMessageSize); - dest.writeLong(mMmsExpiry); - dest.writeString(mMmsSubject); - dest.writeString(mMmsTransactionId); - dest.writeString(mMmsContentLocation); - dest.writeInt(mRawStatus); - dest.writeLong(mRetryStartTimestamp); - - // Write parts - dest.writeInt(mParts.size()); - for (final MessagePartData messagePartData : mParts) { - dest.writeParcelable(messagePartData, flags); - } - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MessageData createFromParcel(final Parcel in) { - return new MessageData(in); - } - - @Override - public MessageData[] newArray(final int size) { - return new MessageData[size]; - } - }; - - @Override - public String toString() { - return toString(mMessageId, mParts); - } - - public static String toString(String messageId, List parts) { - StringBuilder sb = new StringBuilder(); - if (messageId != null) { - sb.append(messageId); - sb.append(": "); - } - for (MessagePartData part : parts) { - sb.append(part.toString()); - sb.append(" "); - } - return sb.toString(); - } -} diff --git a/src/com/android/messaging/datamodel/data/MessagePartData.java b/src/com/android/messaging/datamodel/data/MessagePartData.java deleted file mode 100644 index fffaca8..0000000 --- a/src/com/android/messaging/datamodel/data/MessagePartData.java +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteStatement; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.GifTranscoder; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UriUtil; - -import java.util.Arrays; -import java.util.concurrent.TimeUnit; - -/** - * Represents a single message part. Messages consist of one or more parts which may contain - * either text or media. - */ -public class MessagePartData implements Parcelable { - public static final int UNSPECIFIED_SIZE = MessagingContentProvider.UNSPECIFIED_SIZE; - public static final String[] ACCEPTABLE_IMAGE_TYPES = - new String[] { ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG, - ContentType.IMAGE_GIF }; - - private static final String[] sProjection = { - PartColumns._ID, - PartColumns.MESSAGE_ID, - PartColumns.TEXT, - PartColumns.CONTENT_URI, - PartColumns.CONTENT_TYPE, - PartColumns.WIDTH, - PartColumns.HEIGHT, - }; - - private static final int INDEX_ID = 0; - private static final int INDEX_MESSAGE_ID = 1; - private static final int INDEX_TEXT = 2; - private static final int INDEX_CONTENT_URI = 3; - private static final int INDEX_CONTENT_TYPE = 4; - private static final int INDEX_WIDTH = 5; - private static final int INDEX_HEIGHT = 6; - // This isn't part of the projection - private static final int INDEX_CONVERSATION_ID = 7; - - // SQL statement to insert a "complete" message part row (columns based on projection above). - private static final String INSERT_MESSAGE_PART_SQL = - "INSERT INTO " + DatabaseHelper.PARTS_TABLE + " ( " - + TextUtils.join(",", Arrays.copyOfRange(sProjection, 1, INDEX_CONVERSATION_ID)) - + ", " + PartColumns.CONVERSATION_ID - + ") VALUES (?, ?, ?, ?, ?, ?, ?)"; - - // Used for stuff that's ignored or arbitrarily compressed. - private static final long NO_MINIMUM_SIZE = 0; - - private String mPartId; - private String mMessageId; - private String mText; - private Uri mContentUri; - private String mContentType; - private int mWidth; - private int mHeight; - // This kind of part can only be attached once and with no other attachment - private boolean mSinglePartOnly; - - /** Transient data: true if destroy was already called */ - private boolean mDestroyed; - - /** - * Create an "empty" message part - */ - protected MessagePartData() { - this(null, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE); - } - - /** - * Create a populated text message part - */ - protected MessagePartData(final String messageText) { - this(null, messageText, ContentType.TEXT_PLAIN, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE, - false /*singlePartOnly*/); - } - - /** - * Create a populated attachment message part - */ - protected MessagePartData(final String contentType, final Uri contentUri, - final int width, final int height) { - this(null, null, contentType, contentUri, width, height, false /*singlePartOnly*/); - } - - /** - * Create a populated attachment message part, with additional caption text - */ - protected MessagePartData(final String messageText, final String contentType, - final Uri contentUri, final int width, final int height) { - this(null, messageText, contentType, contentUri, width, height, false /*singlePartOnly*/); - } - - /** - * Create a populated attachment message part, with additional caption text, single part only - */ - protected MessagePartData(final String messageText, final String contentType, - final Uri contentUri, final int width, final int height, final boolean singlePartOnly) { - this(null, messageText, contentType, contentUri, width, height, singlePartOnly); - } - - /** - * Create a populated message part - */ - private MessagePartData(final String messageId, final String messageText, - final String contentType, final Uri contentUri, final int width, final int height, - final boolean singlePartOnly) { - mMessageId = messageId; - mText = messageText; - mContentType = contentType; - mContentUri = contentUri; - mWidth = width; - mHeight = height; - mSinglePartOnly = singlePartOnly; - } - - /** - * Create a "text" message part - */ - public static MessagePartData createTextMessagePart(final String messageText) { - return new MessagePartData(messageText); - } - - /** - * Create a "media" message part - */ - public static MessagePartData createMediaMessagePart(final String contentType, - final Uri contentUri, final int width, final int height) { - return new MessagePartData(contentType, contentUri, width, height); - } - - /** - * Create a "media" message part with caption - */ - public static MessagePartData createMediaMessagePart(final String caption, - final String contentType, final Uri contentUri, final int width, final int height) { - return new MessagePartData(null, caption, contentType, contentUri, width, height, - false /*singlePartOnly*/ - ); - } - - /** - * Create an empty "text" message part - */ - public static MessagePartData createEmptyMessagePart() { - return new MessagePartData(""); - } - - /** - * Creates a new message part reading from the cursor - */ - public static MessagePartData createFromCursor(final Cursor cursor) { - final MessagePartData part = new MessagePartData(); - part.bind(cursor); - return part; - } - - public static String[] getProjection() { - return sProjection; - } - - /** - * Updates the part id. - * Can be used to reset the partId just prior to persisting (which will assign a new partId) - * or can be called on a part that does not yet have a valid part id to set it. - */ - public void updatePartId(final String partId) { - Assert.isTrue(TextUtils.isEmpty(partId) || TextUtils.isEmpty(mPartId)); - mPartId = partId; - } - - /** - * Updates the messageId for the part. - * Can be used to reset the messageId prior to persisting (which will assign a new messageId) - * or can be called on a part that does not yet have a valid messageId to set it. - */ - public void updateMessageId(final String messageId) { - Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId)); - mMessageId = messageId; - } - - protected static String getMessageId(final Cursor cursor) { - return cursor.getString(INDEX_MESSAGE_ID); - } - - protected void bind(final Cursor cursor) { - mPartId = cursor.getString(INDEX_ID); - mMessageId = cursor.getString(INDEX_MESSAGE_ID); - mText = cursor.getString(INDEX_TEXT); - mContentUri = UriUtil.uriFromString(cursor.getString(INDEX_CONTENT_URI)); - mContentType = cursor.getString(INDEX_CONTENT_TYPE); - mWidth = cursor.getInt(INDEX_WIDTH); - mHeight = cursor.getInt(INDEX_HEIGHT); - } - - public final void populate(final ContentValues values) { - // Must have a valid messageId on a part - Assert.isTrue(!TextUtils.isEmpty(mMessageId)); - values.put(PartColumns.MESSAGE_ID, mMessageId); - values.put(PartColumns.TEXT, mText); - values.put(PartColumns.CONTENT_URI, UriUtil.stringFromUri(mContentUri)); - values.put(PartColumns.CONTENT_TYPE, mContentType); - if (mWidth != UNSPECIFIED_SIZE) { - values.put(PartColumns.WIDTH, mWidth); - } - if (mHeight != UNSPECIFIED_SIZE) { - values.put(PartColumns.HEIGHT, mHeight); - } - } - - /** - * Note this is not thread safe so callers need to make sure they own the wrapper + statements - * while they call this and use the returned value. - */ - public SQLiteStatement getInsertStatement(final DatabaseWrapper db, - final String conversationId) { - final SQLiteStatement insert = db.getStatementInTransaction( - DatabaseWrapper.INDEX_INSERT_MESSAGE_PART, INSERT_MESSAGE_PART_SQL); - insert.clearBindings(); - insert.bindString(INDEX_MESSAGE_ID, mMessageId); - if (mText != null) { - insert.bindString(INDEX_TEXT, mText); - } - if (mContentUri != null) { - insert.bindString(INDEX_CONTENT_URI, mContentUri.toString()); - } - if (mContentType != null) { - insert.bindString(INDEX_CONTENT_TYPE, mContentType); - } - insert.bindLong(INDEX_WIDTH, mWidth); - insert.bindLong(INDEX_HEIGHT, mHeight); - insert.bindString(INDEX_CONVERSATION_ID, conversationId); - return insert; - } - - public final String getPartId() { - return mPartId; - } - - public final String getMessageId() { - return mMessageId; - } - - public final String getText() { - return mText; - } - - public final Uri getContentUri() { - return mContentUri; - } - - public boolean isAttachment() { - return mContentUri != null; - } - - public boolean isText() { - return ContentType.isTextType(mContentType); - } - - public boolean isImage() { - return ContentType.isImageType(mContentType); - } - - public boolean isMedia() { - return ContentType.isMediaType(mContentType); - } - - public boolean isVCard() { - return ContentType.isVCardType(mContentType); - } - - public boolean isAudio() { - return ContentType.isAudioType(mContentType); - } - - public boolean isVideo() { - return ContentType.isVideoType(mContentType); - } - - public final String getContentType() { - return mContentType; - } - - public final int getWidth() { - return mWidth; - } - - public final int getHeight() { - return mHeight; - } - - /** - * - * @return true if this part can only exist by itself, with no other attachments - */ - public boolean getSinglePartOnly() { - return mSinglePartOnly; - } - - @Override - public int describeContents() { - return 0; - } - - protected MessagePartData(final Parcel in) { - mMessageId = in.readString(); - mText = in.readString(); - mContentUri = UriUtil.uriFromString(in.readString()); - mContentType = in.readString(); - mWidth = in.readInt(); - mHeight = in.readInt(); - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - Assert.isTrue(!mDestroyed); - dest.writeString(mMessageId); - dest.writeString(mText); - dest.writeString(UriUtil.stringFromUri(mContentUri)); - dest.writeString(mContentType); - dest.writeInt(mWidth); - dest.writeInt(mHeight); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof MessagePartData)) { - return false; - } - - MessagePartData lhs = (MessagePartData) o; - return mWidth == lhs.mWidth && mHeight == lhs.mHeight && - TextUtils.equals(mMessageId, lhs.mMessageId) && - TextUtils.equals(mText, lhs.mText) && - TextUtils.equals(mContentType, lhs.mContentType) && - (mContentUri == null ? lhs.mContentUri == null - : mContentUri.equals(lhs.mContentUri)); - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + mWidth; - result = 31 * result + mHeight; - result = 31 * result + (mMessageId == null ? 0 : mMessageId.hashCode()); - result = 31 * result + (mText == null ? 0 : mText.hashCode()); - result = 31 * result + (mContentType == null ? 0 : mContentType.hashCode()); - result = 31 * result + (mContentUri == null ? 0 : mContentUri.hashCode()); - return result; - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MessagePartData createFromParcel(final Parcel in) { - return new MessagePartData(in); - } - - @Override - public MessagePartData[] newArray(final int size) { - return new MessagePartData[size]; - } - }; - - protected Uri shouldDestroy() { - // We should never double-destroy. - Assert.isTrue(!mDestroyed); - mDestroyed = true; - Uri contentUri = mContentUri; - mContentUri = null; - mContentType = null; - // Only destroy the image if it's staged in our scratch space. - if (!MediaScratchFileProvider.isMediaScratchSpaceUri(contentUri)) { - contentUri = null; - } - return contentUri; - } - - /** - * If application owns content associated with this part delete it (on background thread) - */ - public void destroyAsync() { - final Uri contentUri = shouldDestroy(); - if (contentUri != null) { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - Factory.get().getApplicationContext().getContentResolver().delete( - contentUri, null, null); - } - }); - } - } - - /** - * If application owns content associated with this part delete it - */ - public void destroySync() { - final Uri contentUri = shouldDestroy(); - if (contentUri != null) { - Factory.get().getApplicationContext().getContentResolver().delete( - contentUri, null, null); - } - } - - /** - * If this is an image part, decode the image header and potentially save the size to the db. - */ - public void decodeAndSaveSizeIfImage(final boolean saveToStorage) { - if (isImage()) { - final Rect imageSize = ImageUtils.decodeImageBounds( - Factory.get().getApplicationContext(), mContentUri); - if (imageSize.width() != ImageRequest.UNSPECIFIED_SIZE && - imageSize.height() != ImageRequest.UNSPECIFIED_SIZE) { - mWidth = imageSize.width(); - mHeight = imageSize.height(); - if (saveToStorage) { - UpdateMessagePartSizeAction.updateSize(mPartId, mWidth, mHeight); - } - } - } - } - - /** - * Computes the minimum size that this MessagePartData could be compressed/downsampled/encoded - * before sending to meet the maximum message size imposed by the carriers. This is used to - * determine right before sending a message whether a message could possibly be sent. If not - * then the user is given a chance to unselect some/all of the attachments. - * - * TODO: computing the minimum size could be expensive. Should we cache the - * computed value in db to be retrieved later? - * - * @return the carrier-independent minimum size, in bytes. - */ - @DoesNotRunOnMainThread - public long getMinimumSizeInBytesForSending() { - Assert.isNotMainThread(); - if (!isAttachment()) { - // No limit is imposed on non-attachment part (i.e. plain text), so treat it as zero. - return NO_MINIMUM_SIZE; - } else if (isImage()) { - // GIFs are resized by the native transcoder (exposed by GifTranscoder). - if (ImageUtils.isGif(mContentType, mContentUri)) { - final long originalImageSize = UriUtil.getContentSize(mContentUri); - // Wish we could save the size here, but we don't have a part id yet - decodeAndSaveSizeIfImage(false /* saveToStorage */); - return GifTranscoder.canBeTranscoded(mWidth, mHeight) ? - GifTranscoder.estimateFileSizeAfterTranscode(originalImageSize) - : originalImageSize; - } - // Other images should be arbitrarily resized by ImageResizer before sending. - return MmsUtils.MIN_IMAGE_BYTE_SIZE; - } else if (isAudio()) { - // Audios are already recorded with the lowest sampling settings (AMR_NB), so just - // return the file size as the minimum size. - return UriUtil.getContentSize(mContentUri); - } else if (isVideo()) { - final int mediaDurationMs = UriUtil.getMediaDurationMs(mContentUri); - return MmsUtils.MIN_VIDEO_BYTES_PER_SECOND * mediaDurationMs - / TimeUnit.SECONDS.toMillis(1); - } else if (isVCard()) { - // We can't compress vCards. - return UriUtil.getContentSize(mContentUri); - } else { - // This is some unknown media type that we don't know how to handle. Log an error - // and try sending it anyway. - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Unknown attachment type " + getContentType()); - return NO_MINIMUM_SIZE; - } - } - - @Override - public String toString() { - if (isText()) { - return LogUtil.sanitizePII(getText()); - } else { - return getContentType() + " (" + getContentUri() + ")"; - } - } - - /** - * - * @return true if this part can only exist by itself, with no other attachments - */ - public boolean isSinglePartOnly() { - return mSinglePartOnly; - } - - public void setSinglePartOnly(final boolean isSinglePartOnly) { - mSinglePartOnly = isSinglePartOnly; - } -} diff --git a/src/com/android/messaging/datamodel/data/ParticipantData.java b/src/com/android/messaging/datamodel/data/ParticipantData.java deleted file mode 100644 index 521c354..0000000 --- a/src/com/android/messaging/datamodel/data/ParticipantData.java +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.content.ContentValues; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Color; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v7.mms.MmsManager; -import android.telephony.SubscriptionInfo; -import android.text.TextUtils; - -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.TextUtil; - -/** - * A class that encapsulates all of the data for a specific participant in a conversation. - */ -public class ParticipantData implements Parcelable { - // We always use -1 as default/invalid sub id although system may give us anything negative - public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID; - - // This needs to be something apart from valid or DEFAULT_SELF_SUB_ID - public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1; - - // Active slot ids are non-negative. Using -1 to designate to inactive self participants. - public static final int INVALID_SLOT_ID = -1; - - // TODO: may make sense to move this to common place? - public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1; - public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2; - - public static class ParticipantsQuery { - public static final String[] PROJECTION = new String[] { - ParticipantColumns._ID, - ParticipantColumns.SUB_ID, - ParticipantColumns.SIM_SLOT_ID, - ParticipantColumns.NORMALIZED_DESTINATION, - ParticipantColumns.SEND_DESTINATION, - ParticipantColumns.DISPLAY_DESTINATION, - ParticipantColumns.FULL_NAME, - ParticipantColumns.FIRST_NAME, - ParticipantColumns.PROFILE_PHOTO_URI, - ParticipantColumns.CONTACT_ID, - ParticipantColumns.LOOKUP_KEY, - ParticipantColumns.BLOCKED, - ParticipantColumns.SUBSCRIPTION_COLOR, - ParticipantColumns.SUBSCRIPTION_NAME, - ParticipantColumns.CONTACT_DESTINATION, - }; - - public static final int INDEX_ID = 0; - public static final int INDEX_SUB_ID = 1; - public static final int INDEX_SIM_SLOT_ID = 2; - public static final int INDEX_NORMALIZED_DESTINATION = 3; - public static final int INDEX_SEND_DESTINATION = 4; - public static final int INDEX_DISPLAY_DESTINATION = 5; - public static final int INDEX_FULL_NAME = 6; - public static final int INDEX_FIRST_NAME = 7; - public static final int INDEX_PROFILE_PHOTO_URI = 8; - public static final int INDEX_CONTACT_ID = 9; - public static final int INDEX_LOOKUP_KEY = 10; - public static final int INDEX_BLOCKED = 11; - public static final int INDEX_SUBSCRIPTION_COLOR = 12; - public static final int INDEX_SUBSCRIPTION_NAME = 13; - public static final int INDEX_CONTACT_DESTINATION = 14; - } - - /** - * @return The MMS unknown sender participant entity - */ - public static String getUnknownSenderDestination() { - // This is a hard coded string rather than a localized one because we don't want it to - // change when you change locale. - return "\u02BCUNKNOWN_SENDER!\u02BC"; - } - - private String mParticipantId; - private int mSubId; - private int mSlotId; - private String mNormalizedDestination; - private String mSendDestination; - private String mDisplayDestination; - private String mContactDestination; - private String mFullName; - private String mFirstName; - private String mProfilePhotoUri; - private long mContactId; - private String mLookupKey; - private int mSubscriptionColor; - private String mSubscriptionName; - private boolean mIsEmailAddress; - private boolean mBlocked; - - // Don't call constructor directly - private ParticipantData() { - } - - public static ParticipantData getFromCursor(final Cursor cursor) { - final ParticipantData pd = new ParticipantData(); - pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID); - pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID); - pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID); - pd.mNormalizedDestination = cursor.getString( - ParticipantsQuery.INDEX_NORMALIZED_DESTINATION); - pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION); - pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION); - pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION); - pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME); - pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME); - pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI); - pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID); - pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY); - pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination); - pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0; - pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR); - pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME); - pd.maybeSetupUnknownSender(); - return pd; - } - - public static ParticipantData getFromId(final DatabaseWrapper dbWrapper, - final String participantId) { - Cursor cursor = null; - try { - cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantsQuery.PROJECTION, - ParticipantColumns._ID + " =?", - new String[] { participantId }, null, null, null); - - if (cursor.moveToFirst()) { - return ParticipantData.getFromCursor(cursor); - } else { - return null; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) { - final ParticipantData pd = new ParticipantData(); - pd.mParticipantId = null; - pd.mSubId = OTHER_THAN_SELF_SUB_ID; - pd.mSlotId = INVALID_SLOT_ID; - pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination()); - pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination); - pd.mNormalizedDestination = pd.mIsEmailAddress ? - pd.mSendDestination : - PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination); - pd.mDisplayDestination = pd.mIsEmailAddress ? - pd.mNormalizedDestination : - PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination); - pd.mFullName = recipientEntry.getDisplayName(); - pd.mFirstName = null; - pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null : - recipientEntry.getPhotoThumbnailUri().toString(); - pd.mContactId = recipientEntry.getContactId(); - if (pd.mContactId < 0) { - // ParticipantData only supports real contact ids (>=0) based on faith that the contacts - // provider will continue to only use non-negative ids. The UI uses contactId < 0 for - // special handling. We convert those to 'not resolved' - pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED; - } - pd.mLookupKey = recipientEntry.getLookupKey(); - pd.mBlocked = false; - pd.mSubscriptionColor = Color.TRANSPARENT; - pd.mSubscriptionName = null; - pd.maybeSetupUnknownSender(); - return pd; - } - - // Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale - private static ParticipantData getFromRawPhone(final String phoneNumber) { - Assert.isTrue(phoneNumber != null); - final ParticipantData pd = new ParticipantData(); - pd.mParticipantId = null; - pd.mSubId = OTHER_THAN_SELF_SUB_ID; - pd.mSlotId = INVALID_SLOT_ID; - pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber); - pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination); - pd.mFullName = null; - pd.mFirstName = null; - pd.mProfilePhotoUri = null; - pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED; - pd.mLookupKey = null; - pd.mBlocked = false; - pd.mSubscriptionColor = Color.TRANSPARENT; - pd.mSubscriptionName = null; - return pd; - } - - /** - * Get an instance from a raw phone number and using system locale to normalize it. - * - * Use this when creating a participant that is for displaying UI and not associated - * with a specific SIM. For example, when creating a conversation using user entered - * phone number. - * - * @param phoneNumber The raw phone number - * @return instance - */ - public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) { - final ParticipantData pd = getFromRawPhone(phoneNumber); - pd.mNormalizedDestination = pd.mIsEmailAddress ? - pd.mSendDestination : - PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination); - pd.mDisplayDestination = pd.mIsEmailAddress ? - pd.mNormalizedDestination : - PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination); - pd.maybeSetupUnknownSender(); - return pd; - } - - /** - * Get an instance from a raw phone number and using SIM or system locale to normalize it. - * - * Use this when creating a participant that is associated with a specific SIM. For example, - * the sender of a received message or the recipient of a sending message that is already - * targeted at a specific SIM. - * - * @param phoneNumber The raw phone number - * @return instance - */ - public static ParticipantData getFromRawPhoneBySimLocale( - final String phoneNumber, final int subId) { - final ParticipantData pd = getFromRawPhone(phoneNumber); - pd.mNormalizedDestination = pd.mIsEmailAddress ? - pd.mSendDestination : - PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination); - pd.mDisplayDestination = pd.mIsEmailAddress ? - pd.mNormalizedDestination : - PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination); - pd.maybeSetupUnknownSender(); - return pd; - } - - public static ParticipantData getSelfParticipant(final int subId) { - Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID); - final ParticipantData pd = new ParticipantData(); - pd.mParticipantId = null; - pd.mSubId = subId; - pd.mSlotId = INVALID_SLOT_ID; - pd.mIsEmailAddress = false; - pd.mSendDestination = null; - pd.mNormalizedDestination = null; - pd.mDisplayDestination = null; - pd.mFullName = null; - pd.mFirstName = null; - pd.mProfilePhotoUri = null; - pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED; - pd.mLookupKey = null; - pd.mBlocked = false; - pd.mSubscriptionColor = Color.TRANSPARENT; - pd.mSubscriptionName = null; - return pd; - } - - private void maybeSetupUnknownSender() { - if (isUnknownSender()) { - // Because your locale may change, we setup the display string for the unknown sender - // on the fly rather than relying on the version in the database. - final Resources resources = Factory.get().getApplicationContext().getResources(); - mDisplayDestination = resources.getString(R.string.unknown_sender); - mFullName = mDisplayDestination; - } - } - - public String getNormalizedDestination() { - return mNormalizedDestination; - } - - public String getSendDestination() { - return mSendDestination; - } - - public String getDisplayDestination() { - return mDisplayDestination; - } - - public String getContactDestination() { - return mContactDestination; - } - - public String getFullName() { - return mFullName; - } - - public String getFirstName() { - return mFirstName; - } - - public String getDisplayName(final boolean preferFullName) { - if (preferFullName) { - // Prefer full name over first name - if (!TextUtils.isEmpty(mFullName)) { - return mFullName; - } - if (!TextUtils.isEmpty(mFirstName)) { - return mFirstName; - } - } else { - // Prefer first name over full name - if (!TextUtils.isEmpty(mFirstName)) { - return mFirstName; - } - if (!TextUtils.isEmpty(mFullName)) { - return mFullName; - } - } - - // Fallback to the display destination - if (!TextUtils.isEmpty(mDisplayDestination)) { - return mDisplayDestination; - } - - return Factory.get().getApplicationContext().getResources().getString( - R.string.unknown_sender); - } - - public String getProfilePhotoUri() { - return mProfilePhotoUri; - } - - public long getContactId() { - return mContactId; - } - - public String getLookupKey() { - return mLookupKey; - } - - public boolean updatePhoneNumberForSelfIfChanged() { - final String phoneNumber = - PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/); - boolean changed = false; - if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) { - mNormalizedDestination = phoneNumber; - mSendDestination = phoneNumber; - mDisplayDestination = mIsEmailAddress ? - phoneNumber : - PhoneUtils.getDefault().formatForDisplay(phoneNumber); - changed = true; - } - return changed; - } - - public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) { - boolean changed = false; - if (isSelf()) { - if (subscriptionInfo == null) { - // The subscription is inactive. Check if the participant is still active. - if (isActiveSubscription()) { - mSlotId = INVALID_SLOT_ID; - mSubscriptionColor = Color.TRANSPARENT; - mSubscriptionName = ""; - changed = true; - } - } else { - final int slotId = subscriptionInfo.getSimSlotIndex(); - final int color = subscriptionInfo.getIconTint(); - final CharSequence name = subscriptionInfo.getDisplayName(); - if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) { - mSlotId = slotId; - mSubscriptionColor = color; - mSubscriptionName = name.toString(); - changed = true; - } - } - } - return changed; - } - - public void setFullName(final String fullName) { - mFullName = fullName; - } - - public void setFirstName(final String firstName) { - mFirstName = firstName; - } - - public void setProfilePhotoUri(final String profilePhotoUri) { - mProfilePhotoUri = profilePhotoUri; - } - - public void setContactId(final long contactId) { - mContactId = contactId; - } - - public void setLookupKey(final String lookupKey) { - mLookupKey = lookupKey; - } - - public void setSendDestination(final String destination) { - mSendDestination = destination; - } - - public void setContactDestination(final String destination) { - mContactDestination = destination; - } - - public int getSubId() { - return mSubId; - } - - /** - * @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is - * is considered as active if there is any active SIM. - */ - public boolean isActiveSubscription() { - return mSlotId != INVALID_SLOT_ID; - } - - public boolean isDefaultSelf() { - return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID; - } - - public int getSlotId() { - return mSlotId; - } - - /** - * Slot IDs in the subscription manager is zero-based, but we want to show it - * as 1-based in UI. - */ - public int getDisplaySlotId() { - return getSlotId() + 1; - } - - public int getSubscriptionColor() { - Assert.isTrue(isActiveSubscription()); - // Force the alpha channel to 0xff to ensure the returned color is solid. - return mSubscriptionColor | 0xff000000; - } - - public String getSubscriptionName() { - Assert.isTrue(isActiveSubscription()); - return mSubscriptionName; - } - - public String getId() { - return mParticipantId; - } - - public boolean isSelf() { - return (mSubId != OTHER_THAN_SELF_SUB_ID); - } - - public boolean isEmail() { - return mIsEmailAddress; - } - - public boolean isContactIdResolved() { - return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED); - } - - public boolean isBlocked() { - return mBlocked; - } - - public boolean isUnknownSender() { - final String unknownSender = ParticipantData.getUnknownSenderDestination(); - return (TextUtils.equals(mSendDestination, unknownSender)); - } - - public ContentValues toContentValues() { - final ContentValues values = new ContentValues(); - values.put(ParticipantColumns.SUB_ID, mSubId); - values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId); - values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination); - - if (!isUnknownSender()) { - values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination); - values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION, - mNormalizedDestination); - values.put(ParticipantColumns.FULL_NAME, mFullName); - values.put(ParticipantColumns.FIRST_NAME, mFirstName); - } - - values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri); - values.put(ParticipantColumns.CONTACT_ID, mContactId); - values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey); - values.put(ParticipantColumns.BLOCKED, mBlocked); - values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor); - values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName); - return values; - } - - public ParticipantData(final Parcel in) { - mParticipantId = in.readString(); - mSubId = in.readInt(); - mSlotId = in.readInt(); - mNormalizedDestination = in.readString(); - mSendDestination = in.readString(); - mDisplayDestination = in.readString(); - mFullName = in.readString(); - mFirstName = in.readString(); - mProfilePhotoUri = in.readString(); - mContactId = in.readLong(); - mLookupKey = in.readString(); - mIsEmailAddress = in.readInt() != 0; - mBlocked = in.readInt() != 0; - mSubscriptionColor = in.readInt(); - mSubscriptionName = in.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeString(mParticipantId); - dest.writeInt(mSubId); - dest.writeInt(mSlotId); - dest.writeString(mNormalizedDestination); - dest.writeString(mSendDestination); - dest.writeString(mDisplayDestination); - dest.writeString(mFullName); - dest.writeString(mFirstName); - dest.writeString(mProfilePhotoUri); - dest.writeLong(mContactId); - dest.writeString(mLookupKey); - dest.writeInt(mIsEmailAddress ? 1 : 0); - dest.writeInt(mBlocked ? 1 : 0); - dest.writeInt(mSubscriptionColor); - dest.writeString(mSubscriptionName); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ParticipantData createFromParcel(final Parcel in) { - return new ParticipantData(in); - } - - @Override - public ParticipantData[] newArray(final int size) { - return new ParticipantData[size]; - } - }; -} diff --git a/src/com/android/messaging/datamodel/data/ParticipantListItemData.java b/src/com/android/messaging/datamodel/data/ParticipantListItemData.java deleted file mode 100644 index f6c9b5f..0000000 --- a/src/com/android/messaging/datamodel/data/ParticipantListItemData.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.text.TextUtils; - -import com.android.messaging.datamodel.action.BugleActionToasts; -import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction; -import com.android.messaging.util.AvatarUriUtil; - -/** - * Helps visualize a ParticipantData in a PersonItemView - */ -public class ParticipantListItemData extends PersonItemData { - private final Uri mAvatarUri; - private final String mDisplayName; - private final String mDetails; - private final long mContactId; - private final String mLookupKey; - private final String mNormalizedDestination; - - /** - * Constructor. Takes necessary info from the incoming ParticipantData. - */ - public ParticipantListItemData(final ParticipantData participant) { - mAvatarUri = AvatarUriUtil.createAvatarUri(participant); - mContactId = participant.getContactId(); - mLookupKey = participant.getLookupKey(); - mNormalizedDestination = participant.getNormalizedDestination(); - if (TextUtils.isEmpty(participant.getFullName())) { - mDisplayName = participant.getSendDestination(); - mDetails = null; - } else { - mDisplayName = participant.getFullName(); - mDetails = (participant.isUnknownSender()) ? null : participant.getSendDestination(); - } - } - - @Override - public Uri getAvatarUri() { - return mAvatarUri; - } - - @Override - public String getDisplayName() { - return mDisplayName; - } - - @Override - public String getDetails() { - return mDetails; - } - - @Override - public Intent getClickIntent() { - return null; - } - - @Override - public long getContactId() { - return mContactId; - } - - @Override - public String getLookupKey() { - return mLookupKey; - } - - @Override - public String getNormalizedDestination() { - return mNormalizedDestination; - } - - public void unblock(final Context context) { - UpdateDestinationBlockedAction.updateDestinationBlocked( - mNormalizedDestination, false, null, - BugleActionToasts.makeUpdateDestinationBlockedActionListener(context)); - } -} diff --git a/src/com/android/messaging/datamodel/data/PendingAttachmentData.java b/src/com/android/messaging/datamodel/data/PendingAttachmentData.java deleted file mode 100644 index 5e079f8..0000000 --- a/src/com/android/messaging/datamodel/data/PendingAttachmentData.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.NonNull; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UriUtil; - -/** - * Represents a "pending" message part that acts as a placeholder for the actual attachment being - * loaded. It handles the task to load and persist the attachment from a Uri to local scratch - * folder. This item is not persisted to the database. - */ -public class PendingAttachmentData extends MessagePartData { - /** The pending state. This is the initial state where we haven't started loading yet */ - public static final int STATE_PENDING = 0; - - /** The state for when we are currently loading the attachment to the scratch space */ - public static final int STATE_LOADING = 1; - - /** The attachment has been successfully loaded and no longer pending */ - public static final int STATE_LOADED = 2; - - /** The attachment failed to load */ - public static final int STATE_FAILED = 3; - - private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000; // 60s - - /** The current state of the pending attachment. Refer to the STATE_* states above */ - private int mCurrentState; - - /** - * Create a new instance of PendingAttachmentData with an output Uri. - * @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote, - * so we need to persist it to local storage. - */ - protected PendingAttachmentData(final String caption, final String contentType, - @NonNull final Uri sourceUri, final int width, final int height, - final boolean onlySingleAttachment) { - super(caption, contentType, sourceUri, width, height, onlySingleAttachment); - mCurrentState = STATE_PENDING; - } - - /** - * Creates a pending attachment data that is able to load from the given source uri and - * persist the media resource locally in the scratch folder. - */ - public static PendingAttachmentData createPendingAttachmentData(final String contentType, - final Uri sourceUri) { - return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE, - UNSPECIFIED_SIZE); - } - - public static PendingAttachmentData createPendingAttachmentData(final String caption, - final String contentType, final Uri sourceUri, final int width, final int height) { - Assert.isTrue(ContentType.isMediaType(contentType)); - return new PendingAttachmentData(caption, contentType, sourceUri, width, height, - false /*onlySingleAttachment*/); - } - - public static PendingAttachmentData createPendingAttachmentData(final String caption, - final String contentType, final Uri sourceUri, final int width, final int height, - final boolean onlySingleAttachment) { - Assert.isTrue(ContentType.isMediaType(contentType)); - return new PendingAttachmentData(caption, contentType, sourceUri, width, height, - onlySingleAttachment); - } - - public int getCurrentState() { - return mCurrentState; - } - - public void loadAttachmentForDraft(final DraftMessageData draftMessageData, - final String bindingId) { - if (mCurrentState != STATE_PENDING) { - return; - } - mCurrentState = STATE_LOADING; - - // Kick off a SafeAsyncTask to load the content of the media and persist it locally. - // Note: we need to persist the media locally even if it's not remote, because we - // want to be able to resend the media in case the message failed to send. - new SafeAsyncTask(LOAD_MEDIA_TIME_LIMIT_MILLIS, - true /* cancelExecutionOnTimeout */) { - @Override - protected MessagePartData doInBackgroundTimed(final Void... params) { - final Uri contentUri = getContentUri(); - final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri); - if (persistedUri != null) { - return MessagePartData.createMediaMessagePart( - getText(), - getContentType(), - persistedUri, - getWidth(), - getHeight()); - } - return null; - } - - @Override - protected void onCancelled() { - LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media"); - mCurrentState = STATE_FAILED; - if (draftMessageData.isBound(bindingId)) { - draftMessageData.removePendingAttachment(PendingAttachmentData.this); - } - } - - @Override - protected void onPostExecute(final MessagePartData attachment) { - if (attachment != null) { - mCurrentState = STATE_LOADED; - if (draftMessageData.isBound(bindingId)) { - draftMessageData.updatePendingAttachment(attachment, - PendingAttachmentData.this); - } else { - // The draft message data is no longer bound, drop the loaded attachment. - attachment.destroyAsync(); - } - } else { - // Media load failed. We already logged in doInBackground() so don't need to - // do that again. - mCurrentState = STATE_FAILED; - if (draftMessageData.isBound(bindingId)) { - draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this); - draftMessageData.removePendingAttachment(PendingAttachmentData.this); - } - } - } - }.executeOnThreadPool(); - } - - protected PendingAttachmentData(final Parcel in) { - super(in); - mCurrentState = in.readInt(); - } - - @Override - public void writeToParcel(final Parcel out, final int flags) { - super.writeToParcel(out, flags); - out.writeInt(mCurrentState); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public PendingAttachmentData createFromParcel(final Parcel in) { - return new PendingAttachmentData(in); - } - - @Override - public PendingAttachmentData[] newArray(final int size) { - return new PendingAttachmentData[size]; - } - }; -} diff --git a/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java b/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java deleted file mode 100644 index 650a037..0000000 --- a/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; - -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.BugleActionToasts; -import com.android.messaging.datamodel.action.UpdateConversationOptionsAction; -import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.List; - -/** - * Services data needs for PeopleAndOptionsFragment. - */ -public class PeopleAndOptionsData extends BindableData implements - LoaderManager.LoaderCallbacks { - public interface PeopleAndOptionsDataListener { - void onOptionsCursorUpdated(PeopleAndOptionsData data, Cursor cursor); - void onParticipantsListLoaded(PeopleAndOptionsData data, - List participants); - } - - private static final String BINDING_ID = "bindingId"; - private final Context mContext; - private final String mConversationId; - private final ConversationParticipantsData mParticipantData; - private LoaderManager mLoaderManager; - private PeopleAndOptionsDataListener mListener; - - public PeopleAndOptionsData(final String conversationId, final Context context, - final PeopleAndOptionsDataListener listener) { - mListener = listener; - mContext = context; - mConversationId = conversationId; - mParticipantData = new ConversationParticipantsData(); - } - - private static final int CONVERSATION_OPTIONS_LOADER = 1; - private static final int PARTICIPANT_LOADER = 2; - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - switch (id) { - case CONVERSATION_OPTIONS_LOADER: { - final Uri uri = - MessagingContentProvider.buildConversationMetadataUri(mConversationId); - return new BoundCursorLoader(bindingId, mContext, uri, - PeopleOptionsItemData.PROJECTION, null, null, null); - } - - case PARTICIPANT_LOADER: { - final Uri uri = - MessagingContentProvider - .buildConversationParticipantsUri(mConversationId); - return new BoundCursorLoader(bindingId, mContext, uri, - ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); - } - - default: - Assert.fail("Unknown loader id for PeopleAndOptionsFragment!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding PeopleAndOptionsFragment"); - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoadFinished(final Loader loader, final Cursor data) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case CONVERSATION_OPTIONS_LOADER: - mListener.onOptionsCursorUpdated(this, data); - break; - - case PARTICIPANT_LOADER: - mParticipantData.bind(data); - mListener.onParticipantsListLoaded(this, - mParticipantData.getParticipantListExcludingSelf()); - break; - - default: - Assert.fail("Unknown loader id for PeopleAndOptionsFragment!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, - "Loader finished after unbinding PeopleAndOptionsFragment"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLoaderReset(final Loader loader) { - final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader; - if (isBound(cursorLoader.getBindingId())) { - switch (loader.getId()) { - case CONVERSATION_OPTIONS_LOADER: - mListener.onOptionsCursorUpdated(this, null); - break; - - case PARTICIPANT_LOADER: - mParticipantData.bind(null); - break; - - default: - Assert.fail("Unknown loader id for PeopleAndOptionsFragment!"); - break; - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding PeopleAndOptionsFragment"); - } - } - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - final Bundle args = new Bundle(); - args.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(CONVERSATION_OPTIONS_LOADER, args, this); - mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this); - } - - @Override - protected void unregisterListeners() { - mListener = null; - - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(CONVERSATION_OPTIONS_LOADER); - mLoaderManager.destroyLoader(PARTICIPANT_LOADER); - mLoaderManager = null; - } - } - - public void enableConversationNotifications(final BindingBase binding, - final boolean enable) { - final String bindingId = binding.getBindingId(); - if (isBound(bindingId)) { - UpdateConversationOptionsAction.enableConversationNotifications( - mConversationId, enable); - } - } - - public void setConversationNotificationSound(final BindingBase binding, - final String ringtoneUri) { - final String bindingId = binding.getBindingId(); - if (isBound(bindingId)) { - UpdateConversationOptionsAction.setConversationNotificationSound(mConversationId, - ringtoneUri); - } - } - - public void enableConversationNotificationVibration( - final BindingBase binding, final boolean enable) { - final String bindingId = binding.getBindingId(); - if (isBound(bindingId)) { - UpdateConversationOptionsAction.enableVibrationForConversationNotification( - mConversationId, enable); - } - } - - public void setDestinationBlocked(final BindingBase binding, - final boolean blocked) { - final String bindingId = binding.getBindingId(); - final ParticipantData participantData = mParticipantData.getOtherParticipant(); - if (isBound(bindingId) && participantData != null) { - UpdateDestinationBlockedAction.updateDestinationBlocked( - participantData.getNormalizedDestination(), - blocked, mConversationId, - BugleActionToasts.makeUpdateDestinationBlockedActionListener(mContext)); - } - } -} diff --git a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java deleted file mode 100644 index 5af6a30..0000000 --- a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.content.Context; -import android.database.Cursor; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns; -import com.android.messaging.util.Assert; -import com.android.messaging.util.RingtoneUtil; - -public class PeopleOptionsItemData { - public static final String[] PROJECTION = { - ConversationListViewColumns.NOTIFICATION_ENABLED, - ConversationListViewColumns.NOTIFICATION_SOUND_URI, - ConversationListViewColumns.NOTIFICATION_VIBRATION, - }; - - // Column index for query projection. - private static final int INDEX_NOTIFICATION_ENABLED = 0; - private static final int INDEX_NOTIFICATION_SOUND_URI = 1; - private static final int INDEX_NOTIFICATION_VIBRATION = 2; - - // Identification for each setting that's surfaced to the UI layer. - public static final int SETTING_NOTIFICATION_ENABLED = 0; - public static final int SETTING_NOTIFICATION_SOUND_URI = 1; - public static final int SETTING_NOTIFICATION_VIBRATION = 2; - public static final int SETTING_BLOCKED = 3; - public static final int SETTINGS_COUNT = 4; - - // Type of UI switch to show for the toggle button. - public static final int TOGGLE_TYPE_CHECKBOX = 0; - public static final int TOGGLE_TYPE_SWITCH = 1; - - private String mTitle; - private String mSubtitle; - private Uri mRingtoneUri; - private boolean mCheckable; - private boolean mChecked; - private boolean mEnabled; - private int mItemId; - private ParticipantData mOtherParticipant; - - private final Context mContext; - - public PeopleOptionsItemData(final Context context) { - mContext = context; - } - - /** - * Bind to a specific setting column on conversation metadata cursor. (Note - * that it binds to columns because it treats individual columns of the cursor as - * separate options to display for the conversation, e.g. notification settings). - */ - public void bind( - final Cursor cursor, final ParticipantData otherParticipant, final int settingType) { - mSubtitle = null; - mRingtoneUri = null; - mCheckable = true; - mEnabled = true; - mItemId = settingType; - mOtherParticipant = otherParticipant; - - final boolean notificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1; - switch (settingType) { - case SETTING_NOTIFICATION_ENABLED: - mTitle = mContext.getString(R.string.notifications_enabled_conversation_pref_title); - mChecked = notificationEnabled; - break; - - case SETTING_NOTIFICATION_SOUND_URI: - mTitle = mContext.getString(R.string.notification_sound_pref_title); - final String ringtoneString = cursor.getString(INDEX_NOTIFICATION_SOUND_URI); - Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(ringtoneString); - - mSubtitle = mContext.getString(R.string.silent_ringtone); - if (ringtoneUri != null) { - final Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri); - if (ringtone != null) { - mSubtitle = ringtone.getTitle(mContext); - } - } - mCheckable = false; - mRingtoneUri = ringtoneUri; - mEnabled = notificationEnabled; - break; - - case SETTING_NOTIFICATION_VIBRATION: - mTitle = mContext.getString(R.string.notification_vibrate_pref_title); - mChecked = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1; - mEnabled = notificationEnabled; - break; - - case SETTING_BLOCKED: - Assert.notNull(otherParticipant); - final int resourceId = otherParticipant.isBlocked() ? - R.string.unblock_contact_title : R.string.block_contact_title; - mTitle = mContext.getString(resourceId, otherParticipant.getDisplayDestination()); - mCheckable = false; - break; - - default: - Assert.fail("Unsupported conversation option type!"); - } - } - - public String getTitle() { - return mTitle; - } - - public String getSubtitle() { - return mSubtitle; - } - - public boolean getCheckable() { - return mCheckable; - } - - public boolean getChecked() { - return mChecked; - } - - public boolean getEnabled() { - return mEnabled; - } - - public int getItemId() { - return mItemId; - } - - public Uri getRingtoneUri() { - return mRingtoneUri; - } - - public ParticipantData getOtherParticipant() { - return mOtherParticipant; - } -} diff --git a/src/com/android/messaging/datamodel/data/PersonItemData.java b/src/com/android/messaging/datamodel/data/PersonItemData.java deleted file mode 100644 index a0a1ce8..0000000 --- a/src/com/android/messaging/datamodel/data/PersonItemData.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.content.Intent; -import android.net.Uri; - -import com.android.messaging.datamodel.binding.BindableData; - -/** - * Bridges between any particpant/contact related data and data displayed in the PersonItemView. - */ -public abstract class PersonItemData extends BindableData { - /** - * The UI component that listens for data change and update accordingly. - */ - public interface PersonItemDataListener { - void onPersonDataUpdated(PersonItemData data); - void onPersonDataFailed(PersonItemData data, Exception exception); - } - - private PersonItemDataListener mListener; - - public abstract Uri getAvatarUri(); - public abstract String getDisplayName(); - public abstract String getDetails(); - public abstract Intent getClickIntent(); - public abstract long getContactId(); - public abstract String getLookupKey(); - public abstract String getNormalizedDestination(); - - public void setListener(final PersonItemDataListener listener) { - if (isBound()) { - mListener = listener; - } - } - - protected void notifyDataUpdated() { - if (isBound() && mListener != null) { - mListener.onPersonDataUpdated(this); - } - } - - protected void notifyDataFailed(final Exception exception) { - if (isBound() && mListener != null) { - mListener.onPersonDataFailed(this, exception); - } - } - - @Override - protected void unregisterListeners() { - mListener = null; - } -} diff --git a/src/com/android/messaging/datamodel/data/SelfParticipantsData.java b/src/com/android/messaging/datamodel/data/SelfParticipantsData.java deleted file mode 100644 index 43302ed..0000000 --- a/src/com/android/messaging/datamodel/data/SelfParticipantsData.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.database.Cursor; -import android.support.v4.util.ArrayMap; - -import java.util.ArrayList; -import java.util.List; - -import com.android.messaging.util.OsUtil; - -/** - * A class that contains the list of all self participants potentially involved in a conversation. - * This class contains both active/inactive self entries when there is multi-SIM support. - */ -public class SelfParticipantsData { - /** - * The map from self participant ids to self-participant data entries in the participants table. - * This includes both active, inactive and default (with subId == - * {@link ParticipantData#DEFAULT_SELF_SUB_ID}) subscriptions. - */ - private final ArrayMap mSelfParticipantMap; - - public SelfParticipantsData() { - mSelfParticipantMap = new ArrayMap(); - } - - public void bind(final Cursor cursor) { - mSelfParticipantMap.clear(); - if (cursor != null) { - while (cursor.moveToNext()) { - final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor); - mSelfParticipantMap.put(newParticipant.getId(), newParticipant); - } - } - } - - /** - * Gets the list of self participants for all subscriptions. - * @param activeOnly if set, returns active self entries only (i.e. those with SIMs plugged in). - */ - public List getSelfParticipants(final boolean activeOnly) { - List list = new ArrayList(); - for (final ParticipantData self : mSelfParticipantMap.values()) { - if (!activeOnly || self.isActiveSubscription()) { - list.add(self); - } - } - return list; - } - - /** - * Gets the self participant corresponding to the given self id. - */ - ParticipantData getSelfParticipantById(final String selfId) { - return mSelfParticipantMap.get(selfId); - } - - /** - * Returns if a given self id represents the default self. - */ - boolean isDefaultSelf(final String selfId) { - if (!OsUtil.isAtLeastL_MR1()) { - return true; - } - final ParticipantData self = getSelfParticipantById(selfId); - return self == null ? false : self.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID; - } - - public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) { - int count = 0; - for (final ParticipantData self : mSelfParticipantMap.values()) { - if (!self.isDefaultSelf() && (!activeOnly || self.isActiveSubscription())) { - count++; - } - } - return count; - } - - public ParticipantData getDefaultSelfParticipant() { - for (final ParticipantData self : mSelfParticipantMap.values()) { - if (self.isDefaultSelf()) { - return self; - } - } - return null; - } - - boolean isLoaded() { - return !mSelfParticipantMap.isEmpty(); - } -} diff --git a/src/com/android/messaging/datamodel/data/SettingsData.java b/src/com/android/messaging/datamodel/data/SettingsData.java deleted file mode 100644 index 7474619..0000000 --- a/src/com/android/messaging/datamodel/data/SettingsData.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.app.LoaderManager; -import android.content.Context; -import android.content.Loader; -import android.database.Cursor; -import android.os.Bundle; -import android.text.TextUtils; - -import com.android.messaging.R; -import com.android.messaging.datamodel.BoundCursorLoader; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * Services SettingsFragment's data needs for loading active self participants to display - * the list of active subscriptions. - */ -public class SettingsData extends BindableData implements - LoaderManager.LoaderCallbacks { - public interface SettingsDataListener { - void onSelfParticipantDataLoaded(SettingsData data); - } - - public static class SettingsItem { - public static final int TYPE_GENERAL_SETTINGS = 1; - public static final int TYPE_PER_SUBSCRIPTION_SETTINGS = 2; - - private final String mDisplayName; - private final String mDisplayDetail; - private final String mActivityTitle; - private final int mType; - private final int mSubId; - - private SettingsItem(final String displayName, final String displayDetail, - final String activityTitle, final int type, final int subId) { - mDisplayName = displayName; - mDisplayDetail = displayDetail; - mActivityTitle = activityTitle; - mType = type; - mSubId = subId; - } - - public String getDisplayName() { - return mDisplayName; - } - - public String getDisplayDetail() { - return mDisplayDetail; - } - - public int getType() { - return mType; - } - - public int getSubId() { - return mSubId; - } - - public String getActivityTitle() { - return mActivityTitle; - } - - public static SettingsItem fromSelfParticipant(final Context context, - final ParticipantData self) { - Assert.isTrue(self.isSelf()); - Assert.isTrue(self.isActiveSubscription()); - final String displayDetail = TextUtils.isEmpty(self.getDisplayDestination()) ? - context.getString(R.string.sim_settings_unknown_number) : - self.getDisplayDestination(); - final String displayName = context.getString(R.string.sim_specific_settings, - self.getSubscriptionName()); - return new SettingsItem(displayName, displayDetail, displayName, - TYPE_PER_SUBSCRIPTION_SETTINGS, self.getSubId()); - } - - public static SettingsItem createGeneralSettingsItem(final Context context) { - return new SettingsItem(context.getString(R.string.general_settings), - null, context.getString(R.string.general_settings_activity_title), - TYPE_GENERAL_SETTINGS, -1); - } - - public static SettingsItem createDefaultMmsSettingsItem(final Context context, - final int subId) { - return new SettingsItem(context.getString(R.string.advanced_settings), - null, context.getString(R.string.advanced_settings_activity_title), - TYPE_PER_SUBSCRIPTION_SETTINGS, subId); - } - } - - private static final String BINDING_ID = "bindingId"; - private final Context mContext; - private final SelfParticipantsData mSelfParticipantsData; - private LoaderManager mLoaderManager; - private SettingsDataListener mListener; - - public SettingsData(final Context context, - final SettingsDataListener listener) { - mListener = listener; - mContext = context; - mSelfParticipantsData = new SelfParticipantsData(); - } - - private static final int SELF_PARTICIPANT_LOADER = 1; - - @Override - public Loader onCreateLoader(final int id, final Bundle args) { - Assert.equals(SELF_PARTICIPANT_LOADER, id); - Loader loader = null; - - final String bindingId = args.getString(BINDING_ID); - // Check if data still bound to the requesting ui element - if (isBound(bindingId)) { - loader = new BoundCursorLoader(bindingId, mContext, - MessagingContentProvider.PARTICIPANTS_URI, - ParticipantData.ParticipantsQuery.PROJECTION, - ParticipantColumns.SUB_ID + " <> ?", - new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) }, - null); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Creating self loader after unbinding"); - } - return loader; - } - - @Override - public void onLoadFinished(final Loader generic, final Cursor data) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mSelfParticipantsData.bind(data); - mListener.onSelfParticipantDataLoaded(this); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Self loader finished after unbinding"); - } - } - - @Override - public void onLoaderReset(final Loader generic) { - final BoundCursorLoader loader = (BoundCursorLoader) generic; - - // Check if data still bound to the requesting ui element - if (isBound(loader.getBindingId())) { - mSelfParticipantsData.bind(null); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Self loader reset after unbinding"); - } - } - - public void init(final LoaderManager loaderManager, - final BindingBase binding) { - final Bundle args = new Bundle(); - args.putString(BINDING_ID, binding.getBindingId()); - mLoaderManager = loaderManager; - mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, this); - } - - @Override - protected void unregisterListeners() { - mListener = null; - - // This could be null if we bind but the caller doesn't init the BindableData - if (mLoaderManager != null) { - mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER); - mLoaderManager = null; - } - } - - public List getSettingsItems() { - final List selfs = mSelfParticipantsData.getSelfParticipants(true); - final List settingsItems = new ArrayList(); - // First goes the general settings, followed by per-subscription settings. - settingsItems.add(SettingsItem.createGeneralSettingsItem(mContext)); - // For per-subscription settings, show the actual SIM name with phone number if the - // platorm is at least L-MR1 and there are multiple active SIMs. - final int activeSubCountExcludingDefault = - mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(true); - if (OsUtil.isAtLeastL_MR1() && activeSubCountExcludingDefault > 0) { - for (ParticipantData self : selfs) { - if (!self.isDefaultSelf()) { - if (activeSubCountExcludingDefault > 1) { - settingsItems.add(SettingsItem.fromSelfParticipant(mContext, self)); - } else { - // This is the only active non-default SIM. - settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext, - self.getSubId())); - break; - } - } - } - } else { - // Either pre-L-MR1, or there's no active SIM, so show the default MMS settings. - settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext, - ParticipantData.DEFAULT_SELF_SUB_ID)); - } - return settingsItems; - } -} diff --git a/src/com/android/messaging/datamodel/data/SubscriptionListData.java b/src/com/android/messaging/datamodel/data/SubscriptionListData.java deleted file mode 100644 index b5d4e4b..0000000 --- a/src/com/android/messaging/datamodel/data/SubscriptionListData.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.content.Context; -import android.net.Uri; -import android.text.TextUtils; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * This is a UI facing data model component that holds a list of - * {@link SubscriptionListData.SubscriptionListEntry}'s, one for each *active* subscriptions. - * - * This is used to: - * 1) Show a list of SIMs in the SIM Selector - * 2) Show the currently selected SIM in the compose message view - * 3) Show SIM indicators on conversation message views - * - * It builds on top of SelfParticipantsData and performs additional logic such as determining - * the set of icons to use for the individual Subs. - */ -public class SubscriptionListData { - /** - * Represents a single sub that backs UI. - */ - public static class SubscriptionListEntry { - public final String selfParticipantId; - public final Uri iconUri; - public final Uri selectedIconUri; - public final String displayName; - public final int displayColor; - public final String displayDestination; - - private SubscriptionListEntry(final String selfParticipantId, final Uri iconUri, - final Uri selectedIconUri, final String displayName, final int displayColor, - final String displayDestination) { - this.selfParticipantId = selfParticipantId; - this.iconUri = iconUri; - this.selectedIconUri = selectedIconUri; - this.displayName = displayName; - this.displayColor = displayColor; - this.displayDestination = displayDestination; - } - - static SubscriptionListEntry fromSelfParticipantData( - final ParticipantData selfParticipantData, final Context context) { - Assert.isTrue(selfParticipantData.isSelf()); - Assert.isTrue(selfParticipantData.isActiveSubscription()); - final int slotId = selfParticipantData.getDisplaySlotId(); - final String iconIdentifier = String.format(Locale.getDefault(), "%d", slotId); - final String subscriptionName = selfParticipantData.getSubscriptionName(); - final String displayName = TextUtils.isEmpty(subscriptionName) ? - context.getString(R.string.sim_slot_identifier, slotId) : subscriptionName; - return new SubscriptionListEntry(selfParticipantData.getId(), - AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier, - false /* selected */, false /* incoming */), - AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier, - true /* selected */, false /* incoming */), - displayName, selfParticipantData.getSubscriptionColor(), - selfParticipantData.getDisplayDestination()); - } - } - - private final List mEntriesExcludingDefault; - private SubscriptionListEntry mDefaultEntry; - private final Context mContext; - - public SubscriptionListData(final Context context) { - mEntriesExcludingDefault = new ArrayList(); - mContext = context; - } - - public void bind(final List subs) { - mEntriesExcludingDefault.clear(); - mDefaultEntry = null; - for (final ParticipantData sub : subs) { - final SubscriptionListEntry entry = - SubscriptionListEntry.fromSelfParticipantData(sub, mContext); - if (!sub.isDefaultSelf()) { - mEntriesExcludingDefault.add(entry); - } else { - mDefaultEntry = entry; - } - } - } - - public List getActiveSubscriptionEntriesExcludingDefault() { - return mEntriesExcludingDefault; - } - - public SubscriptionListEntry getActiveSubscriptionEntryBySelfId(final String selfId, - final boolean excludeDefault) { - if (mDefaultEntry != null && TextUtils.equals(mDefaultEntry.selfParticipantId, selfId)) { - return excludeDefault ? null : mDefaultEntry; - } - - for (final SubscriptionListEntry entry : mEntriesExcludingDefault) { - if (TextUtils.equals(entry.selfParticipantId, selfId)) { - return entry; - } - } - return null; - } - - public boolean hasData() { - return !mEntriesExcludingDefault.isEmpty() || mDefaultEntry != null; - } -} diff --git a/src/com/android/messaging/datamodel/data/VCardContactItemData.java b/src/com/android/messaging/datamodel/data/VCardContactItemData.java deleted file mode 100644 index 8abf493..0000000 --- a/src/com/android/messaging/datamodel/data/VCardContactItemData.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.media.BindableMediaRequest; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; -import com.android.messaging.datamodel.media.VCardRequestDescriptor; -import com.android.messaging.datamodel.media.VCardResource; -import com.android.messaging.datamodel.media.VCardResourceEntry; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ContactUtil; - -import java.util.List; - -/** - * Data class for visualizing and loading data for a VCard contact. - */ -public class VCardContactItemData extends PersonItemData - implements MediaResourceLoadListener { - private final Context mContext; - private final Uri mVCardUri; - private String mDetails; - private final Binding> mBinding = - BindingBase.createBinding(this); - private VCardResource mVCardResource; - - private static final Uri sDefaultAvatarUri = - AvatarUriUtil.createAvatarUri(null, null, null, null); - - /** - * Constructor. This parses data from the given MessagePartData describing the vcard - */ - public VCardContactItemData(final Context context, final MessagePartData messagePartData) { - this(context, messagePartData.getContentUri()); - Assert.isTrue(messagePartData.isVCard()); - } - - /** - * Constructor. This parses data from the given VCard Uri - */ - public VCardContactItemData(final Context context, final Uri vCardUri) { - mContext = context; - mDetails = mContext.getString(R.string.loading_vcard); - mVCardUri = vCardUri; - } - - @Override - public Uri getAvatarUri() { - if (hasValidVCard()) { - final List vcards = mVCardResource.getVCards(); - Assert.isTrue(vcards.size() > 0); - if (vcards.size() == 1) { - return vcards.get(0).getAvatarUri(); - } - } - return sDefaultAvatarUri; - } - - @Override - public String getDisplayName() { - if (hasValidVCard()) { - final List vcards = mVCardResource.getVCards(); - Assert.isTrue(vcards.size() > 0); - if (vcards.size() == 1) { - return vcards.get(0).getDisplayName(); - } else { - return mContext.getResources().getQuantityString( - R.plurals.vcard_multiple_display_name, vcards.size(), vcards.size()); - } - } - return null; - } - - @Override - public String getDetails() { - return mDetails; - } - - @Override - public Intent getClickIntent() { - return null; - } - - @Override - public long getContactId() { - return ContactUtil.INVALID_CONTACT_ID; - } - - @Override - public String getLookupKey() { - return null; - } - - @Override - public String getNormalizedDestination() { - return null; - } - - public VCardResource getVCardResource() { - return hasValidVCard() ? mVCardResource : null; - } - - public Uri getVCardUri() { - return hasValidVCard() ? mVCardUri : null; - } - - public boolean hasValidVCard() { - return isBound() && mVCardResource != null; - } - - @Override - public void bind(final String bindingId) { - super.bind(bindingId); - - // Bind and request the VCard from media resource manager. - mBinding.bind(new VCardRequestDescriptor(mVCardUri).buildAsyncMediaRequest(mContext, this)); - MediaResourceManager.get().requestMediaResourceAsync(mBinding.getData()); - } - - @Override - public void unbind(final String bindingId) { - super.unbind(bindingId); - mBinding.unbind(); - if (mVCardResource != null) { - mVCardResource.release(); - mVCardResource = null; - } - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof VCardContactItemData)) { - return false; - } - - final VCardContactItemData lhs = (VCardContactItemData) o; - return mVCardUri.equals(lhs.mVCardUri); - } - - @Override - public void onMediaResourceLoaded(final MediaRequest request, - final VCardResource resource, final boolean isCached) { - Assert.isTrue(mVCardResource == null); - mBinding.ensureBound(); - mDetails = mContext.getString(R.string.vcard_tap_hint); - mVCardResource = resource; - mVCardResource.addRef(); - notifyDataUpdated(); - } - - @Override - public void onMediaResourceLoadError(final MediaRequest request, - final Exception exception) { - mBinding.ensureBound(); - mDetails = mContext.getString(R.string.failed_loading_vcard); - notifyDataFailed(exception); - } -} diff --git a/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java b/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java deleted file mode 100644 index 380d93c..0000000 --- a/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; - -import java.util.List; - -/** - * A mix-in style class that wraps around a normal, threading-agnostic MediaRequest object with - * functionalities offered by {@link BindableMediaRequest} to allow for async processing. - */ -class AsyncMediaRequestWrapper extends BindableMediaRequest { - - /** - * Create a new async media request wrapper instance given the listener. - */ - public static AsyncMediaRequestWrapper - createWith(final MediaRequest wrappedRequest, - final MediaResourceLoadListener listener) { - return new AsyncMediaRequestWrapper(listener, wrappedRequest); - } - - private final MediaRequest mWrappedRequest; - - private AsyncMediaRequestWrapper(final MediaResourceLoadListener listener, - final MediaRequest wrappedRequest) { - super(listener); - mWrappedRequest = wrappedRequest; - } - - @Override - public String getKey() { - return mWrappedRequest.getKey(); - } - - @Override - public MediaCache getMediaCache() { - return mWrappedRequest.getMediaCache(); - } - - @Override - public int getRequestType() { - return mWrappedRequest.getRequestType(); - } - - @Override - public T loadMediaBlocking(List> chainedTask) throws Exception { - return mWrappedRequest.loadMediaBlocking(chainedTask); - } - - @Override - public int getCacheId() { - return mWrappedRequest.getCacheId(); - } - - @Override - public MediaRequestDescriptor getDescriptor() { - return mWrappedRequest.getDescriptor(); - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java deleted file mode 100644 index 719b296..0000000 --- a/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.RectF; -import android.net.Uri; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class AvatarGroupRequestDescriptor extends CompositeImageRequestDescriptor { - private static final int MAX_GROUP_SIZE = 4; - - public AvatarGroupRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight) { - this(convertToDescriptor(uri, desiredWidth, desiredHeight), desiredWidth, desiredHeight); - } - - public AvatarGroupRequestDescriptor(final List descriptors, - final int desiredWidth, final int desiredHeight) { - super(descriptors, desiredWidth, desiredHeight); - Assert.isTrue(descriptors.size() <= MAX_GROUP_SIZE); - } - - private static List convertToDescriptor(final Uri uri, - final int desiredWidth, final int desiredHeight) { - final List participantUriStrings = AvatarUriUtil.getGroupParticipantUris(uri); - final List avatarDescriptors = - new ArrayList(participantUriStrings.size()); - for (final String uriString : participantUriStrings) { - final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor( - Uri.parse(uriString), desiredWidth, desiredHeight); - avatarDescriptors.add(descriptor); - } - return avatarDescriptors; - } - - @Override - public CompositeImageRequest buildBatchImageRequest(final Context context) { - return new CompositeImageRequest(context, this); - } - - @Override - public List getChildRequestTargetRects() { - return Arrays.asList(generateDestRectArray()); - } - - /** - * Generates an array of {@link RectF} which represents where each of the individual avatar - * should be located in the final group avatar image. The location of each avatar depends on - * the size of the group and the size of the overall group avatar size. - */ - private RectF[] generateDestRectArray() { - final int groupSize = mDescriptors.size(); - final float width = desiredWidth; - final float height = desiredHeight; - final float halfWidth = width / 2F; - final float halfHeight = height / 2F; - final RectF[] destArray = new RectF[groupSize]; - switch (groupSize) { - case 2: - /** - * +-------+ - * | 0 | | - * +-------+ - * | | 1 | - * +-------+ - * - * We want two circles which touches in the center. To get this we know that the - * diagonal of the overall group avatar is squareRoot(2) * w We also know that the - * two circles touches the at the center of the overall group avatar and the - * distance from the center of the circle to the corner of the group avatar is - * radius * squareRoot(2). Therefore, the following emerges. - * - * w * squareRoot(2) = 2 (radius + radius * squareRoot(2)) - * Solving for radius we get: - * d = 2 * radius = ( squareRoot(2) / (squareRoot(2) + 1)) * w - * d = (2 - squareRoot(2)) * w - */ - final float diameter = (float) ((2 - Math.sqrt(2)) * width); - destArray[0] = new RectF(0, 0, diameter, diameter); - destArray[1] = new RectF(width - diameter, height - diameter, width, height); - break; - case 3: - /** - * +-------+ - * | | 0 | | - * +-------+ - * | 1 | 2 | - * +-------+ - * i0 - * |\ - * a | \ c - * --- i2 - * b - * - * a = radius * squareRoot(3) due to the triangle being a 30-60-90 right triangle. - * b = radius of circle - * c = 2 * radius of circle - * - * All three of the images are circles and therefore image zero will not touch - * image one or image two. Move image zero down so it touches image one and image - * two. This can be done by keeping image zero in the center and moving it down - * slightly. The amount to move down can be calculated by solving a right triangle. - * We know that the center x of image two to the center x of image zero is the - * radius of the circle, this is the length of edge b. Also we know that the - * distance from image zero to image two's center is 2 * radius, edge c. From this - * we know that the distance from center y of image two to center y of image one, - * edge a, is equal to radius * squareRoot(3) due to this triangle being a 30-60-90 - * right triangle. - */ - final float quarterWidth = width / 4F; - final float threeQuarterWidth = 3 * quarterWidth; - final float radius = height / 4F; - final float imageTwoCenterY = height - radius; - final float lengthOfEdgeA = (float) (radius * Math.sqrt(3)); - final float imageZeroCenterY = imageTwoCenterY - lengthOfEdgeA; - final float imageZeroTop = imageZeroCenterY - radius; - final float imageZeroBottom = imageZeroCenterY + radius; - destArray[0] = new RectF( - quarterWidth, imageZeroTop, threeQuarterWidth, imageZeroBottom); - destArray[1] = new RectF(0, halfHeight, halfWidth, height); - destArray[2] = new RectF(halfWidth, halfHeight, width, height); - break; - default: - /** - * +-------+ - * | 0 | 1 | - * +-------+ - * | 2 | 3 | - * +-------+ - */ - destArray[0] = new RectF(0, 0, halfWidth, halfHeight); - destArray[1] = new RectF(halfWidth, 0, width, halfHeight); - destArray[2] = new RectF(0, halfHeight, halfWidth, height); - destArray[3] = new RectF(halfWidth, halfHeight, width, height); - break; - } - return destArray; - } -} diff --git a/src/com/android/messaging/datamodel/media/AvatarRequest.java b/src/com/android/messaging/datamodel/media/AvatarRequest.java deleted file mode 100644 index 22d5ccc..0000000 --- a/src/com/android/messaging/datamodel/media/AvatarRequest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.media.ExifInterface; -import android.net.Uri; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UriUtil; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -public class AvatarRequest extends UriImageRequest { - private static Bitmap sDefaultPersonBitmap; - private static Bitmap sDefaultPersonBitmapLarge; - - public AvatarRequest(final Context context, - final AvatarRequestDescriptor descriptor) { - super(context, descriptor); - } - - @Override - protected InputStream getInputStreamForResource() throws FileNotFoundException { - if (UriUtil.isLocalResourceUri(mDescriptor.uri)) { - return super.getInputStreamForResource(); - } else { - final Uri primaryUri = AvatarUriUtil.getPrimaryUri(mDescriptor.uri); - Assert.isTrue(UriUtil.isLocalResourceUri(primaryUri)); - return mContext.getContentResolver().openInputStream(primaryUri); - } - } - - /** - * We can load multiple types of images for avatars depending on the uri. The uri should be - * built by {@link com.android.messaging.util.AvatarUriUtil} which will decide on - * what uri to build based on the available profile photo and name. Here we will check if the - * image is a local resource (ie profile photo uri), if the resource isn't a local one we will - * generate a tile with the first letter of the name. - */ - @Override - protected ImageResource loadMediaInternal(List> chainedTasks) - throws IOException { - Assert.isNotMainThread(); - String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri); - Bitmap bitmap = null; - int orientation = ExifInterface.ORIENTATION_NORMAL; - final boolean isLocalResourceUri = UriUtil.isLocalResourceUri(mDescriptor.uri) || - AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI.equals(avatarType); - if (isLocalResourceUri) { - try { - ImageResource imageResource = super.loadMediaInternal(chainedTasks); - bitmap = imageResource.getBitmap(); - orientation = imageResource.mOrientation; - } catch (Exception ex) { - // If we encountered any exceptions trying to load the local avatar resource, - // fall back to generated avatar. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "AvatarRequest: failed to load local avatar " + - "resource, switching to fallback rendering", ex); - } - } - - final int width = mDescriptor.desiredWidth; - final int height = mDescriptor.desiredHeight; - // Check to see if we already got the bitmap. If not get a fallback avatar - if (bitmap == null) { - Uri generatedUri = mDescriptor.uri; - if (isLocalResourceUri) { - // If we are here, we just failed to load the local resource. Use the fallback Uri - // if possible. - generatedUri = AvatarUriUtil.getFallbackUri(mDescriptor.uri); - if (generatedUri == null) { - // No fallback Uri was provided, use the default avatar. - generatedUri = AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR; - } - } - - avatarType = AvatarUriUtil.getAvatarType(generatedUri); - if (AvatarUriUtil.TYPE_LETTER_TILE_URI.equals(avatarType)) { - final String name = AvatarUriUtil.getName(generatedUri); - bitmap = renderLetterTile(name, width, height); - } else { - bitmap = renderDefaultAvatar(width, height); - } - } - return new DecodedImageResource(getKey(), bitmap, orientation); - } - - private Bitmap renderDefaultAvatar(final int width, final int height) { - final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height, - getBackgroundColor()); - final Canvas canvas = new Canvas(bitmap); - - if (sDefaultPersonBitmap == null) { - final BitmapDrawable defaultPerson = (BitmapDrawable) mContext.getResources() - .getDrawable(R.drawable.ic_person_light); - sDefaultPersonBitmap = defaultPerson.getBitmap(); - } - if (sDefaultPersonBitmapLarge == null) { - final BitmapDrawable largeDefaultPerson = (BitmapDrawable) mContext.getResources() - .getDrawable(R.drawable.ic_person_light_large); - sDefaultPersonBitmapLarge = largeDefaultPerson.getBitmap(); - } - - Bitmap defaultPerson = null; - if (mDescriptor.isWearBackground) { - final BitmapDrawable wearDefaultPerson = (BitmapDrawable) mContext.getResources() - .getDrawable(R.drawable.ic_person_wear); - defaultPerson = wearDefaultPerson.getBitmap(); - } else { - final boolean isLargeDefault = (width > sDefaultPersonBitmap.getWidth()) || - (height > sDefaultPersonBitmap.getHeight()); - defaultPerson = - isLargeDefault ? sDefaultPersonBitmapLarge : sDefaultPersonBitmap; - } - - final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Matrix matrix = new Matrix(); - final RectF source = new RectF(0, 0, defaultPerson.getWidth(), defaultPerson.getHeight()); - final RectF dest = new RectF(0, 0, width, height); - matrix.setRectToRect(source, dest, Matrix.ScaleToFit.FILL); - - canvas.drawBitmap(defaultPerson, matrix, paint); - - return bitmap; - } - - private Bitmap renderLetterTile(final String name, final int width, final int height) { - final float halfWidth = width / 2; - final float halfHeight = height / 2; - final int minOfWidthAndHeight = Math.min(width, height); - final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height, - getBackgroundColor()); - final Resources resources = mContext.getResources(); - final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setTypeface(Typeface.create("sans-serif-thin", Typeface.NORMAL)); - paint.setColor(resources.getColor(R.color.letter_tile_font_color)); - final float letterToTileRatio = resources.getFraction(R.dimen.letter_to_tile_ratio, 1, 1); - paint.setTextSize(letterToTileRatio * minOfWidthAndHeight); - - final String firstCharString = name.substring(0, 1).toUpperCase(); - final Rect textBound = new Rect(); - paint.getTextBounds(firstCharString, 0, 1, textBound); - - final Canvas canvas = new Canvas(bitmap); - final float xOffset = halfWidth - textBound.centerX(); - final float yOffset = halfHeight - textBound.centerY(); - canvas.drawText(firstCharString, xOffset, yOffset, paint); - - return bitmap; - } - - private int getBackgroundColor() { - return mContext.getResources().getColor(R.color.primary_color); - } - - @Override - public int getCacheId() { - return BugleMediaCacheManager.AVATAR_IMAGE_CACHE; - } -} diff --git a/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java deleted file mode 100644 index 9afa9ad..0000000 --- a/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.UriUtil; - -public class AvatarRequestDescriptor extends UriImageRequestDescriptor { - final boolean isWearBackground; - - public AvatarRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight) { - this(uri, desiredWidth, desiredHeight, true /* cropToCircle */); - } - - public AvatarRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight, final boolean cropToCircle) { - this(uri, desiredWidth, desiredHeight, cropToCircle, false /* isWearBackground */); - } - - public AvatarRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight, boolean cropToCircle, boolean isWearBackground) { - super(uri, desiredWidth, desiredHeight, false /* allowCompression */, true /* isStatic */, - cropToCircle, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - Assert.isTrue(uri == null || UriUtil.isLocalResourceUri(uri) || - AvatarUriUtil.isAvatarUri(uri)); - this.isWearBackground = isWearBackground; - } - - @Override - public MediaRequest buildSyncMediaRequest(final Context context) { - final String avatarType = uri == null ? null : AvatarUriUtil.getAvatarType(uri); - if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)) { - return new SimSelectorAvatarRequest(context, this); - } else { - return new AvatarRequest(context, this); - } - } -} diff --git a/src/com/android/messaging/datamodel/media/BindableMediaRequest.java b/src/com/android/messaging/datamodel/media/BindableMediaRequest.java deleted file mode 100644 index 36521d5..0000000 --- a/src/com/android/messaging/datamodel/media/BindableMediaRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import com.android.messaging.datamodel.binding.BindableOnceData; -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; - -/** - * The {@link MediaRequest} interface is threading-model-blind, allowing the implementations to - * be processed synchronously or asynchronously. - * This is a {@link MediaRequest} implementation that includes functionalities such as binding and - * event callbacks for multi-threaded media request processing. - */ -public abstract class BindableMediaRequest - extends BindableOnceData - implements MediaRequest, MediaResourceLoadListener { - private MediaResourceLoadListener mListener; - - public BindableMediaRequest(final MediaResourceLoadListener listener) { - mListener = listener; - } - - /** - * Delegates the media resource callback to the listener. Performs binding check to ensure - * the listener is still bound to this request. - */ - @Override - public void onMediaResourceLoaded(final MediaRequest request, final T resource, - final boolean cached) { - if (isBound() && mListener != null) { - mListener.onMediaResourceLoaded(request, resource, cached); - } - } - - /** - * Delegates the media resource callback to the listener. Performs binding check to ensure - * the listener is still bound to this request. - */ - @Override - public void onMediaResourceLoadError(final MediaRequest request, final Exception exception) { - if (isBound() && mListener != null) { - mListener.onMediaResourceLoadError(request, exception); - } - } - - @Override - protected void unregisterListeners() { - mListener = null; - } -} diff --git a/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java b/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java deleted file mode 100644 index c41ba60..0000000 --- a/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import com.android.messaging.util.Assert; - -/** - * An implementation of {@link MediaCacheManager} that creates caches specific to Bugle's needs. - * - * To create a new type of cache, add to the list of cache ids and create a new MediaCache<> - * for your cache id / media resource type in createMediaCacheById(). - */ -public class BugleMediaCacheManager extends MediaCacheManager { - // List of available cache ids. - public static final int DEFAULT_IMAGE_CACHE = 1; - public static final int AVATAR_IMAGE_CACHE = 2; - public static final int VCARD_CACHE = 3; - - // VCard cache size - we compute the size by count, not by bytes. - private static final int VCARD_CACHE_SIZE = 5; - private static final int SHARED_IMAGE_CACHE_SIZE = 1024 * 10; // 10MB - - @Override - protected MediaCache createMediaCacheById(final int id) { - switch (id) { - case DEFAULT_IMAGE_CACHE: - return new PoolableImageCache(SHARED_IMAGE_CACHE_SIZE, id, "DefaultImageCache"); - - case AVATAR_IMAGE_CACHE: - return new PoolableImageCache(id, "AvatarImageCache"); - - case VCARD_CACHE: - return new MediaCache(VCARD_CACHE_SIZE, id, "VCardCache"); - - default: - Assert.fail("BugleMediaCacheManager: unsupported cache id " + id); - break; - } - return null; - } -} diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequest.java b/src/com/android/messaging/datamodel/media/CompositeImageRequest.java deleted file mode 100644 index 66f1bff..0000000 --- a/src/com/android/messaging/datamodel/media/CompositeImageRequest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.RectF; -import android.media.ExifInterface; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.ImageUtils; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.List; - -/** - * Requests a composite image resource. The composite image resource is constructed by first - * sequentially requesting a number of sub image resources specified by - * {@link CompositeImageRequestDescriptor#getChildRequestDescriptors()}. After this, the - * individual sub images are composed into the final image onto their respective target rects - * returned by {@link CompositeImageRequestDescriptor#getChildRequestTargetRects()}. - */ -public class CompositeImageRequest - extends ImageRequest { - private final Bitmap mBitmap; - private final Canvas mCanvas; - private final Paint mPaint; - - public CompositeImageRequest(final Context context, final D descriptor) { - super(context, descriptor); - mBitmap = getBitmapPool().createOrReuseBitmap( - mDescriptor.desiredWidth, mDescriptor.desiredHeight); - mCanvas = new Canvas(mBitmap); - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - } - - @Override - protected ImageResource loadMediaInternal(List> chainedTask) { - final List descriptors = - mDescriptor.getChildRequestDescriptors(); - final List targetRects = mDescriptor.getChildRequestTargetRects(); - Assert.equals(descriptors.size(), targetRects.size()); - Assert.isTrue(descriptors.size() > 1); - - for (int i = 0; i < descriptors.size(); i++) { - final MediaRequest request = - descriptors.get(i).buildSyncMediaRequest(mContext); - // Synchronously request the child image. - final ImageResource resource = - MediaResourceManager.get().requestMediaResourceSync(request); - if (resource != null) { - try { - final RectF avatarDestOnGroup = targetRects.get(i); - - // Draw the bitmap into a smaller size with a circle mask. - final Bitmap resourceBitmap = resource.getBitmap(); - final RectF resourceRect = new RectF( - 0, 0, resourceBitmap.getWidth(), resourceBitmap.getHeight()); - final Bitmap smallCircleBitmap = getBitmapPool().createOrReuseBitmap( - Math.round(avatarDestOnGroup.width()), - Math.round(avatarDestOnGroup.height())); - final RectF smallCircleRect = new RectF( - 0, 0, smallCircleBitmap.getWidth(), smallCircleBitmap.getHeight()); - final Canvas smallCircleCanvas = new Canvas(smallCircleBitmap); - ImageUtils.drawBitmapWithCircleOnCanvas(resource.getBitmap(), smallCircleCanvas, - resourceRect, smallCircleRect, null /* bitmapPaint */, - false /* fillBackground */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - final Matrix matrix = new Matrix(); - matrix.setRectToRect(smallCircleRect, avatarDestOnGroup, - Matrix.ScaleToFit.FILL); - mCanvas.drawBitmap(smallCircleBitmap, matrix, mPaint); - } finally { - resource.release(); - } - } - } - - return new DecodedImageResource(getKey(), mBitmap, ExifInterface.ORIENTATION_NORMAL); - } - - @Override - public int getCacheId() { - return BugleMediaCacheManager.AVATAR_IMAGE_CACHE; - } - - @Override - protected InputStream getInputStreamForResource() throws FileNotFoundException { - throw new IllegalStateException("Composite image request doesn't support input stream!"); - } -} diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java deleted file mode 100644 index 071130e..0000000 --- a/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.RectF; - -import com.google.common.base.Joiner; - -import java.util.List; - -public abstract class CompositeImageRequestDescriptor extends ImageRequestDescriptor { - protected final List mDescriptors; - private final String mKey; - - public CompositeImageRequestDescriptor(final List descriptors, - final int desiredWidth, final int desiredHeight) { - super(desiredWidth, desiredHeight); - mDescriptors = descriptors; - - final String[] keyParts = new String[descriptors.size()]; - for (int i = 0; i < descriptors.size(); i++) { - keyParts[i] = descriptors.get(i).getKey(); - } - mKey = Joiner.on(",").skipNulls().join(keyParts); - } - - /** - * Gets a key that uniquely identify all the underlying image resource to be loaded (e.g. Uri or - * file path). - */ - @Override - public String getKey() { - return mKey; - } - - public List getChildRequestDescriptors(){ - return mDescriptors; - } - - public abstract List getChildRequestTargetRects(); - public abstract CompositeImageRequest buildBatchImageRequest(final Context context); - - @Override - public MediaRequest buildSyncMediaRequest(final Context context) { - return buildBatchImageRequest(context); - } -} diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntry.java b/src/com/android/messaging/datamodel/media/CustomVCardEntry.java deleted file mode 100644 index aee9fdc..0000000 --- a/src/com/android/messaging/datamodel/media/CustomVCardEntry.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.accounts.Account; -import android.support.v4.util.ArrayMap; - -import com.android.vcard.VCardEntry; -import com.android.vcard.VCardProperty; - -import java.util.Map; - -/** - * Class which extends VCardEntry to add support for unknown properties. Currently there is a TODO - * to add this in the VCardEntry code, but we have to extend it to add the needed support - */ -public class CustomVCardEntry extends VCardEntry { - // List of properties keyed by their name for easy lookup - private final Map mAllProperties; - - public CustomVCardEntry(int vCardType, Account account) { - super(vCardType, account); - mAllProperties = new ArrayMap(); - } - - @Override - public void addProperty(VCardProperty property) { - super.addProperty(property); - mAllProperties.put(property.getName(), property); - } - - public VCardProperty getProperty(String name) { - return mAllProperties.get(name); - } -} diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java b/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java deleted file mode 100644 index 06b10a3..0000000 --- a/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.accounts.Account; -import com.android.vcard.VCardConfig; -import com.android.vcard.VCardInterpreter; -import com.android.vcard.VCardProperty; - -import java.util.ArrayList; -import java.util.List; - -public class CustomVCardEntryConstructor implements VCardInterpreter { - - public interface EntryHandler { - /** - * Called when the parsing started. - */ - public void onStart(); - - /** - * The method called when one vCard entry is created. Children come before their parent in - * nested vCard files. - * - * e.g. - * In the following vCard, the entry for "entry2" comes before one for "entry1". - * - * BEGIN:VCARD - * N:entry1 - * BEGIN:VCARD - * N:entry2 - * END:VCARD - * END:VCARD - * - */ - public void onEntryCreated(final CustomVCardEntry entry); - - /** - * Called when the parsing ended. - * Able to be use this method for showing performance log, etc. - */ - public void onEnd(); - } - - /** - * Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1). - */ - private final List mEntryStack = new ArrayList(); - private CustomVCardEntry mCurrentEntry; - - private final int mVCardType; - private final Account mAccount; - - private final List mEntryHandlers = new ArrayList(); - - public CustomVCardEntryConstructor() { - this(VCardConfig.VCARD_TYPE_V21_GENERIC, null); - } - - public CustomVCardEntryConstructor(final int vcardType) { - this(vcardType, null); - } - - public CustomVCardEntryConstructor(final int vcardType, final Account account) { - mVCardType = vcardType; - mAccount = account; - } - - public void addEntryHandler(EntryHandler entryHandler) { - mEntryHandlers.add(entryHandler); - } - - @Override - public void onVCardStarted() { - for (EntryHandler entryHandler : mEntryHandlers) { - entryHandler.onStart(); - } - } - - @Override - public void onVCardEnded() { - for (EntryHandler entryHandler : mEntryHandlers) { - entryHandler.onEnd(); - } - } - - public void clear() { - mCurrentEntry = null; - mEntryStack.clear(); - } - - @Override - public void onEntryStarted() { - mCurrentEntry = new CustomVCardEntry(mVCardType, mAccount); - mEntryStack.add(mCurrentEntry); - } - - @Override - public void onEntryEnded() { - mCurrentEntry.consolidateFields(); - for (EntryHandler entryHandler : mEntryHandlers) { - entryHandler.onEntryCreated(mCurrentEntry); - } - - final int size = mEntryStack.size(); - if (size > 1) { - CustomVCardEntry parent = mEntryStack.get(size - 2); - parent.addChild(mCurrentEntry); - mCurrentEntry = parent; - } else { - mCurrentEntry = null; - } - mEntryStack.remove(size - 1); - } - - @Override - public void onPropertyCreated(VCardProperty property) { - mCurrentEntry.addProperty(property); - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/DecodedImageResource.java b/src/com/android/messaging/datamodel/media/DecodedImageResource.java deleted file mode 100644 index 3627ba4..0000000 --- a/src/com/android/messaging/datamodel/media/DecodedImageResource.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; - -import com.android.messaging.ui.OrientedBitmapDrawable; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -import java.util.List; - - -/** - * Container class for holding a bitmap resource used by the MediaResourceManager. This resource - * can both be cached (albeit not very storage-efficiently) and directly used by the UI. - */ -public class DecodedImageResource extends ImageResource { - private static final int BITMAP_QUALITY = 100; - private static final int COMPRESS_QUALITY = 50; - - private Bitmap mBitmap; - private final int mOrientation; - private boolean mCacheable = true; - - public DecodedImageResource(final String key, final Bitmap bitmap, int orientation) { - super(key, orientation); - mBitmap = bitmap; - mOrientation = orientation; - } - - /** - * Gets the contained bitmap. - */ - @Override - public Bitmap getBitmap() { - acquireLock(); - try { - return mBitmap; - } finally { - releaseLock(); - } - } - - /** - * Attempt to reuse the bitmap in the image resource and repurpose it for something else. - * After this, the image resource will relinquish ownership on the bitmap resource so that - * it doesn't try to recycle it when getting closed. - */ - @Override - public Bitmap reuseBitmap() { - acquireLock(); - try { - assertSingularRefCount(); - final Bitmap retBitmap = mBitmap; - mBitmap = null; - return retBitmap; - } finally { - releaseLock(); - } - } - - @Override - public boolean supportsBitmapReuse() { - return true; - } - - @Override - public byte[] getBytes() { - acquireLock(); - try { - return ImageUtils.bitmapToBytes(mBitmap, BITMAP_QUALITY); - } catch (final Exception e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error trying to get the bitmap bytes " + e); - } finally { - releaseLock(); - } - return null; - } - - /** - * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants - */ - @Override - public int getOrientation() { - return mOrientation; - } - - @Override - public int getMediaSize() { - acquireLock(); - try { - Assert.notNull(mBitmap); - if (OsUtil.isAtLeastKLP()) { - return mBitmap.getAllocationByteCount(); - } else { - return mBitmap.getRowBytes() * mBitmap.getHeight(); - } - } finally { - releaseLock(); - } - } - - @Override - protected void close() { - acquireLock(); - try { - if (mBitmap != null) { - mBitmap.recycle(); - mBitmap = null; - } - } finally { - releaseLock(); - } - } - - @Override - public Drawable getDrawable(Resources resources) { - acquireLock(); - try { - Assert.notNull(mBitmap); - return OrientedBitmapDrawable.create(getOrientation(), resources, mBitmap); - } finally { - releaseLock(); - } - } - - @Override - boolean isCacheable() { - return mCacheable; - } - - public void setCacheable(final boolean cacheable) { - mCacheable = cacheable; - } - - @SuppressWarnings("unchecked") - @Override - MediaRequest getMediaEncodingRequest( - final MediaRequest originalRequest) { - Assert.isFalse(isEncoded()); - if (getBitmap().hasAlpha()) { - // We can't compress images with alpha, as JPEG encoding doesn't support this. - return null; - } - return new EncodeImageRequest((MediaRequest) originalRequest); - } - - /** - * A MediaRequest that encodes the contained image resource. - */ - private class EncodeImageRequest implements MediaRequest { - private final MediaRequest mOriginalImageRequest; - - public EncodeImageRequest(MediaRequest originalImageRequest) { - mOriginalImageRequest = originalImageRequest; - // Hold a ref onto the encoded resource before the request finishes. - DecodedImageResource.this.addRef(); - } - - @Override - public String getKey() { - return DecodedImageResource.this.getKey(); - } - - @Override - @DoesNotRunOnMainThread - public ImageResource loadMediaBlocking(List> chainedRequests) - throws Exception { - Assert.isNotMainThread(); - acquireLock(); - Bitmap scaledBitmap = null; - try { - Bitmap bitmap = getBitmap(); - Assert.isFalse(bitmap.hasAlpha()); - final int bitmapWidth = bitmap.getWidth(); - final int bitmapHeight = bitmap.getHeight(); - // The original bitmap was loaded using sub-sampling which was fast in terms of - // loading speed, but not optimized for caching, encoding and rendering (since - // bitmap resizing to fit the UI image views happens on the UI thread and should - // be avoided if possible). Therefore, try to resize the bitmap to the exact desired - // size before compressing it. - if (bitmapWidth > 0 && bitmapHeight > 0 && - mOriginalImageRequest instanceof ImageRequest) { - final ImageRequestDescriptor descriptor = - ((ImageRequest) mOriginalImageRequest).getDescriptor(); - final float targetScale = Math.max( - (float) descriptor.desiredWidth / bitmapWidth, - (float) descriptor.desiredHeight / bitmapHeight); - final int targetWidth = (int) (bitmapWidth * targetScale); - final int targetHeight = (int) (bitmapHeight * targetScale); - // Only try to scale down the image to the desired size. - if (targetScale < 1.0f && targetWidth > 0 && targetHeight > 0 && - targetWidth != bitmapWidth && targetHeight != bitmapHeight) { - scaledBitmap = bitmap = - Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false); - } - } - byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY); - return new EncodedImageResource(getKey(), encodedBytes, getOrientation()); - } catch (Exception ex) { - // Something went wrong during bitmap compression, fall back to just using the - // original bitmap. - LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "Error compressing bitmap", ex); - return DecodedImageResource.this; - } finally { - if (scaledBitmap != null && scaledBitmap != getBitmap()) { - scaledBitmap.recycle(); - scaledBitmap = null; - } - releaseLock(); - release(); - } - } - - @Override - public MediaCache getMediaCache() { - return mOriginalImageRequest.getMediaCache(); - } - - @Override - public int getCacheId() { - return mOriginalImageRequest.getCacheId(); - } - - @Override - public int getRequestType() { - return REQUEST_ENCODE_MEDIA; - } - - @Override - public MediaRequestDescriptor getDescriptor() { - return mOriginalImageRequest.getDescriptor(); - } - } -} diff --git a/src/com/android/messaging/datamodel/media/EncodedImageResource.java b/src/com/android/messaging/datamodel/media/EncodedImageResource.java deleted file mode 100644 index 0bc94e5..0000000 --- a/src/com/android/messaging/datamodel/media/EncodedImageResource.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; - -import java.util.Arrays; -import java.util.List; - -/** - * A cache-facing image resource that's much more compact than the raw Bitmap objects stored in - * {@link com.android.messaging.datamodel.media.DecodedImageResource}. - * - * This resource is created from a regular Bitmap-based ImageResource before being pushed to - * {@link com.android.messaging.datamodel.media.MediaCache}, if the image request - * allows for resource encoding/compression. - * - * During resource retrieval on cache hit, - * {@link #getMediaDecodingRequest(MediaRequest)} is invoked to create a async - * decode task, which decodes the compressed byte array back to a regular image resource to - * be consumed by the UI. - */ -public class EncodedImageResource extends ImageResource { - private final byte[] mImageBytes; - - public EncodedImageResource(String key, byte[] imageBytes, int orientation) { - super(key, orientation); - mImageBytes = imageBytes; - } - - @Override - @DoesNotRunOnMainThread - public Bitmap getBitmap() { - acquireLock(); - try { - // This should only be called during the decode request. - Assert.isNotMainThread(); - return BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length); - } finally { - releaseLock(); - } - } - - @Override - public byte[] getBytes() { - acquireLock(); - try { - return Arrays.copyOf(mImageBytes, mImageBytes.length); - } finally { - releaseLock(); - } - } - - @Override - public Bitmap reuseBitmap() { - return null; - } - - @Override - public boolean supportsBitmapReuse() { - return false; - } - - @Override - public int getMediaSize() { - return mImageBytes.length; - } - - @Override - protected void close() { - } - - @Override - public Drawable getDrawable(Resources resources) { - return null; - } - - @Override - boolean isEncoded() { - return true; - } - - @Override - MediaRequest getMediaDecodingRequest( - final MediaRequest originalRequest) { - Assert.isTrue(isEncoded()); - return new DecodeImageRequest(); - } - - /** - * A MediaRequest that decodes the encoded image resource. This class is chained to the - * original media request that requested the image, so it inherits the listener and - * properties such as binding. - */ - private class DecodeImageRequest implements MediaRequest { - public DecodeImageRequest() { - // Hold a ref onto the encoded resource before the request finishes. - addRef(); - } - - @Override - public String getKey() { - return EncodedImageResource.this.getKey(); - } - - @Override - @DoesNotRunOnMainThread - public ImageResource loadMediaBlocking(List> chainedTask) - throws Exception { - Assert.isNotMainThread(); - acquireLock(); - try { - final Bitmap decodedBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, - mImageBytes.length); - return new DecodedImageResource(getKey(), decodedBitmap, getOrientation()); - } finally { - releaseLock(); - release(); - } - } - - @Override - public MediaCache getMediaCache() { - // Decoded resource is non-cachable, it's for UI consumption only (for now at least) - return null; - } - - @Override - public int getCacheId() { - return 0; - } - - @Override - public int getRequestType() { - return REQUEST_DECODE_MEDIA; - } - - @Override - public MediaRequestDescriptor getDescriptor() { - return null; - } - } -} diff --git a/src/com/android/messaging/datamodel/media/FileImageRequest.java b/src/com/android/messaging/datamodel/media/FileImageRequest.java deleted file mode 100644 index 31c053a..0000000 --- a/src/com/android/messaging/datamodel/media/FileImageRequest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.ExifInterface; - -import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; - -/** - * Serves file system based image requests. Since file paths can be expressed in Uri form, this - * extends regular UriImageRequest but performs additional optimizations such as loading thumbnails - * directly from Exif information. - */ -public class FileImageRequest extends UriImageRequest { - private final String mPath; - private final boolean mCanUseThumbnail; - - public FileImageRequest(final Context context, - final FileImageRequestDescriptor descriptor) { - super(context, descriptor); - mPath = descriptor.path; - mCanUseThumbnail = descriptor.canUseThumbnail; - } - - @Override - protected Bitmap loadBitmapInternal() - throws IOException { - // Before using the FileInputStream, check if the Exif has a thumbnail that we can use. - if (mCanUseThumbnail) { - byte[] thumbnail = null; - try { - final ExifInterface exif = new ExifInterface(mPath); - if (exif.hasThumbnail()) { - thumbnail = exif.getThumbnail(); - } - } catch (final IOException e) { - // Nothing to do - } - - if (thumbnail != null) { - final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool( - false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */); - // First, check dimensions of the bitmap. - options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options); - - // Calculate inSampleSize - options.inSampleSize = ImageUtils.get().calculateInSampleSize(options, - mDescriptor.desiredWidth, mDescriptor.desiredHeight); - - options.inJustDecodeBounds = false; - - // Actually decode the bitmap, optionally using the bitmap pool. - try { - // Get the orientation. We should be able to get the orientation from - // the thumbnail itself but at least on some phones, the thumbnail - // doesn't have an orientation tag. So use the outer image's orientation - // tag and hope for the best. - mOrientation = ImageUtils.getOrientation(getInputStreamForResource()); - if (com.android.messaging.util.exif.ExifInterface. - getOrientationParams(mOrientation).invertDimensions) { - mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth); - } else { - mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight); - } - final ReusableImageResourcePool bitmapPool = getBitmapPool(); - if (bitmapPool == null) { - return BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, - options); - } else { - final int sampledWidth = options.outWidth / options.inSampleSize; - final int sampledHeight = options.outHeight / options.inSampleSize; - return bitmapPool.decodeByteArray(thumbnail, options, sampledWidth, - sampledHeight); - } - } catch (IOException ex) { - // If the thumbnail is broken due to IOException, this will - // fall back to default bitmap loading. - LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "FileImageRequest: failed to load " + - "thumbnail from Exif", ex); - } - } - } - - // Fall back to default InputStream-based loading if no thumbnails could be retrieved. - return super.loadBitmapInternal(); - } -} diff --git a/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java deleted file mode 100644 index 00105f5..0000000 --- a/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; - -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.UriUtil; - -/** - * Holds image request info about file system based image resource. - */ -public class FileImageRequestDescriptor extends UriImageRequestDescriptor { - public final String path; - - // Can we use the thumbnail image from Exif data? - public final boolean canUseThumbnail; - - /** - * Convenience constructor for when the image file's dimensions are not known. - */ - public FileImageRequestDescriptor(final String path, final int desiredWidth, - final int desiredHeight, final boolean canUseThumbnail, final boolean canCompress, - final boolean isStatic) { - this(path, desiredWidth, desiredHeight, FileImageRequest.UNSPECIFIED_SIZE, - FileImageRequest.UNSPECIFIED_SIZE, canUseThumbnail, canCompress, isStatic); - } - - /** - * Creates a new file image request with this descriptor. Oftentimes image file metadata - * has information such as the size of the image. Provide these metrics if they are known. - */ - public FileImageRequestDescriptor(final String path, final int desiredWidth, - final int desiredHeight, final int sourceWidth, final int sourceHeight, - final boolean canUseThumbnail, final boolean canCompress, final boolean isStatic) { - super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth, - sourceHeight, canCompress, isStatic, false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - this.path = path; - this.canUseThumbnail = canUseThumbnail; - } - - @Override - public String getKey() { - final String prefixKey = super.getKey(); - return prefixKey == null ? null : new StringBuilder(prefixKey).append(KEY_PART_DELIMITER) - .append(canUseThumbnail).toString(); - } - - @Override - public MediaRequest buildSyncMediaRequest(final Context context) { - return new FileImageRequest(context, this); - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/GifImageResource.java b/src/com/android/messaging/datamodel/media/GifImageResource.java deleted file mode 100644 index d50cf47..0000000 --- a/src/com/android/messaging/datamodel/media/GifImageResource.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.media.ExifInterface; -import android.support.rastermill.FrameSequence; -import android.support.rastermill.FrameSequenceDrawable; - -import com.android.messaging.util.Assert; - -import java.io.IOException; -import java.io.InputStream; - -public class GifImageResource extends ImageResource { - private FrameSequence mFrameSequence; - - public GifImageResource(String key, FrameSequence frameSequence) { - // GIF does not support exif tags - super(key, ExifInterface.ORIENTATION_NORMAL); - mFrameSequence = frameSequence; - } - - public static GifImageResource createGifImageResource(String key, InputStream inputStream) { - final FrameSequence frameSequence; - try { - frameSequence = FrameSequence.decodeStream(inputStream); - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // Nothing to do if we fail closing the stream - } - } - if (frameSequence == null) { - return null; - } - return new GifImageResource(key, frameSequence); - } - - @Override - public Drawable getDrawable(Resources resources) { - return new FrameSequenceDrawable(mFrameSequence); - } - - @Override - public Bitmap getBitmap() { - Assert.fail("GetBitmap() should never be called on a gif."); - return null; - } - - @Override - public byte[] getBytes() { - Assert.fail("GetBytes() should never be called on a gif."); - return null; - } - - @Override - public Bitmap reuseBitmap() { - return null; - } - - @Override - public boolean supportsBitmapReuse() { - // FrameSequenceDrawable a.) takes two bitmaps and thus does not fit into the current - // bitmap pool architecture b.) will rarely use bitmaps from one FrameSequenceDrawable to - // the next that are the same sizes since they are used by attachments. - return false; - } - - @Override - public int getMediaSize() { - Assert.fail("GifImageResource should not be used by a media cache"); - // Only used by the media cache, which this does not use. - return 0; - } - - @Override - public boolean isCacheable() { - return false; - } - - @Override - protected void close() { - acquireLock(); - try { - if (mFrameSequence != null) { - mFrameSequence = null; - } - } finally { - releaseLock(); - } - } - -} diff --git a/src/com/android/messaging/datamodel/media/ImageRequest.java b/src/com/android/messaging/datamodel/media/ImageRequest.java deleted file mode 100644 index ab8880d..0000000 --- a/src/com/android/messaging/datamodel/media/ImageRequest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; - -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.exif.ExifInterface; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -/** - * Base class that serves an image request for resolving, retrieving and decoding bitmap resources. - * - * Subclasses may choose to load images from different medium, such as from the file system or - * from the local content resolver, by overriding the abstract getInputStreamForResource() method. - */ -public abstract class ImageRequest - implements MediaRequest { - public static final int UNSPECIFIED_SIZE = MessagePartData.UNSPECIFIED_SIZE; - - protected final Context mContext; - protected final D mDescriptor; - protected int mOrientation; - - /** - * Creates a new image request with the given descriptor. - */ - public ImageRequest(final Context context, final D descriptor) { - mContext = context; - mDescriptor = descriptor; - } - - /** - * Gets a key that uniquely identify the underlying image resource to be loaded (e.g. Uri or - * file path). - */ - @Override - public String getKey() { - return mDescriptor.getKey(); - } - - /** - * Returns the image request descriptor attached to this request. - */ - @Override - public D getDescriptor() { - return mDescriptor; - } - - @Override - public int getRequestType() { - return MediaRequest.REQUEST_LOAD_MEDIA; - } - - /** - * Allows sub classes to specify that they want us to call getBitmapForResource rather than - * getInputStreamForResource - */ - protected boolean hasBitmapObject() { - return false; - } - - protected Bitmap getBitmapForResource() throws IOException { - return null; - } - - /** - * Retrieves an input stream from which image resource could be loaded. - * @throws FileNotFoundException - */ - protected abstract InputStream getInputStreamForResource() throws FileNotFoundException; - - /** - * Loads the image resource. This method is final; to override the media loading behavior - * the subclass should override {@link #loadMediaInternal(List)} - */ - @Override - public final ImageResource loadMediaBlocking(List> chainedTask) - throws IOException { - Assert.isNotMainThread(); - final ImageResource loadedResource = loadMediaInternal(chainedTask); - return postProcessOnBitmapResourceLoaded(loadedResource); - } - - protected ImageResource loadMediaInternal(List> chainedTask) - throws IOException { - if (!mDescriptor.isStatic() && isGif()) { - final GifImageResource gifImageResource = - GifImageResource.createGifImageResource(getKey(), getInputStreamForResource()); - if (gifImageResource == null) { - throw new RuntimeException("Error decoding gif"); - } - return gifImageResource; - } else { - final Bitmap loadedBitmap = loadBitmapInternal(); - if (loadedBitmap == null) { - throw new RuntimeException("failed decoding bitmap"); - } - return new DecodedImageResource(getKey(), loadedBitmap, mOrientation); - } - } - - protected boolean isGif() throws FileNotFoundException { - return ImageUtils.isGif(getInputStreamForResource()); - } - - /** - * The internal routine for loading the image. The caller may optionally provide the width - * and height of the source image if known so that we don't need to manually decode those. - */ - protected Bitmap loadBitmapInternal() throws IOException { - - final boolean unknownSize = mDescriptor.sourceWidth == UNSPECIFIED_SIZE || - mDescriptor.sourceHeight == UNSPECIFIED_SIZE; - - // If the ImageRequest has a Bitmap object rather than a stream, there's little to do here - if (hasBitmapObject()) { - final Bitmap bitmap = getBitmapForResource(); - if (bitmap != null && unknownSize) { - mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight()); - } - return bitmap; - } - - mOrientation = ImageUtils.getOrientation(getInputStreamForResource()); - - final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool( - false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */); - // First, check dimensions of the bitmap if not already known. - if (unknownSize) { - final InputStream inputStream = getInputStreamForResource(); - if (inputStream != null) { - try { - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(inputStream, null, options); - // This is called when dimensions of image were unknown to allow db update - if (ExifInterface.getOrientationParams(mOrientation).invertDimensions) { - mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth); - } else { - mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight); - } - } finally { - inputStream.close(); - } - } else { - throw new FileNotFoundException(); - } - } else { - options.outWidth = mDescriptor.sourceWidth; - options.outHeight = mDescriptor.sourceHeight; - } - - // Calculate inSampleSize - options.inSampleSize = ImageUtils.get().calculateInSampleSize(options, - mDescriptor.desiredWidth, mDescriptor.desiredHeight); - Assert.isTrue(options.inSampleSize > 0); - - // Reopen the input stream and actually decode the bitmap. The initial - // BitmapFactory.decodeStream() reads the header portion of the bitmap stream and leave - // the input stream at the last read position. Since this input stream doesn't support - // mark() and reset(), the only viable way to reload the input stream is to re-open it. - // Alternatively, we could decode the bitmap into a byte array first and act on the byte - // array, but that also means the entire bitmap (for example a 10MB image from the gallery) - // without downsampling will have to be loaded into memory up front, which we don't want - // as it gives a much bigger possibility of OOM when handling big images. Therefore, the - // solution here is to close and reopen the bitmap input stream. - // For inline images the size is cached in DB and this hit is only taken once per image - final InputStream inputStream = getInputStreamForResource(); - if (inputStream != null) { - try { - options.inJustDecodeBounds = false; - - // Actually decode the bitmap, optionally using the bitmap pool. - final ReusableImageResourcePool bitmapPool = getBitmapPool(); - if (bitmapPool == null) { - return BitmapFactory.decodeStream(inputStream, null, options); - } else { - final int sampledWidth = (options.outWidth + options.inSampleSize - 1) / - options.inSampleSize; - final int sampledHeight = (options.outHeight + options.inSampleSize - 1) / - options.inSampleSize; - return bitmapPool.decodeSampledBitmapFromInputStream( - inputStream, options, sampledWidth, sampledHeight); - } - } finally { - inputStream.close(); - } - } else { - throw new FileNotFoundException(); - } - } - - private ImageResource postProcessOnBitmapResourceLoaded(final ImageResource loadedResource) { - if (mDescriptor.cropToCircle && loadedResource instanceof DecodedImageResource) { - final int width = mDescriptor.desiredWidth; - final int height = mDescriptor.desiredHeight; - final Bitmap sourceBitmap = loadedResource.getBitmap(); - final Bitmap targetBitmap = getBitmapPool().createOrReuseBitmap(width, height); - final RectF dest = new RectF(0, 0, width, height); - final RectF source = new RectF(0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight()); - final int backgroundColor = mDescriptor.circleBackgroundColor; - final int strokeColor = mDescriptor.circleStrokeColor; - ImageUtils.drawBitmapWithCircleOnCanvas(sourceBitmap, new Canvas(targetBitmap), source, - dest, null, backgroundColor == 0 ? false : true /* fillBackground */, - backgroundColor, strokeColor); - return new DecodedImageResource(getKey(), targetBitmap, - loadedResource.getOrientation()); - } - return loadedResource; - } - - /** - * Returns the bitmap pool for this image request. - */ - protected ReusableImageResourcePool getBitmapPool() { - return MediaCacheManager.get().getOrCreateBitmapPoolForCache(getCacheId()); - } - - @SuppressWarnings("unchecked") - @Override - public MediaCache getMediaCache() { - return (MediaCache) MediaCacheManager.get().getOrCreateMediaCacheById( - getCacheId()); - } - - /** - * Returns the cache id. Subclasses may override this to use a different cache. - */ - @Override - public int getCacheId() { - return BugleMediaCacheManager.DEFAULT_IMAGE_CACHE; - } -} diff --git a/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java deleted file mode 100644 index 20cb9af..0000000 --- a/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; - -import com.android.messaging.util.Assert; - -/** - * The base ImageRequest descriptor that describes the requirement of the requested image - * resource, including the desired size. It holds request info that will be consumed by - * ImageRequest instances. Subclasses of ImageRequest are expected to take - * more descriptions such as content URI or file path. - */ -public abstract class ImageRequestDescriptor extends MediaRequestDescriptor { - /** Desired size for the image (if known). This is used for bitmap downsampling */ - public final int desiredWidth; - public final int desiredHeight; - - /** Source size of the image (if known). This is used so that we don't have to manually decode - * the metrics from the image resource */ - public final int sourceWidth; - public final int sourceHeight; - - /** - * A static image resource is required, even if the image format supports animation (like Gif). - */ - public final boolean isStatic; - - /** - * The loaded image will be cropped to circular shape. - */ - public final boolean cropToCircle; - - /** - * The loaded image will be cropped to circular shape with the background color. - */ - public final int circleBackgroundColor; - - /** - * The loaded image will be cropped to circular shape with a stroke color. - */ - public final int circleStrokeColor; - - protected static final char KEY_PART_DELIMITER = '|'; - - /** - * Creates a new image request with unspecified width and height. In this case, the full - * bitmap is loaded and decoded, so unless you are sure that the image will be of - * reasonable size, you should consider limiting at least one of the two dimensions - * (for example, limiting the image width to the width of the ImageView container). - */ - public ImageRequestDescriptor() { - this(ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, - ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0); - } - - public ImageRequestDescriptor(final int desiredWidth, final int desiredHeight) { - this(desiredWidth, desiredHeight, - ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0); - } - - public ImageRequestDescriptor(final int desiredWidth, - final int desiredHeight, final int sourceWidth, final int sourceHeight, - final boolean isStatic, final boolean cropToCircle, final int circleBackgroundColor, - int circleStrokeColor) { - Assert.isTrue(desiredWidth == ImageRequest.UNSPECIFIED_SIZE || desiredWidth > 0); - Assert.isTrue(desiredHeight == ImageRequest.UNSPECIFIED_SIZE || desiredHeight > 0); - Assert.isTrue(sourceWidth == ImageRequest.UNSPECIFIED_SIZE || sourceWidth > 0); - Assert.isTrue(sourceHeight == ImageRequest.UNSPECIFIED_SIZE || sourceHeight > 0); - this.desiredWidth = desiredWidth; - this.desiredHeight = desiredHeight; - this.sourceWidth = sourceWidth; - this.sourceHeight = sourceHeight; - this.isStatic = isStatic; - this.cropToCircle = cropToCircle; - this.circleBackgroundColor = circleBackgroundColor; - this.circleStrokeColor = circleStrokeColor; - } - - public String getKey() { - return new StringBuilder() - .append(desiredWidth).append(KEY_PART_DELIMITER) - .append(desiredHeight).append(KEY_PART_DELIMITER) - .append(String.valueOf(cropToCircle)).append(KEY_PART_DELIMITER) - .append(String.valueOf(circleBackgroundColor)).append(KEY_PART_DELIMITER) - .append(String.valueOf(isStatic)).toString(); - } - - public boolean isStatic() { - return isStatic; - } - - @Override - public abstract MediaRequest buildSyncMediaRequest(Context context); - - // Called once source dimensions finally determined upon loading the image - public void updateSourceDimensions(final int sourceWidth, final int sourceHeight) { - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/ImageResource.java b/src/com/android/messaging/datamodel/media/ImageResource.java deleted file mode 100644 index 75d817d..0000000 --- a/src/com/android/messaging/datamodel/media/ImageResource.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; - -/** - * Base class for holding some form of image resource. The subclass gets to define the specific - * type of data format it's holding, whether it be Bitmap objects or compressed byte arrays. - */ -public abstract class ImageResource extends RefCountedMediaResource { - protected final int mOrientation; - - public ImageResource(final String key, int orientation) { - super(key); - mOrientation = orientation; - } - - /** - * Gets the contained image in drawable format. - */ - public abstract Drawable getDrawable(Resources resources); - - /** - * Gets the contained image in bitmap format. - */ - public abstract Bitmap getBitmap(); - - /** - * Gets the contained image in byte array format. - */ - public abstract byte[] getBytes(); - - /** - * Attempt to reuse the bitmap in the image resource and re-purpose it for something else. - * After this, the image resource will relinquish ownership on the bitmap resource so that - * it doesn't try to recycle it when getting closed. - */ - public abstract Bitmap reuseBitmap(); - public abstract boolean supportsBitmapReuse(); - - /** - * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants - */ - public int getOrientation() { - return mOrientation; - } -} diff --git a/src/com/android/messaging/datamodel/media/MediaBytes.java b/src/com/android/messaging/datamodel/media/MediaBytes.java deleted file mode 100644 index 823bf27..0000000 --- a/src/com/android/messaging/datamodel/media/MediaBytes.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - - -/** - * Container class for handing around media information used by the MediaResourceManager. - */ -public class MediaBytes extends RefCountedMediaResource { - private final byte[] mBytes; - - public MediaBytes(final String key, final byte[] bytes) { - super(key); - mBytes = bytes; - } - - public byte[] getMediaBytes() { - return mBytes; - } - - @Override - public int getMediaSize() { - return mBytes.length; - } - - @Override - protected void close() { - } -} diff --git a/src/com/android/messaging/datamodel/media/MediaCache.java b/src/com/android/messaging/datamodel/media/MediaCache.java deleted file mode 100644 index 510da2d..0000000 --- a/src/com/android/messaging/datamodel/media/MediaCache.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.util.LruCache; - -import com.android.messaging.util.LogUtil; - -/** - * A modified LruCache that is able to hold RefCountedMediaResource instances. It releases - * ref on the entries as they are evicted from the cache, and it uses the media resource - * size in kilobytes, instead of the entry count, as the size of the cache. - * - * This class is used by the MediaResourceManager class to maintain a number of caches for - * holding different types of {@link RefCountedMediaResource} - */ -public class MediaCache extends LruCache { - private static final String TAG = LogUtil.BUGLE_IMAGE_TAG; - - // Default memory cache size in kilobytes - protected static final int DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES = 1024 * 5; // 5MB - - // Unique identifier for the cache. - private final int mId; - // Descriptive name given to the cache for debugging purposes. - private final String mName; - - // Convenience constructor that uses the default cache size. - public MediaCache(final int id, final String name) { - this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name); - } - - public MediaCache(final int maxSize, final int id, final String name) { - super(maxSize); - mId = id; - mName = name; - } - - public void destroy() { - evictAll(); - } - - public String getName() { - return mName; - } - - public int getId() { - return mId; - } - - /** - * Gets a media resource from this cache. Must use this method to get resource instead of get() - * to ensure addRef() on the resource. - */ - public synchronized T fetchResourceFromCache(final String key) { - final T ret = get(key); - if (ret != null) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "cache hit in mediaCache @ " + getName() + - ", total cache hit = " + hitCount() + - ", total cache miss = " + missCount()); - } - ret.addRef(); - } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "cache miss in mediaCache @ " + getName() + - ", total cache hit = " + hitCount() + - ", total cache miss = " + missCount()); - } - return ret; - } - - /** - * Add a media resource to this cache. Must use this method to add resource instead of put() - * to ensure addRef() on the resource. - */ - public synchronized T addResourceToCache(final String key, final T mediaResource) { - mediaResource.addRef(); - return put(key, mediaResource); - } - - /** - * Notify the removed entry that is no longer being cached - */ - @Override - protected synchronized void entryRemoved(final boolean evicted, final String key, - final T oldValue, final T newValue) { - oldValue.release(); - } - - /** - * Measure item size in kilobytes rather than units which is more practical - * for a media resource cache - */ - @Override - protected int sizeOf(final String key, final T value) { - final int mediaSizeInKilobytes = value.getMediaSize() / 1024; - // Never zero-count any resource, count as at least 1KB. - return mediaSizeInKilobytes == 0 ? 1 : mediaSizeInKilobytes; - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/MediaCacheManager.java b/src/com/android/messaging/datamodel/media/MediaCacheManager.java deleted file mode 100644 index 6e029f2..0000000 --- a/src/com/android/messaging/datamodel/media/MediaCacheManager.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.util.SparseArray; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache; -import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool; - -/** - * Manages a set of media caches by id. - */ -public abstract class MediaCacheManager implements MemoryCache { - public static MediaCacheManager get() { - return Factory.get().getMediaCacheManager(); - } - - protected final SparseArray> mCaches; - - public MediaCacheManager() { - mCaches = new SparseArray>(); - MemoryCacheManager.get().registerMemoryCache(this); - } - - @Override - public void reclaim() { - final int count = mCaches.size(); - for (int i = 0; i < count; i++) { - mCaches.valueAt(i).destroy(); - } - mCaches.clear(); - } - - public synchronized MediaCache getOrCreateMediaCacheById(final int id) { - MediaCache cache = mCaches.get(id); - if (cache == null) { - cache = createMediaCacheById(id); - if (cache != null) { - mCaches.put(id, cache); - } - } - return cache; - } - - public ReusableImageResourcePool getOrCreateBitmapPoolForCache(final int cacheId) { - final MediaCache cache = getOrCreateMediaCacheById(cacheId); - if (cache != null && cache instanceof PoolableImageCache) { - return ((PoolableImageCache) cache).asReusableBitmapPool(); - } - return null; - } - - protected abstract MediaCache createMediaCacheById(final int id); -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/MediaRequest.java b/src/com/android/messaging/datamodel/media/MediaRequest.java deleted file mode 100644 index 703671b..0000000 --- a/src/com/android/messaging/datamodel/media/MediaRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import java.util.List; - -/** - * Keeps track of a media loading request. MediaResourceManager uses this interface to load, encode, - * decode, and cache different types of media resource. - * - * This interface defines a media request class that's threading-model-blind. Wrapper classes - * (such as {@link AsyncMediaRequestWrapper} wraps around any base media request to offer async - * extensions). - */ -public interface MediaRequest { - public static final int REQUEST_ENCODE_MEDIA = 1; - public static final int REQUEST_DECODE_MEDIA = 2; - public static final int REQUEST_LOAD_MEDIA = 3; - - /** - * Returns a unique key used for storing and looking up the MediaRequest. - */ - String getKey(); - - /** - * This method performs the heavy-lifting work of synchronously loading the media bytes for - * this MediaRequest on a single threaded executor. - * @param chainedTask subsequent tasks to be performed after this request is complete. For - * example, an image request may need to compress the image resource before putting it in the - * cache - */ - T loadMediaBlocking(List> chainedTask) throws Exception; - - /** - * Returns the media cache where this MediaRequest wants to store the loaded - * media resource. - */ - MediaCache getMediaCache(); - - /** - * Returns the id of the cache where this MediaRequest wants to store the loaded - * media resource. - */ - int getCacheId(); - - /** - * Returns the request type of this media request, i.e. one of {@link #REQUEST_ENCODE_MEDIA}, - * {@link #REQUEST_DECODE_MEDIA}, or {@link #REQUEST_LOAD_MEDIA}. The default is - * {@link #REQUEST_LOAD_MEDIA} - */ - int getRequestType(); - - /** - * Returns the descriptor defining the request. - */ - MediaRequestDescriptor getDescriptor(); -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java deleted file mode 100644 index 216b2a3..0000000 --- a/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; - -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; - -/** - * The base data holder/builder class for constructing async/sync MediaRequest objects during - * runtime. - */ -public abstract class MediaRequestDescriptor { - public abstract MediaRequest buildSyncMediaRequest(Context context); - - /** - * Builds an async media request to be used with - * {@link MediaResourceManager#requestMediaResourceAsync(MediaRequest)} - */ - public BindableMediaRequest buildAsyncMediaRequest(final Context context, - final MediaResourceLoadListener listener) { - final MediaRequest syncRequest = buildSyncMediaRequest(context); - return AsyncMediaRequestWrapper.createWith(syncRequest, listener); - } -} diff --git a/src/com/android/messaging/datamodel/media/MediaResourceManager.java b/src/com/android/messaging/datamodel/media/MediaResourceManager.java deleted file mode 100644 index 13f7291..0000000 --- a/src/com/android/messaging/datamodel/media/MediaResourceManager.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.os.AsyncTask; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnAnyThread; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - *

Loads and maintains a set of in-memory LRU caches for different types of media resources. - * Right now we don't utilize any disk cache as all media urls are expected to be resolved to - * local content.

- * - *

The MediaResourceManager takes media loading requests through one of two ways:

- * - *
    - *
  1. {@link #requestMediaResourceAsync(MediaRequest)} that takes a MediaRequest, which may be a - * regular request if the caller doesn't want to listen for events (fire-and-forget), - * or an async request wrapper if event callback is needed.
  2. - *
  3. {@link #requestMediaResourceSync(MediaRequest)} which takes a MediaRequest and synchronously - * returns the loaded result, or null if failed.
  4. - *
- * - *

For each media loading task, MediaResourceManager starts an AsyncTask that runs on a - * dedicated thread, which calls MediaRequest.loadMediaBlocking() to perform the actual media - * loading work. As the media resources are loaded, MediaResourceManager notifies the callers - * (which must implement the MediaResourceLoadListener interface) via onMediaResourceLoaded() - * callback. Meanwhile, MediaResourceManager also pushes the loaded resource onto its dedicated - * cache.

- * - *

The media resource caches ({@link MediaCache}) are maintained as a set of LRU caches. They are - * created on demand by the incoming MediaRequest's getCacheId() method. The implementations of - * MediaRequest (such as {@link ImageRequest}) get to determine the desired cache id. For Bugle, - * the list of available caches are in {@link BugleMediaCacheManager}

- * - *

Optionally, media loading can support on-demand media encoding and decoding. - * All {@link MediaRequest}'s can opt to chain additional {@link MediaRequest}'s to be executed - * after the completion of the main media loading task, by adding new tasks to the chained - * task list in {@link MediaRequest#loadMediaBlocking(List)}. One possible type of chained task is - * media encoding task. Loaded media will be encoded on a dedicated single threaded executor - * *after* the UI is notified of the loaded media. In this case, the encoded media resource will - * be eventually pushed to the cache, which will later be decoded before posting to the UI thread - * on cache hit.

- * - *

To add support for a new type of media resource,

- * - *
    - *
  1. Create a new subclass of {@link RefCountedMediaResource} for the new resource type (example: - * {@link ImageResource} class).
  2. - * - *
  3. Implement the {@link MediaRequest} interface (example: {@link ImageRequest}). Perform the - * media loading work in loadMediaBlocking() and return a cache id in getCacheId().
  4. - * - *
  5. For the UI component that requests the media resource, let it implement - * {@link MediaResourceLoadListener} interface to listen for resource load callback. Let the - * UI component call MediaResourceManager.requestMediaResourceAsync() to request a media source. - * (example: {@link com.android.messaging.ui.ContactIconView}
  6. - *
- */ -public class MediaResourceManager { - private static final String TAG = LogUtil.BUGLE_TAG; - - public static MediaResourceManager get() { - return Factory.get().getMediaResourceManager(); - } - - /** - * Listener for asynchronous callback from media loading events. - */ - public interface MediaResourceLoadListener { - void onMediaResourceLoaded(MediaRequest request, T resource, boolean cached); - void onMediaResourceLoadError(MediaRequest request, Exception exception); - } - - // We use a fixed thread pool for handling media loading tasks. Using a cached thread pool - // allows for unlimited thread creation which can lead to OOMs so we limit the threads here. - private static final Executor MEDIA_LOADING_EXECUTOR = Executors.newFixedThreadPool(10); - - // A dedicated single thread executor for performing background task after loading the resource - // on the media loading executor. This includes work such as encoding loaded media to be cached. - // These tasks are run on a single worker thread with low priority so as not to contend with the - // media loading tasks. - private static final Executor MEDIA_BACKGROUND_EXECUTOR = Executors.newSingleThreadExecutor( - new ThreadFactory() { - @Override - public Thread newThread(final Runnable runnable) { - final Thread encodingThread = new Thread(runnable); - encodingThread.setPriority(Thread.MIN_PRIORITY); - return encodingThread; - } - }); - - /** - * Requests a media resource asynchronously. Upon completion of the media loading task, - * the listener will be notified of success/failure iff it's still bound. A refcount on the - * resource is held and guaranteed for the caller for the duration of the - * {@link MediaResourceLoadListener#onMediaResourceLoaded( - * MediaRequest, RefCountedMediaResource, boolean)} callback. - * @param mediaRequest the media request. May be either an - * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media - * request for fire-and-forget type of behavior. - */ - public void requestMediaResourceAsync( - final MediaRequest mediaRequest) { - scheduleAsyncMediaRequest(mediaRequest, MEDIA_LOADING_EXECUTOR); - } - - /** - * Requests a media resource synchronously. - * @return the loaded resource with a refcount reserved for the caller. The caller must call - * release() on the resource once it's done using it (like with Cursors). - */ - public T requestMediaResourceSync( - final MediaRequest mediaRequest) { - Assert.isNotMainThread(); - // Block and load media. - MediaLoadingResult loadResult = null; - try { - loadResult = processMediaRequestInternal(mediaRequest); - // The loaded resource should have at least one refcount by now reserved for the caller. - Assert.isTrue(loadResult.loadedResource.getRefCount() > 0); - return loadResult.loadedResource; - } catch (final Exception e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Synchronous media loading failed, key=" + - mediaRequest.getKey(), e); - return null; - } finally { - if (loadResult != null) { - // Schedule the background requests chained to the main request. - loadResult.scheduleChainedRequests(); - } - } - } - - @SuppressWarnings("unchecked") - private MediaLoadingResult processMediaRequestInternal( - final MediaRequest mediaRequest) - throws Exception { - final List> chainedRequests = new ArrayList<>(); - T loadedResource = null; - // Try fetching from cache first. - final T cachedResource = loadMediaFromCache(mediaRequest); - if (cachedResource != null) { - if (cachedResource.isEncoded()) { - // The resource is encoded, issue a decoding request. - final MediaRequest decodeRequest = (MediaRequest) cachedResource - .getMediaDecodingRequest(mediaRequest); - Assert.notNull(decodeRequest); - cachedResource.release(); - loadedResource = loadMediaFromRequest(decodeRequest, chainedRequests); - } else { - // The resource is ready-to-use. - loadedResource = cachedResource; - } - } else { - // Actually load the media after cache miss. - loadedResource = loadMediaFromRequest(mediaRequest, chainedRequests); - } - return new MediaLoadingResult<>(loadedResource, cachedResource != null /* fromCache */, - chainedRequests); - } - - private T loadMediaFromCache( - final MediaRequest mediaRequest) { - if (mediaRequest.getRequestType() != MediaRequest.REQUEST_LOAD_MEDIA) { - // Only look up in the cache if we are loading media. - return null; - } - final MediaCache mediaCache = mediaRequest.getMediaCache(); - if (mediaCache != null) { - final T mediaResource = mediaCache.fetchResourceFromCache(mediaRequest.getKey()); - if (mediaResource != null) { - return mediaResource; - } - } - return null; - } - - private T loadMediaFromRequest( - final MediaRequest mediaRequest, final List> chainedRequests) - throws Exception { - final T resource = mediaRequest.loadMediaBlocking(chainedRequests); - // mediaRequest.loadMediaBlocking() should never return null without - // throwing an exception. - Assert.notNull(resource); - // It's possible for the media to be evicted right after it's added to - // the cache (possibly because it's by itself too big for the cache). - // It's also possible that, after added to the cache, something else comes - // to the cache and evicts this media resource. To prevent this from - // recycling the underlying resource objects, make sure to add ref before - // adding to cache so that the caller is guaranteed a ref on the resource. - resource.addRef(); - // Don't cache the media request if it is defined as non-cacheable. - if (resource.isCacheable()) { - addResourceToMemoryCache(mediaRequest, resource); - } - return resource; - } - - /** - * Schedule an async media request on the given executor. - * @param mediaRequest the media request to be processed asynchronously. May be either an - * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media - * request for fire-and-forget type of behavior. - */ - private void scheduleAsyncMediaRequest( - final MediaRequest mediaRequest, final Executor executor) { - final BindableMediaRequest bindableRequest = - (mediaRequest instanceof BindableMediaRequest) ? - (BindableMediaRequest) mediaRequest : null; - if (bindableRequest != null && !bindableRequest.isBound()) { - return; // Request is obsolete - } - // We don't use SafeAsyncTask here since it enforces the shared thread pool executor - // whereas we want a dedicated thread pool executor. - AsyncTask> mediaLoadingTask = - new AsyncTask>() { - private Exception mException; - - @Override - protected MediaLoadingResult doInBackground(Void... params) { - // Double check the request is still valid by the time we start processing it - if (bindableRequest != null && !bindableRequest.isBound()) { - return null; // Request is obsolete - } - try { - return processMediaRequestInternal(mediaRequest); - } catch (Exception e) { - mException = e; - return null; - } - } - - @Override - protected void onPostExecute(final MediaLoadingResult result) { - if (result != null) { - Assert.isNull(mException); - Assert.isTrue(result.loadedResource.getRefCount() > 0); - try { - if (bindableRequest != null) { - bindableRequest.onMediaResourceLoaded( - bindableRequest, result.loadedResource, result.fromCache); - } - } finally { - result.loadedResource.release(); - result.scheduleChainedRequests(); - } - } else if (mException != null) { - LogUtil.e(LogUtil.BUGLE_TAG, "Asynchronous media loading failed, key=" + - mediaRequest.getKey(), mException); - if (bindableRequest != null) { - bindableRequest.onMediaResourceLoadError(bindableRequest, mException); - } - } else { - Assert.isTrue(bindableRequest == null || !bindableRequest.isBound()); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "media request not processed, no longer bound; key=" + - LogUtil.sanitizePII(mediaRequest.getKey()) /* key with phone# */); - } - } - } - }; - mediaLoadingTask.executeOnExecutor(executor, (Void) null); - } - - @VisibleForTesting - @RunsOnAnyThread - void addResourceToMemoryCache( - final MediaRequest mediaRequest, final T mediaResource) { - Assert.isTrue(mediaResource != null); - final MediaCache mediaCache = mediaRequest.getMediaCache(); - if (mediaCache != null) { - mediaCache.addResourceToCache(mediaRequest.getKey(), mediaResource); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "added media resource to " + mediaCache.getName() + ". key=" + - LogUtil.sanitizePII(mediaRequest.getKey()) /* key can contain phone# */); - } - } - } - - private class MediaLoadingResult { - public final T loadedResource; - public final boolean fromCache; - private final List> mChainedRequests; - - MediaLoadingResult(final T loadedResource, final boolean fromCache, - final List> chainedRequests) { - this.loadedResource = loadedResource; - this.fromCache = fromCache; - mChainedRequests = chainedRequests; - } - - /** - * Asynchronously schedule a list of chained requests on the background thread. - */ - public void scheduleChainedRequests() { - for (final MediaRequest mediaRequest : mChainedRequests) { - scheduleAsyncMediaRequest(mediaRequest, MEDIA_BACKGROUND_EXECUTOR); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java deleted file mode 100644 index 1871e66..0000000 --- a/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.media; - -import android.net.Uri; - -import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.util.ImageUtils; - -/** - * Image descriptor attached to a message part. - * Once image size is determined during loading this descriptor will update the db if necessary. - */ -public class MessagePartImageRequestDescriptor extends UriImageRequestDescriptor { - private final String mMessagePartId; - - /** - * Creates a new image request for a message part. - */ - public MessagePartImageRequestDescriptor(final MessagePartData messagePart, - final int desiredWidth, final int desiredHeight, boolean isStatic) { - // Pull image parameters out of the MessagePart record - this(messagePart.getPartId(), messagePart.getContentUri(), desiredWidth, desiredHeight, - messagePart.getWidth(), messagePart.getHeight(), isStatic); - } - - protected MessagePartImageRequestDescriptor(final String messagePartId, final Uri contentUri, - final int desiredWidth, final int desiredHeight, final int sourceWidth, - final int sourceHeight, boolean isStatic) { - super(contentUri, desiredWidth, desiredHeight, sourceWidth, sourceHeight, - true /* allowCompression */, isStatic, false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - mMessagePartId = messagePartId; - } - - @Override - public void updateSourceDimensions(final int updatedWidth, final int updatedHeight) { - // If the dimensions of the image do not match then queue a DB update with new size. - // Don't update if we don't have a part id, which happens if this part is loaded as - // draft through actions such as share intent/message forwarding. - if (mMessagePartId != null && - updatedWidth != MessagePartData.UNSPECIFIED_SIZE && - updatedHeight != MessagePartData.UNSPECIFIED_SIZE && - updatedWidth != sourceWidth && updatedHeight != sourceHeight) { - UpdateMessagePartSizeAction.updateSize(mMessagePartId, updatedWidth, updatedHeight); - } - } -} diff --git a/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java deleted file mode 100644 index ff11e92..0000000 --- a/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.datamodel.data.MessagePartData; - -public class MessagePartVideoThumbnailRequestDescriptor extends MessagePartImageRequestDescriptor { - public MessagePartVideoThumbnailRequestDescriptor(MessagePartData messagePart) { - super(messagePart, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false); - } - - public MessagePartVideoThumbnailRequestDescriptor(Uri uri) { - super(null, uri, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, - ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false); - } - - @Override - public MediaRequest buildSyncMediaRequest(final Context context) { - return new VideoThumbnailRequest(context, this); - } -} diff --git a/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java b/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java deleted file mode 100644 index 642e947..0000000 --- a/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Serves network content URI based image requests. - */ -public class NetworkUriImageRequest extends - ImageRequest { - - public NetworkUriImageRequest(Context context, D descriptor) { - super(context, descriptor); - mOrientation = android.media.ExifInterface.ORIENTATION_UNDEFINED; - } - - @Override - protected InputStream getInputStreamForResource() throws FileNotFoundException { - Assert.isNotMainThread(); - // Since we need to have an open urlConnection to get the stream, but we don't want to keep - // that connection open. There is no good way to perform this method. - return null; - } - - @Override - protected boolean isGif() throws FileNotFoundException { - Assert.isNotMainThread(); - - HttpURLConnection connection = null; - try { - final URL url = new URL(mDescriptor.uri.toString()); - connection = (HttpURLConnection) url.openConnection(); - connection.connect(); - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - return ContentType.IMAGE_GIF.equalsIgnoreCase(connection.getContentType()); - } - } catch (MalformedURLException e) { - LogUtil.e(LogUtil.BUGLE_TAG, - "MalformedUrl for image with url: " - + mDescriptor.uri.toString(), e); - } catch (IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, - "IOException trying to get inputStream for image with url: " - + mDescriptor.uri.toString(), e); - } finally { - if (connection != null) { - connection.disconnect(); - } - } - return false; - } - - @SuppressWarnings("deprecation") - @Override - public Bitmap loadBitmapInternal() throws IOException { - Assert.isNotMainThread(); - - InputStream inputStream = null; - Bitmap bitmap = null; - HttpURLConnection connection = null; - try { - final URL url = new URL(mDescriptor.uri.toString()); - connection = (HttpURLConnection) url.openConnection(); - connection.setDoInput(true); - connection.connect(); - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - bitmap = BitmapFactory.decodeStream(connection.getInputStream()); - } - } catch (MalformedURLException e) { - LogUtil.e(LogUtil.BUGLE_TAG, - "MalformedUrl for image with url: " - + mDescriptor.uri.toString(), e); - } catch (final OutOfMemoryError e) { - LogUtil.e(LogUtil.BUGLE_TAG, - "OutOfMemoryError for image with url: " - + mDescriptor.uri.toString(), e); - Factory.get().reclaimMemory(); - } catch (IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, - "IOException trying to get inputStream for image with url: " - + mDescriptor.uri.toString(), e); - } finally { - if (inputStream != null) { - inputStream.close(); - } - if (connection != null) { - connection.disconnect(); - } - } - return bitmap; - } -} diff --git a/src/com/android/messaging/datamodel/media/PoolableImageCache.java b/src/com/android/messaging/datamodel/media/PoolableImageCache.java deleted file mode 100644 index df814ba..0000000 --- a/src/com/android/messaging/datamodel/media/PoolableImageCache.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.util.SparseArray; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; - -/** - * A media cache that holds image resources, which doubles as a bitmap pool that allows the - * consumer to optionally decode image resources using unused bitmaps stored in the cache. - */ -public class PoolableImageCache extends MediaCache { - private static final int MIN_TIME_IN_POOL = 5000; - - /** Encapsulates bitmap pool representation of the image cache */ - private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool(); - - public PoolableImageCache(final int id, final String name) { - this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name); - } - - public PoolableImageCache(final int maxSize, final int id, final String name) { - super(maxSize, id, name); - } - - /** - * Creates a new BitmapFactory.Options for using the self-contained bitmap pool. - */ - public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled, - final int inputDensity, final int targetDensity) { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = scaled; - options.inDensity = inputDensity; - options.inTargetDensity = targetDensity; - options.inSampleSize = 1; - options.inJustDecodeBounds = false; - options.inMutable = true; - return options; - } - - @Override - public synchronized ImageResource addResourceToCache(final String key, - final ImageResource imageResource) { - mReusablePoolAccessor.onResourceEnterCache(imageResource); - return super.addResourceToCache(key, imageResource); - } - - @Override - protected synchronized void entryRemoved(final boolean evicted, final String key, - final ImageResource oldValue, final ImageResource newValue) { - mReusablePoolAccessor.onResourceLeaveCache(oldValue); - super.entryRemoved(evicted, key, oldValue, newValue); - } - - /** - * Returns a representation of the image cache as a reusable bitmap pool. - */ - public ReusableImageResourcePool asReusableBitmapPool() { - return mReusablePoolAccessor; - } - - /** - * A bitmap pool representation built on top of the image cache. It treats the image resources - * stored in the image cache as a self-contained bitmap pool and is able to create or - * reclaim bitmap resource as needed. - */ - public class ReusableImageResourcePool { - private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF; - private static final int INVALID_POOL_KEY = 0; - - /** - * Number of reuse failures to skip before reporting. - * For debugging purposes, change to a lower number for more frequent reporting. - */ - private static final int FAILED_REPORTING_FREQUENCY = 100; - - /** - * Count of reuse failures which have occurred. - */ - private volatile int mFailedBitmapReuseCount = 0; - - /** - * Count of reuse successes which have occurred. - */ - private volatile int mSucceededBitmapReuseCount = 0; - - /** - * A sparse array from bitmap size to a list of image cache entries that match the - * given size. This map is used to quickly retrieve a usable bitmap to be reused by an - * incoming ImageRequest. We need to ensure that this sparse array always contains only - * elements currently in the image cache with no other consumer. - */ - private final SparseArray> mImageListSparseArray; - - public ReusableImageResourcePool() { - mImageListSparseArray = new SparseArray>(); - } - - /** - * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce - * memory turnover. - * @param inputStream InputStream load. Cannot be null. - * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). - * Cannot be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return The decoded Bitmap with the resource drawn in it. - * @throws IOException - */ - public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream, - @NonNull final BitmapFactory.Options optionsTmp, - final int width, final int height) throws IOException { - if (width <= 0 || height <= 0) { - // This is an invalid / corrupted image of zero size. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + - "invalid size"); - throw new IOException("Invalid size / corrupted image"); - } - Assert.notNull(inputStream); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - mSucceededBitmapReuseCount++; - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap.recycle(); - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - onFailedToReuse(); - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); - Factory.get().reclaimMemory(); - } - return b; - } - - /** - * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce - * memory turnover. - * @param bytes Encoded bytes to draw on the bitmap. Cannot be null. - * @param optionsTmp The bitmap will set here and the input should be generated from - * getBitmapOptionsForPool(). Cannot be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return A Bitmap with the encoded bytes drawn in it. - * @throws IOException - */ - public Bitmap decodeByteArray(@NonNull final byte[] bytes, - @NonNull final BitmapFactory.Options optionsTmp, final int width, - final int height) throws OutOfMemoryError, IOException { - if (width <= 0 || height <= 0) { - // This is an invalid / corrupted image of zero size. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + - "invalid size"); - throw new IOException("Invalid size / corrupted image"); - } - Assert.notNull(bytes); - Assert.notNull(optionsTmp); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - mSucceededBitmapReuseCount++; - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - // (i.e. without the bitmap from the pool) - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap.recycle(); - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - onFailedToReuse(); - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); - Factory.get().reclaimMemory(); - } - return b; - } - - /** - * Called when a new image resource is added to the cache. We add the resource to the - * pool so it's properly keyed into the pool structure. - */ - void onResourceEnterCache(final ImageResource imageResource) { - if (getPoolKey(imageResource) != INVALID_POOL_KEY) { - addResourceToPool(imageResource); - } - } - - /** - * Called when an image resource is evicted from the cache. Bitmap pool's entries are - * strictly tied to their presence in the image cache. Once an image is evicted from the - * cache, it should be removed from the pool. - */ - void onResourceLeaveCache(final ImageResource imageResource) { - if (getPoolKey(imageResource) != INVALID_POOL_KEY) { - removeResourceFromPool(imageResource); - } - } - - private void addResourceToPool(final ImageResource imageResource) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(imageResource); - Assert.isTrue(poolKey != INVALID_POOL_KEY); - LinkedList imageList = mImageListSparseArray.get(poolKey); - if (imageList == null) { - imageList = new LinkedList(); - mImageListSparseArray.put(poolKey, imageList); - } - imageList.addLast(imageResource); - } - } - - private void removeResourceFromPool(final ImageResource imageResource) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(imageResource); - Assert.isTrue(poolKey != INVALID_POOL_KEY); - final LinkedList imageList = mImageListSparseArray.get(poolKey); - if (imageList != null) { - imageList.remove(imageResource); - } - } - } - - /** - * Try to get a reusable bitmap from the pool with the given width and height. As a - * result of this call, the caller will assume ownership of the returned bitmap. - */ - private Bitmap getReusableBitmapFromPool(final int width, final int height) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(width, height); - if (poolKey != INVALID_POOL_KEY) { - final LinkedList images = mImageListSparseArray.get(poolKey); - if (images != null && images.size() > 0) { - // Try to reuse the first available bitmap from the pool list. We start from - // the least recently added cache entry of the given size. - ImageResource imageToUse = null; - for (int i = 0; i < images.size(); i++) { - final ImageResource image = images.get(i); - if (image.getRefCount() == 1) { - image.acquireLock(); - if (image.getRefCount() == 1) { - // The image is only used by the cache, so it's reusable. - imageToUse = images.remove(i); - break; - } else { - // Logically, this shouldn't happen, because as soon as the - // cache is the only user of this resource, it will not be - // used by anyone else until the next cache access, but we - // currently hold on to the cache lock. But technically - // future changes may violate this assumption, so warn about - // this. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " + - "from 1 in getReusableBitmapFromPool()"); - image.releaseLock(); - } - } - } - - if (imageToUse == null) { - return null; - } - - try { - imageToUse.assertLockHeldByCurrentThread(); - - // Only reuse the bitmap if the last time we use was greater than 5s. - // This allows the cache a chance to reuse instead of always taking the - // oldest. - final long timeSinceLastRef = SystemClock.elapsedRealtime() - - imageToUse.getLastRefAddTimestamp(); - if (timeSinceLastRef < MIN_TIME_IN_POOL) { - if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " + - "first available bitmap from the pool because it " + - "has not been in the pool long enough. " + - "timeSinceLastRef=" + timeSinceLastRef); - } - // Put back the image and return no reuseable bitmap. - images.addLast(imageToUse); - return null; - } - - // Add a temp ref on the image resource so it won't be GC'd after - // being removed from the cache. - imageToUse.addRef(); - - // Remove the image resource from the image cache. - final ImageResource removed = remove(imageToUse.getKey()); - Assert.isTrue(removed == imageToUse); - - // Try to reuse the bitmap from the image resource. This will transfer - // ownership of the bitmap object to the caller of this method. - final Bitmap reusableBitmap = imageToUse.reuseBitmap(); - - imageToUse.release(); - return reusableBitmap; - } finally { - // We are either done with the reuse operation, or decided not to use - // the image. Either way, release the lock. - imageToUse.releaseLock(); - } - } - } - } - return null; - } - - /** - * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. - * @param width desired bitmap width - * @param height desired bitmap height - * @return the created or reused mutable bitmap that has its background cleared to - * {@value Color#TRANSPARENT} - */ - public Bitmap createOrReuseBitmap(final int width, final int height) { - return createOrReuseBitmap(width, height, Color.TRANSPARENT); - } - - /** - * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. - * @param width desired bitmap width - * @param height desired bitmap height - * @param backgroundColor the background color for the returned bitmap - * @return the created or reused mutable bitmap with the requested background color - */ - public Bitmap createOrReuseBitmap(final int width, final int height, - final int backgroundColor) { - Bitmap retBitmap = null; - try { - final Bitmap poolBitmap = getReusableBitmapFromPool(width, height); - retBitmap = (poolBitmap != null) ? poolBitmap : - Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - retBitmap.eraseColor(backgroundColor); - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap"); - Factory.get().reclaimMemory(); - } - return retBitmap; - } - - private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width, - final int height) { - if (optionsTmp.inJustDecodeBounds) { - return; - } - optionsTmp.inBitmap = getReusableBitmapFromPool(width, height); - } - - /** - * @return The pool key for the provided image dimensions or 0 if either width or height is - * greater than the max supported image dimension. - */ - private int getPoolKey(final int width, final int height) { - if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) { - return INVALID_POOL_KEY; - } - return (width << 16) | height; - } - - /** - * @return the pool key for a given image resource. - */ - private int getPoolKey(final ImageResource imageResource) { - if (imageResource.supportsBitmapReuse()) { - final Bitmap bitmap = imageResource.getBitmap(); - if (bitmap != null && bitmap.isMutable()) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - if (width > 0 && height > 0) { - return getPoolKey(width, height); - } - } - } - return INVALID_POOL_KEY; - } - - /** - * Called when bitmap reuse fails. Conditionally report the failure with statistics. - */ - private void onFailedToReuse() { - mFailedBitmapReuseCount++; - if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, - "Pooled bitmap consistently not being reused. Failure count = " + - mFailedBitmapReuseCount + ", success count = " + - mSucceededBitmapReuseCount); - } - } - } -} diff --git a/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java b/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java deleted file mode 100644 index c21f477..0000000 --- a/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.os.SystemClock; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.google.common.base.Throwables; - -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A ref-counted class that holds loaded media resource, be it bitmaps or media bytes. - * Subclasses must implement the close() method to release any resources (such as bitmaps) - * when it's no longer used. - * - * Instances of the subclasses are: - * 1. Loaded by their corresponding MediaRequest classes. - * 2. Maintained by MediaResourceManager in its MediaCache pool. - * 3. Used by the UI (such as ContactIconViews) to present the content. - * - * Note: all synchronized methods in this class (e.g. addRef()) should not attempt to make outgoing - * calls that could potentially acquire media cache locks due to the potential deadlock this can - * cause. To synchronize read/write access to shared resource, {@link #acquireLock()} and - * {@link #releaseLock()} must be used, instead of using synchronized keyword. - */ -public abstract class RefCountedMediaResource { - private final String mKey; - private int mRef = 0; - private long mLastRefAddTimestamp; - - // Set DEBUG to true to enable detailed stack trace for each addRef() and release() operation - // to find out where each ref change happens. - private static final boolean DEBUG = false; - private static final String TAG = "bugle_media_ref_history"; - private final ArrayList mRefHistory = new ArrayList(); - - // A lock that guards access to shared members in this class (and all its subclasses). - private final ReentrantLock mLock = new ReentrantLock(); - - public RefCountedMediaResource(final String key) { - mKey = key; - } - - public String getKey() { - return mKey; - } - - public void addRef() { - acquireLock(); - try { - if (DEBUG) { - mRefHistory.add("Added ref current ref = " + mRef); - mRefHistory.add(Throwables.getStackTraceAsString(new Exception())); - } - - mRef++; - mLastRefAddTimestamp = SystemClock.elapsedRealtime(); - } finally { - releaseLock(); - } - } - - public void release() { - acquireLock(); - try { - if (DEBUG) { - mRefHistory.add("Released ref current ref = " + mRef); - mRefHistory.add(Throwables.getStackTraceAsString(new Exception())); - } - - mRef--; - if (mRef == 0) { - close(); - } else if (mRef < 0) { - if (DEBUG) { - LogUtil.i(TAG, "Unwinding ref count history for RefCountedMediaResource " - + this); - for (final String ref : mRefHistory) { - LogUtil.i(TAG, ref); - } - } - Assert.fail("RefCountedMediaResource has unbalanced ref. Refcount=" + mRef); - } - } finally { - releaseLock(); - } - } - - public int getRefCount() { - acquireLock(); - try { - return mRef; - } finally { - releaseLock(); - } - } - - public long getLastRefAddTimestamp() { - acquireLock(); - try { - return mLastRefAddTimestamp; - } finally { - releaseLock(); - } - } - - public void assertSingularRefCount() { - acquireLock(); - try { - Assert.equals(1, mRef); - } finally { - releaseLock(); - } - } - - void acquireLock() { - mLock.lock(); - } - - void releaseLock() { - mLock.unlock(); - } - - void assertLockHeldByCurrentThread() { - Assert.isTrue(mLock.isHeldByCurrentThread()); - } - - boolean isEncoded() { - return false; - } - - boolean isCacheable() { - return true; - } - - MediaRequest getMediaDecodingRequest( - final MediaRequest originalRequest) { - return null; - } - - MediaRequest getMediaEncodingRequest( - final MediaRequest originalRequest) { - return null; - } - - public abstract int getMediaSize(); - protected abstract void close(); -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java b/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java deleted file mode 100644 index e4f0334..0000000 --- a/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.media.ExifInterface; -import android.text.TextUtils; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; - -import java.io.IOException; -import java.util.List; - -public class SimSelectorAvatarRequest extends AvatarRequest { - private static Bitmap sRegularSimIcon; - - public SimSelectorAvatarRequest(final Context context, - final AvatarRequestDescriptor descriptor) { - super(context, descriptor); - } - - /** - * {@inheritDoc} - */ - @Override - protected ImageResource loadMediaInternal(List> chainedTasks) - throws IOException { - Assert.isNotMainThread(); - final String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri); - if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)){ - final int width = mDescriptor.desiredWidth; - final int height = mDescriptor.desiredHeight; - final String identifier = AvatarUriUtil.getIdentifier(mDescriptor.uri); - final boolean simSelected = AvatarUriUtil.getSimSelected(mDescriptor.uri); - final int simColor = AvatarUriUtil.getSimColor(mDescriptor.uri); - final boolean incoming = AvatarUriUtil.getSimIncoming(mDescriptor.uri); - return renderSimAvatarInternal(identifier, width, height, simColor, simSelected, - incoming); - } - return super.loadMediaInternal(chainedTasks); - } - - private ImageResource renderSimAvatarInternal(final String identifier, final int width, - final int height, final int subColor, final boolean selected, final boolean incoming) { - final Resources resources = mContext.getResources(); - final float halfWidth = width / 2; - final float halfHeight = height / 2; - final int minOfWidthAndHeight = Math.min(width, height); - final int backgroundColor = selected ? subColor : Color.WHITE; - final int textColor = selected ? subColor : Color.WHITE; - final int simColor = selected ? Color.WHITE : subColor; - final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height, backgroundColor); - final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Canvas canvas = new Canvas(bitmap); - - if (sRegularSimIcon == null) { - final BitmapDrawable regularSim = (BitmapDrawable) mContext.getResources() - .getDrawable(R.drawable.ic_sim_card_send); - sRegularSimIcon = regularSim.getBitmap(); - } - - paint.setColorFilter(new PorterDuffColorFilter(simColor, PorterDuff.Mode.SRC_ATOP)); - paint.setAlpha(0xff); - canvas.drawBitmap(sRegularSimIcon, halfWidth - sRegularSimIcon.getWidth() / 2, - halfHeight - sRegularSimIcon.getHeight() / 2, paint); - paint.setColorFilter(null); - paint.setAlpha(0xff); - - if (!TextUtils.isEmpty(identifier)) { - paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); - paint.setColor(textColor); - final float letterToTileRatio = - resources.getFraction(R.dimen.sim_identifier_to_tile_ratio, 1, 1); - paint.setTextSize(letterToTileRatio * minOfWidthAndHeight); - - final String firstCharString = identifier.substring(0, 1).toUpperCase(); - final Rect textBound = new Rect(); - paint.getTextBounds(firstCharString, 0, 1, textBound); - - final float xOffset = halfWidth - textBound.centerX(); - final float yOffset = halfHeight - textBound.centerY(); - canvas.drawText(firstCharString, xOffset, yOffset, paint); - } - - return new DecodedImageResource(getKey(), bitmap, ExifInterface.ORIENTATION_NORMAL); - } - - @Override - public int getCacheId() { - return BugleMediaCacheManager.AVATAR_IMAGE_CACHE; - } -} diff --git a/src/com/android/messaging/datamodel/media/UriImageRequest.java b/src/com/android/messaging/datamodel/media/UriImageRequest.java deleted file mode 100644 index b4934ca..0000000 --- a/src/com/android/messaging/datamodel/media/UriImageRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -/** - * Serves local content URI based image requests. - */ -public class UriImageRequest extends ImageRequest { - public UriImageRequest(final Context context, final D descriptor) { - super(context, descriptor); - } - - @Override - protected InputStream getInputStreamForResource() throws FileNotFoundException { - return mContext.getContentResolver().openInputStream(mDescriptor.uri); - } - - @Override - protected ImageResource loadMediaInternal(List> chainedTasks) - throws IOException { - final ImageResource resource = super.loadMediaInternal(chainedTasks); - // Check if the caller asked for compression. If so, chain an encoding task if possible. - if (mDescriptor.allowCompression && chainedTasks != null) { - @SuppressWarnings("unchecked") - final MediaRequest chainedTask = (MediaRequest) - resource.getMediaEncodingRequest(this); - if (chainedTask != null) { - chainedTasks.add(chainedTask); - // Don't cache decoded image resource since we'll perform compression and cache - // the compressed resource. - if (resource instanceof DecodedImageResource) { - ((DecodedImageResource) resource).setCacheable(false); - } - } - } - return resource; - } -} diff --git a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java deleted file mode 100644 index c5685d1..0000000 --- a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.util.UriUtil; - -public class UriImageRequestDescriptor extends ImageRequestDescriptor { - public final Uri uri; - public final boolean allowCompression; - - public UriImageRequestDescriptor(final Uri uri) { - this(uri, UriImageRequest.UNSPECIFIED_SIZE, UriImageRequest.UNSPECIFIED_SIZE, false, false, - false, 0, 0); - } - - public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight) - { - this(uri, desiredWidth, desiredHeight, false, false, false, 0, 0); - } - - public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight, - final boolean cropToCircle, final int circleBackgroundColor, int circleStrokeColor) - { - this(uri, desiredWidth, desiredHeight, false, - false, cropToCircle, circleBackgroundColor, circleStrokeColor); - } - - public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight, final boolean allowCompression, boolean isStatic, - boolean cropToCircle, int circleBackgroundColor, int circleStrokeColor) { - this(uri, desiredWidth, desiredHeight, UriImageRequest.UNSPECIFIED_SIZE, - UriImageRequest.UNSPECIFIED_SIZE, allowCompression, isStatic, cropToCircle, - circleBackgroundColor, circleStrokeColor); - } - - /** - * Creates a new Uri-based image request. - * @param uri the content Uri. Currently Bugle only supports local resources Uri (i.e. it has - * to begin with content: or android.resource: - * @param circleStrokeColor - */ - public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, - final int desiredHeight, final int sourceWidth, final int sourceHeight, - final boolean allowCompression, final boolean isStatic, final boolean cropToCircle, - final int circleBackgroundColor, int circleStrokeColor) { - super(desiredWidth, desiredHeight, sourceWidth, sourceHeight, isStatic, - cropToCircle, circleBackgroundColor, circleStrokeColor); - this.uri = uri; - this.allowCompression = allowCompression; - } - - @Override - public String getKey() { - if (uri != null) { - final String key = super.getKey(); - if (key != null) { - return new StringBuilder() - .append(uri).append(KEY_PART_DELIMITER) - .append(String.valueOf(allowCompression)).append(KEY_PART_DELIMITER) - .append(key).toString(); - } - } - return null; - } - - @Override - public MediaRequest buildSyncMediaRequest(final Context context) { - if (uri == null || UriUtil.isLocalUri(uri)) { - return new UriImageRequest(context, this); - } else { - return new NetworkUriImageRequest(context, this); - } - } - - /** ID of the resource in MediaStore or null if this resource didn't come from MediaStore */ - public Long getMediaStoreId() { - return null; - } -} \ No newline at end of file diff --git a/src/com/android/messaging/datamodel/media/VCardRequest.java b/src/com/android/messaging/datamodel/media/VCardRequest.java deleted file mode 100644 index d6e992c..0000000 --- a/src/com/android/messaging/datamodel/media/VCardRequest.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.UriUtil; -import com.android.vcard.VCardConfig; -import com.android.vcard.VCardEntry; -import com.android.vcard.VCardEntryCounter; -import com.android.vcard.VCardInterpreter; -import com.android.vcard.VCardParser; -import com.android.vcard.VCardParser_V21; -import com.android.vcard.VCardParser_V30; -import com.android.vcard.VCardSourceDetector; -import com.android.vcard.exception.VCardException; -import com.android.vcard.exception.VCardNestedException; -import com.android.vcard.exception.VCardNotSupportedException; -import com.android.vcard.exception.VCardVersionException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Requests and parses VCard data. In Bugle, we need to display VCard details in the conversation - * view such as avatar icon and name, which can be expensive if we parse VCard every time. - * Therefore, we'd like to load the vcard once and cache it in our media cache using the - * MediaResourceManager component. To load the VCard, we use framework's VCard support to - * interpret the VCard content, which gives us information such as phone and email list, which - * we'll put in VCardResource object to be cached. - * - * Some particular attention is needed for the avatar icon. If the VCard contains avatar icon, - * it's in byte array form that can't easily be cached/persisted. Therefore, we persist the - * image bytes to the scratch directory and generate a content Uri for it, so that ContactIconView - * may use this Uri to display and cache the image if needed. - */ -public class VCardRequest implements MediaRequest { - private final Context mContext; - private final VCardRequestDescriptor mDescriptor; - private final List mLoadedVCards; - private VCardResource mLoadedResource; - private static final int VCARD_LOADING_TIMEOUT_MILLIS = 10000; // 10s - private static final String DEFAULT_VCARD_TYPE = "default"; - - VCardRequest(final Context context, final VCardRequestDescriptor descriptor) { - mDescriptor = descriptor; - mContext = context; - mLoadedVCards = new ArrayList(); - } - - @Override - public String getKey() { - return mDescriptor.vCardUri.toString(); - } - - @Override - @DoesNotRunOnMainThread - public VCardResource loadMediaBlocking(List> chainedTask) - throws Exception { - Assert.isNotMainThread(); - Assert.isTrue(mLoadedResource == null); - Assert.equals(0, mLoadedVCards.size()); - - // The VCard library doesn't support synchronously loading the media resource. Therefore, - // We have to burn the thread waiting for the result to come back. - final CountDownLatch signal = new CountDownLatch(1); - if (!parseVCard(mDescriptor.vCardUri, signal)) { - // Directly fail without actually going through the interpreter, return immediately. - throw new VCardException("Invalid vcard"); - } - - signal.await(VCARD_LOADING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - if (mLoadedResource == null) { - // Maybe null if failed or timeout. - throw new VCardException("Failure or timeout loading vcard"); - } - return mLoadedResource; - } - - @Override - public int getCacheId() { - return BugleMediaCacheManager.VCARD_CACHE; - } - - @SuppressWarnings("unchecked") - @Override - public MediaCache getMediaCache() { - return (MediaCache) MediaCacheManager.get().getOrCreateMediaCacheById( - getCacheId()); - } - - @DoesNotRunOnMainThread - private boolean parseVCard(final Uri targetUri, final CountDownLatch signal) { - Assert.isNotMainThread(); - final VCardEntryCounter counter = new VCardEntryCounter(); - final VCardSourceDetector detector = new VCardSourceDetector(); - boolean result; - try { - // We don't know which type should be used to parse the Uri. - // It is possible to misinterpret the vCard, but we expect the parser - // lets VCardSourceDetector detect the type before the misinterpretation. - result = readOneVCardFile(targetUri, VCardConfig.VCARD_TYPE_UNKNOWN, - detector, true, null); - } catch (final VCardNestedException e) { - try { - final int estimatedVCardType = detector.getEstimatedType(); - // Assume that VCardSourceDetector was able to detect the source. - // Try again with the detector. - result = readOneVCardFile(targetUri, estimatedVCardType, - counter, false, null); - } catch (final VCardNestedException e2) { - result = false; - LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e2); - } - } - - if (!result) { - // Load failure. - return false; - } - - return doActuallyReadOneVCard(targetUri, true, detector, null, signal); - } - - @DoesNotRunOnMainThread - private boolean doActuallyReadOneVCard(final Uri uri, final boolean showEntryParseProgress, - final VCardSourceDetector detector, final List errorFileNameList, - final CountDownLatch signal) { - Assert.isNotMainThread(); - int vcardType = detector.getEstimatedType(); - if (vcardType == VCardConfig.VCARD_TYPE_UNKNOWN) { - vcardType = VCardConfig.getVCardTypeFromString(DEFAULT_VCARD_TYPE); - } - final CustomVCardEntryConstructor builder = - new CustomVCardEntryConstructor(vcardType, null); - builder.addEntryHandler(new ContactVCardEntryHandler(signal)); - - try { - if (!readOneVCardFile(uri, vcardType, builder, false, null)) { - return false; - } - } catch (final VCardNestedException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e); - return false; - } - return true; - } - - @DoesNotRunOnMainThread - private boolean readOneVCardFile(final Uri uri, final int vcardType, - final VCardInterpreter interpreter, - final boolean throwNestedException, final List errorFileNameList) - throws VCardNestedException { - Assert.isNotMainThread(); - final ContentResolver resolver = mContext.getContentResolver(); - VCardParser vCardParser; - InputStream is; - try { - is = resolver.openInputStream(uri); - vCardParser = new VCardParser_V21(vcardType); - vCardParser.addInterpreter(interpreter); - - try { - vCardParser.parse(is); - } catch (final VCardVersionException e1) { - try { - is.close(); - } catch (final IOException e) { - // Do nothing. - } - if (interpreter instanceof CustomVCardEntryConstructor) { - // Let the object clean up internal temporal objects, - ((CustomVCardEntryConstructor) interpreter).clear(); - } - - is = resolver.openInputStream(uri); - - try { - vCardParser = new VCardParser_V30(vcardType); - vCardParser.addInterpreter(interpreter); - vCardParser.parse(is); - } catch (final VCardVersionException e2) { - throw new VCardException("vCard with unspported version."); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - // Do nothing. - } - } - } - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "IOException was emitted: " + e.getMessage()); - - if (errorFileNameList != null) { - errorFileNameList.add(uri.toString()); - } - return false; - } catch (final VCardNotSupportedException e) { - if ((e instanceof VCardNestedException) && throwNestedException) { - throw (VCardNestedException) e; - } - if (errorFileNameList != null) { - errorFileNameList.add(uri.toString()); - } - return false; - } catch (final VCardException e) { - if (errorFileNameList != null) { - errorFileNameList.add(uri.toString()); - } - return false; - } - return true; - } - - class ContactVCardEntryHandler implements CustomVCardEntryConstructor.EntryHandler { - final CountDownLatch mSignal; - - public ContactVCardEntryHandler(final CountDownLatch signal) { - mSignal = signal; - } - - @Override - public void onStart() { - } - - @Override - @DoesNotRunOnMainThread - public void onEntryCreated(final CustomVCardEntry entry) { - Assert.isNotMainThread(); - final String displayName = entry.getDisplayName(); - final List photos = entry.getPhotoList(); - Uri avatarUri = null; - if (photos != null && photos.size() > 0) { - // The photo data is in bytes form, so we need to persist it in our temp directory - // so that ContactIconView can load it and display it later - // (and cache it, of course). - for (final VCardEntry.PhotoData photo : photos) { - final byte[] photoBytes = photo.getBytes(); - if (photoBytes != null) { - final InputStream inputStream = new ByteArrayInputStream(photoBytes); - try { - avatarUri = UriUtil.persistContentToScratchSpace(inputStream); - if (avatarUri != null) { - // Just load the first avatar and be done. Want more? wait for V2. - break; - } - } finally { - try { - inputStream.close(); - } catch (final IOException e) { - // Do nothing. - } - } - } - } - } - - // Fall back to generated avatar. - if (avatarUri == null) { - String destination = null; - final List phones = entry.getPhoneList(); - if (phones != null && phones.size() > 0) { - destination = PhoneUtils.getDefault().getCanonicalBySystemLocale( - phones.get(0).getNumber()); - } - - if (destination == null) { - final List emails = entry.getEmailList(); - if (emails != null && emails.size() > 0) { - destination = emails.get(0).getAddress(); - } - } - avatarUri = AvatarUriUtil.createAvatarUri(null, displayName, destination, null); - } - - // Add the loaded vcard to the list. - mLoadedVCards.add(new VCardResourceEntry(entry, avatarUri)); - } - - @Override - public void onEnd() { - // Finished loading all vCard entries, signal the loading thread to proceed with the - // result. - if (mLoadedVCards.size() > 0) { - mLoadedResource = new VCardResource(getKey(), mLoadedVCards); - } - mSignal.countDown(); - } - } - - @Override - public int getRequestType() { - return MediaRequest.REQUEST_LOAD_MEDIA; - } - - @Override - public MediaRequestDescriptor getDescriptor() { - return mDescriptor; - } -} diff --git a/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java deleted file mode 100644 index 4084851..0000000 --- a/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; -import android.net.Uri; - -import com.android.messaging.util.Assert; - -public class VCardRequestDescriptor extends MediaRequestDescriptor { - public final Uri vCardUri; - - public VCardRequestDescriptor(final Uri vCardUri) { - Assert.notNull(vCardUri); - this.vCardUri = vCardUri; - } - - @Override - public MediaRequest buildSyncMediaRequest(Context context) { - return new VCardRequest(context, this); - } -} diff --git a/src/com/android/messaging/datamodel/media/VCardResource.java b/src/com/android/messaging/datamodel/media/VCardResource.java deleted file mode 100644 index edf5e88..0000000 --- a/src/com/android/messaging/datamodel/media/VCardResource.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import java.util.List; - -/** - * Holds cached information of VCard contact info. - * The temporarily persisted avatar icon Uri is tied to the VCardResource. As a result, whenever - * the VCardResource is no longer used (i.e. close() is called), we need to asynchronously - * delete the avatar image from temp storage since no one will have reference to the avatar Uri - * again. The next time the same VCard is displayed, since the old resource has been evicted from - * the memory cache, we'll load and persist the avatar icon again. - */ -public class VCardResource extends RefCountedMediaResource { - private final List mVCards; - - public VCardResource(final String key, final List vcards) { - super(key); - mVCards = vcards; - } - - public List getVCards() { - return mVCards; - } - - @Override - public int getMediaSize() { - // Instead of track VCards by size in kilobytes, we track them by count. - return 0; - } - - @Override - protected void close() { - for (final VCardResourceEntry vcard : mVCards) { - vcard.close(); - } - } -} diff --git a/src/com/android/messaging/datamodel/media/VCardResourceEntry.java b/src/com/android/messaging/datamodel/media/VCardResourceEntry.java deleted file mode 100644 index f76b796..0000000 --- a/src/com/android/messaging/datamodel/media/VCardResourceEntry.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Intent; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.net.Uri; -import android.provider.ContactsContract.CommonDataKinds.Im; -import android.provider.ContactsContract.CommonDataKinds.Organization; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v4.util.ArrayMap; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.vcard.VCardEntry; -import com.android.vcard.VCardEntry.EmailData; -import com.android.vcard.VCardEntry.ImData; -import com.android.vcard.VCardEntry.NoteData; -import com.android.vcard.VCardEntry.OrganizationData; -import com.android.vcard.VCardEntry.PhoneData; -import com.android.vcard.VCardEntry.PostalData; -import com.android.vcard.VCardEntry.WebsiteData; -import com.android.vcard.VCardProperty; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; - -/** - * Holds one entry item (i.e. a single contact) within a VCard resource. It is able to take - * a VCardEntry and extract relevant information from it. - */ -public class VCardResourceEntry { - public static final String PROPERTY_KIND = "KIND"; - - public static final String KIND_LOCATION = "location"; - - private final List mContactInfo; - private final Uri mAvatarUri; - private final String mDisplayName; - private final CustomVCardEntry mVCard; - - public VCardResourceEntry(final CustomVCardEntry vcard, final Uri avatarUri) { - mContactInfo = getContactInfoFromVCardEntry(vcard); - mDisplayName = getDisplayNameFromVCardEntry(vcard); - mAvatarUri = avatarUri; - mVCard = vcard; - } - - void close() { - // If the avatar image was temporarily saved in the scratch folder, remove that. - if (MediaScratchFileProvider.isMediaScratchSpaceUri(mAvatarUri)) { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - Factory.get().getApplicationContext().getContentResolver().delete( - mAvatarUri, null, null); - } - }); - } - } - - public String getKind() { - VCardProperty kindProperty = mVCard.getProperty(PROPERTY_KIND); - return kindProperty == null ? null : kindProperty.getRawValue(); - } - - public Uri getAvatarUri() { - return mAvatarUri; - } - - public String getDisplayName() { - return mDisplayName; - } - - public String getDisplayAddress() { - List postalList = mVCard.getPostalList(); - if (postalList == null || postalList.size() < 1) { - return null; - } - - return formatAddress(postalList.get(0)); - } - - public String getNotes() { - List notes = mVCard.getNotes(); - if (notes == null || notes.size() == 0) { - return null; - } - StringBuilder noteBuilder = new StringBuilder(); - for (NoteData note : notes) { - noteBuilder.append(note.getNote()); - } - return noteBuilder.toString(); - } - - /** - * Returns a UI-facing representation that can be bound and consumed by the UI layer to display - * this VCard resource entry. - */ - public PersonItemData getDisplayItem() { - return new PersonItemData() { - @Override - public Uri getAvatarUri() { - return VCardResourceEntry.this.getAvatarUri(); - } - - @Override - public String getDisplayName() { - return VCardResourceEntry.this.getDisplayName(); - } - - @Override - public String getDetails() { - return null; - } - - @Override - public Intent getClickIntent() { - return null; - } - - @Override - public long getContactId() { - return ContactUtil.INVALID_CONTACT_ID; - } - - @Override - public String getLookupKey() { - return null; - } - - @Override - public String getNormalizedDestination() { - return null; - } - }; - } - - public List getContactInfo() { - return mContactInfo; - } - - private static List getContactInfoFromVCardEntry( - final VCardEntry vcard) { - final Resources resources = Factory.get().getApplicationContext().getResources(); - final List retList = - new ArrayList(); - if (vcard.getPhoneList() != null) { - for (final PhoneData phone : vcard.getPhoneList()) { - final Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse("tel:" + phone.getNumber())); - retList.add(new VCardResourceEntryDestinationItem(phone.getNumber(), - Phone.getTypeLabel(resources, phone.getType(), phone.getLabel()).toString(), - intent)); - } - } - - if (vcard.getEmailList() != null) { - for (final EmailData email : vcard.getEmailList()) { - final Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.parse("mailto:")); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] { email.getAddress() }); - retList.add(new VCardResourceEntryDestinationItem(email.getAddress(), - Phone.getTypeLabel(resources, email.getType(), - email.getLabel()).toString(), intent)); - } - } - - if (vcard.getPostalList() != null) { - for (final PostalData postalData : vcard.getPostalList()) { - String type; - try { - type = resources. - getStringArray(android.R.array.postalAddressTypes) - [postalData.getType() - 1]; - } catch (final NotFoundException ex) { - type = resources.getStringArray(android.R.array.postalAddressTypes)[2]; - } catch (final Exception e) { - LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem postal Exception:" + e); - type = resources.getStringArray(android.R.array.postalAddressTypes)[2]; - } - Intent intent = new Intent(Intent.ACTION_VIEW); - final String address = formatAddress(postalData); - try { - intent.setData(Uri.parse("geo:0,0?q=" + URLEncoder.encode(address, "UTF-8"))); - } catch (UnsupportedEncodingException e) { - intent = null; - } - - retList.add(new VCardResourceEntryDestinationItem(address, type, intent)); - } - } - - if (vcard.getImList() != null) { - for (final ImData imData : vcard.getImList()) { - String type = null; - try { - type = resources. - getString(Im.getProtocolLabelResource(imData.getProtocol())); - } catch (final NotFoundException ex) { - // Do nothing since this implies an empty label. - } - retList.add(new VCardResourceEntryDestinationItem(imData.getAddress(), type, null)); - } - } - - if (vcard.getOrganizationList() != null) { - for (final OrganizationData organtization : vcard.getOrganizationList()) { - String type = null; - try { - type = resources.getString(Organization.getTypeLabelResource( - organtization.getType())); - } catch (final NotFoundException ex) { - //set other kind as "other" - type = resources.getStringArray(android.R.array.organizationTypes)[1]; - } catch (final Exception e) { - LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem org Exception:" + e); - type = resources.getStringArray(android.R.array.organizationTypes)[1]; - } - retList.add(new VCardResourceEntryDestinationItem( - organtization.getOrganizationName(), type, null)); - } - } - - if (vcard.getWebsiteList() != null) { - for (final WebsiteData web : vcard.getWebsiteList()) { - if (web != null && TextUtils.isGraphic(web.getWebsite())){ - String website = web.getWebsite(); - if (!website.startsWith("http://") && !website.startsWith("https://")) { - // Prefix required for parsing to end up with a scheme and result in - // navigation - website = "http://" + website; - } - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(website)); - retList.add(new VCardResourceEntryDestinationItem(web.getWebsite(), null, - intent)); - } - } - } - - if (vcard.getBirthday() != null) { - final String birthday = vcard.getBirthday(); - if (TextUtils.isGraphic(birthday)){ - retList.add(new VCardResourceEntryDestinationItem(birthday, - resources.getString(R.string.vcard_detail_birthday_label), null)); - } - } - - if (vcard.getNotes() != null) { - for (final NoteData note : vcard.getNotes()) { - final ArrayMap curChildMap = new ArrayMap(); - if (TextUtils.isGraphic(note.getNote())){ - retList.add(new VCardResourceEntryDestinationItem(note.getNote(), - resources.getString(R.string.vcard_detail_notes_label), null)); - } - } - } - return retList; - } - - private static String formatAddress(final PostalData postalData) { - final StringBuilder sb = new StringBuilder(); - final String poBox = postalData.getPobox(); - if (!TextUtils.isEmpty(poBox)) { - sb.append(poBox); - sb.append(" "); - } - final String extendedAddress = postalData.getExtendedAddress(); - if (!TextUtils.isEmpty(extendedAddress)) { - sb.append(extendedAddress); - sb.append(" "); - } - final String street = postalData.getStreet(); - if (!TextUtils.isEmpty(street)) { - sb.append(street); - sb.append(" "); - } - final String localty = postalData.getLocalty(); - if (!TextUtils.isEmpty(localty)) { - sb.append(localty); - sb.append(" "); - } - final String region = postalData.getRegion(); - if (!TextUtils.isEmpty(region)) { - sb.append(region); - sb.append(" "); - } - final String postalCode = postalData.getPostalCode(); - if (!TextUtils.isEmpty(postalCode)) { - sb.append(postalCode); - sb.append(" "); - } - final String country = postalData.getCountry(); - if (!TextUtils.isEmpty(country)) { - sb.append(country); - } - return sb.toString(); - } - - private static String getDisplayNameFromVCardEntry(final VCardEntry vcard) { - String name = vcard.getDisplayName(); - if (name == null) { - vcard.consolidateFields(); - name = vcard.getDisplayName(); - } - return name; - } - - /** - * Represents one entry line (e.g. phone number and phone label) for a single contact. Each - * VCardResourceEntry may hold one or more VCardResourceEntryDestinationItem's. - */ - public static class VCardResourceEntryDestinationItem { - private final String mDisplayDestination; - private final String mDestinationType; - private final Intent mClickIntent; - public VCardResourceEntryDestinationItem(final String displayDestination, - final String destinationType, final Intent clickIntent) { - mDisplayDestination = displayDestination; - mDestinationType = destinationType; - mClickIntent = clickIntent; - } - - /** - * Returns a UI-facing representation that can be bound and consumed by the UI layer to - * display this VCard resource destination entry. - */ - public PersonItemData getDisplayItem() { - return new PersonItemData() { - @Override - public Uri getAvatarUri() { - return null; - } - - @Override - public String getDisplayName() { - return mDisplayDestination; - } - - @Override - public String getDetails() { - return mDestinationType; - } - - @Override - public Intent getClickIntent() { - return mClickIntent; - } - - @Override - public long getContactId() { - return ContactUtil.INVALID_CONTACT_ID; - } - - @Override - public String getLookupKey() { - return null; - } - - @Override - public String getNormalizedDestination() { - return null; - } - }; - } - } -} diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java deleted file mode 100644 index f17591c..0000000 --- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.media; - -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Bitmap; -import android.provider.MediaStore.Video.Thumbnails; - -import com.android.messaging.Factory; -import com.android.messaging.util.MediaMetadataRetrieverWrapper; -import com.android.messaging.util.OsUtil; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -/** - * Class to request a video thumbnail. - * Users of this class as responsible for checking {@link #shouldShowIncomingVideoThumbnails} - */ -public class VideoThumbnailRequest extends ImageRequest { - - public VideoThumbnailRequest(final Context context, - final UriImageRequestDescriptor descriptor) { - super(context, descriptor); - } - - public static boolean shouldShowIncomingVideoThumbnails() { - return OsUtil.isAtLeastM(); - } - - @Override - protected InputStream getInputStreamForResource() throws FileNotFoundException { - return null; - } - - @Override - protected boolean hasBitmapObject() { - return true; - } - - @Override - protected Bitmap getBitmapForResource() throws IOException { - final Long mediaId = mDescriptor.getMediaStoreId(); - Bitmap bitmap = null; - if (mediaId != null) { - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - bitmap = Thumbnails.getThumbnail(cr, mediaId, Thumbnails.MICRO_KIND, null); - } else { - final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper(); - try { - retriever.setDataSource(mDescriptor.uri); - bitmap = retriever.getFrameAtTime(); - } finally { - retriever.release(); - } - } - if (bitmap != null) { - mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight()); - } - return bitmap; - } -} diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java deleted file mode 100644 index 907bb8f..0000000 --- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.Context; - -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.UriUtil; - -public class VideoThumbnailRequestDescriptor extends UriImageRequestDescriptor { - protected final long mMediaId; - public VideoThumbnailRequestDescriptor(final long id, String path, int desiredWidth, - int desiredHeight, int sourceWidth, int sourceHeight) { - super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth, - sourceHeight, false /* canCompress */, false /* isStatic */, - false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - mMediaId = id; - } - - @Override - public MediaRequest buildSyncMediaRequest(Context context) { - return new VideoThumbnailRequest(context, this); - } - - @Override - public Long getMediaStoreId() { - return mMediaId; - } -} diff --git a/src/com/android/messaging/mmslib/Downloads.java b/src/com/android/messaging/mmslib/Downloads.java deleted file mode 100644 index 9afc48c..0000000 --- a/src/com/android/messaging/mmslib/Downloads.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib; - -import android.app.DownloadManager; -import android.content.Context; -import android.net.Uri; -import android.provider.BaseColumns; - -/** - * The Download Manager - * - * @pending - */ -public final class Downloads { - private Downloads() {} - - /** - * Implementation details - * - * Exposes constants used to interact with the download manager's - * content provider. - * The constants URI ... STATUS are the names of columns in the downloads table. - * - * @hide - */ - public static final class Impl implements BaseColumns { - private Impl() {} - - /** - * The permission to access the download manager - */ - public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER"; - - /** - * The permission to access the download manager's advanced functions - */ - public static final String PERMISSION_ACCESS_ADVANCED = - "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"; - - /** - * The permission to access the all the downloads in the manager. - */ - public static final String PERMISSION_ACCESS_ALL = - "android.permission.ACCESS_ALL_DOWNLOADS"; - - /** - * The permission to directly access the download manager's cache - * directory - */ - public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM"; - - /** - * The permission to send broadcasts on download completion - */ - public static final String PERMISSION_SEND_INTENTS = - "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"; - - /** - * The permission to download files to the cache partition that won't be automatically - * purged when space is needed. - */ - public static final String PERMISSION_CACHE_NON_PURGEABLE = - "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE"; - - /** - * The permission to download files without any system notification being shown. - */ - public static final String PERMISSION_NO_NOTIFICATION = - "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"; - - /** - * The content:// URI to access downloads owned by the caller's UID. - */ - public static final Uri CONTENT_URI = - Uri.parse("content://downloads/my_downloads"); - - /** - * The content URI for accessing all downloads across all UIDs (requires the - * ACCESS_ALL_DOWNLOADS permission). - */ - public static final Uri ALL_DOWNLOADS_CONTENT_URI = - Uri.parse("content://downloads/all_downloads"); - - /** URI segment to access a publicly accessible downloaded file */ - public static final String PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT = "public_downloads"; - - /** - * The content URI for accessing publicly accessible downloads (i.e., it requires no - * permissions to access this downloaded file) - */ - public static final Uri PUBLICLY_ACCESSIBLE_DOWNLOADS_URI = - Uri.parse("content://downloads/" + PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT); - - /** - * Broadcast Action: this is sent by the download manager to the app - * that had initiated a download when that download completes. The - * download's content: uri is specified in the intent's data. - */ - public static final String ACTION_DOWNLOAD_COMPLETED = - "android.intent.action.DOWNLOAD_COMPLETED"; - - /** - * Broadcast Action: this is sent by the download manager to the app - * that had initiated a download when the user selects the notification - * associated with that download. The download's content: uri is specified - * in the intent's data if the click is associated with a single download, - * or Downloads.CONTENT_URI if the notification is associated with - * multiple downloads. - * Note: this is not currently sent for downloads that have completed - * successfully. - */ - public static final String ACTION_NOTIFICATION_CLICKED = - "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; - - /** - * The name of the column containing the URI of the data being downloaded. - *

Type: TEXT

- *

Owner can Init/Read

- */ - public static final String COLUMN_URI = "uri"; - - /** - * The name of the column containing application-specific data. - *

Type: TEXT

- *

Owner can Init/Read/Write

- */ - public static final String COLUMN_APP_DATA = "entity"; - - /** - * The name of the column containing the flags that indicates whether - * the initiating application is capable of verifying the integrity of - * the downloaded file. When this flag is set, the download manager - * performs downloads and reports success even in some situations where - * it can't guarantee that the download has completed (e.g. when doing - * a byte-range request without an ETag, or when it can't determine - * whether a download fully completed). - *

Type: BOOLEAN

- *

Owner can Init

- */ - public static final String COLUMN_NO_INTEGRITY = "no_integrity"; - - /** - * The name of the column containing the filename that the initiating - * application recommends. When possible, the download manager will attempt - * to use this filename, or a variation, as the actual name for the file. - *

Type: TEXT

- *

Owner can Init

- */ - public static final String COLUMN_FILE_NAME_HINT = "hint"; - - /** - * The name of the column containing the filename where the downloaded data - * was actually stored. - *

Type: TEXT

- *

Owner can Read

- */ - public static final String _DATA = "_data"; - - /** - * The name of the column containing the MIME type of the downloaded data. - *

Type: TEXT

- *

Owner can Init/Read

- */ - public static final String COLUMN_MIME_TYPE = "mimetype"; - - /** - * The name of the column containing the flag that controls the destination - * of the download. See the DESTINATION_* constants for a list of legal values. - *

Type: INTEGER

- *

Owner can Init

- */ - public static final String COLUMN_DESTINATION = "destination"; - - /** - * The name of the column containing the flags that controls whether the - * download is displayed by the UI. See the VISIBILITY_* constants for - * a list of legal values. - *

Type: INTEGER

- *

Owner can Init/Read/Write

- */ - public static final String COLUMN_VISIBILITY = "visibility"; - - /** - * The name of the column containing the current control state of the download. - * Applications can write to this to control (pause/resume) the download. - * the CONTROL_* constants for a list of legal values. - *

Type: INTEGER

- *

Owner can Read

- */ - public static final String COLUMN_CONTROL = "control"; - - /** - * The name of the column containing the current status of the download. - * Applications can read this to follow the progress of each download. See - * the STATUS_* constants for a list of legal values. - *

Type: INTEGER

- *

Owner can Read

- */ - public static final String COLUMN_STATUS = "status"; - - /** - * The name of the column containing the date at which some interesting - * status changed in the download. Stored as a System.currentTimeMillis() - * value. - *

Type: BIGINT

- *

Owner can Read

- */ - public static final String COLUMN_LAST_MODIFICATION = "lastmod"; - - /** - * The name of the column containing the package name of the application - * that initiating the download. The download manager will send - * notifications to a component in this package when the download completes. - *

Type: TEXT

- *

Owner can Init/Read

- */ - public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage"; - - /** - * The name of the column containing the component name of the class that - * will receive notifications associated with the download. The - * package/class combination is passed to - * Intent.setClassName(String,String). - *

Type: TEXT

- *

Owner can Init/Read

- */ - public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass"; - - /** - * If extras are specified when requesting a download they will be provided in the intent - * that is sent to the specified class and package when a download has finished. - *

Type: TEXT

- *

Owner can Init

- */ - public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras"; - - /** - * The name of the column contain the values of the cookie to be used for - * the download. This is used directly as the value for the Cookie: HTTP - * header that gets sent with the request. - *

Type: TEXT

- *

Owner can Init

- */ - public static final String COLUMN_COOKIE_DATA = "cookiedata"; - - /** - * The name of the column containing the user agent that the initiating - * application wants the download manager to use for this download. - *

Type: TEXT

- *

Owner can Init

- */ - public static final String COLUMN_USER_AGENT = "useragent"; - - /** - * The name of the column containing the referer (sic) that the initiating - * application wants the download manager to use for this download. - *

Type: TEXT

- *

Owner can Init

- */ - public static final String COLUMN_REFERER = "referer"; - - /** - * The name of the column containing the total size of the file being - * downloaded. - *

Type: INTEGER

- *

Owner can Read

- */ - public static final String COLUMN_TOTAL_BYTES = "total_bytes"; - - /** - * The name of the column containing the size of the part of the file that - * has been downloaded so far. - *

Type: INTEGER

- *

Owner can Read

- */ - public static final String COLUMN_CURRENT_BYTES = "current_bytes"; - - /** - * The name of the column where the initiating application can provide the - * UID of another application that is allowed to access this download. If - * multiple applications share the same UID, all those applications will be - * allowed to access this download. This column can be updated after the - * download is initiated. This requires the permission - * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED. - *

Type: INTEGER

- *

Owner can Init

- */ - public static final String COLUMN_OTHER_UID = "otheruid"; - - /** - * The name of the column where the initiating application can provided the - * title of this download. The title will be displayed ito the user in the - * list of downloads. - *

Type: TEXT

- *

Owner can Init/Read/Write

- */ - public static final String COLUMN_TITLE = "title"; - - /** - * The name of the column where the initiating application can provide the - * description of this download. The description will be displayed to the - * user in the list of downloads. - *

Type: TEXT

- *

Owner can Init/Read/Write

- */ - public static final String COLUMN_DESCRIPTION = "description"; - - /** - * The name of the column indicating whether the download was requesting through the public - * API. This controls some differences in behavior. - *

Type: BOOLEAN

- *

Owner can Init/Read

- */ - public static final String COLUMN_IS_PUBLIC_API = "is_public_api"; - - /** - * The name of the column holding a bitmask of allowed network types. This is only used for - * public API downloads. - *

Type: INTEGER

- *

Owner can Init/Read

- */ - public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types"; - - /** - * The name of the column indicating whether roaming connections can be used. This is only - * used for public API downloads. - *

Type: BOOLEAN

- *

Owner can Init/Read

- */ - public static final String COLUMN_ALLOW_ROAMING = "allow_roaming"; - - /** - * The name of the column indicating whether metered connections can be used. This is only - * used for public API downloads. - *

Type: BOOLEAN

- *

Owner can Init/Read

- */ - public static final String COLUMN_ALLOW_METERED = "allow_metered"; - - /** - * Whether or not this download should be displayed in the system's Downloads UI. Defaults - * to true. - *

Type: INTEGER

- *

Owner can Init/Read

- */ - public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui"; - - /** - * If true, the user has confirmed that this download can proceed over the mobile network - * even though it exceeds the recommended maximum size. - *

Type: BOOLEAN

- */ - public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT = - "bypass_recommended_size_limit"; - - /** - * Set to true if this download is deleted. It is completely removed from the database - * when MediaProvider database also deletes the metadata asociated with this downloaded - * file. - *

Type: BOOLEAN

- *

Owner can Read

- */ - public static final String COLUMN_DELETED = "deleted"; - - /** - * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is - * used to delete the entries from MediaProvider database when it is deleted from the - * downloaded list. - *

Type: TEXT

- *

Owner can Read

- */ - public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri"; - - /** - * The column that is used to remember whether the media scanner was invoked. - * It can take the values: null or 0(not scanned), 1(scanned), 2 (not scannable). - *

Type: TEXT

- */ - public static final String COLUMN_MEDIA_SCANNED = "scanned"; - - /** - * The column with errorMsg for a failed downloaded. - * Used only for debugging purposes. - *

Type: TEXT

- */ - public static final String COLUMN_ERROR_MSG = "errorMsg"; - - /** - * This column stores the source of the last update to this row. - * This column is only for internal use. - * Valid values are indicated by LAST_UPDATESRC_* constants. - *

Type: INT

- */ - public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc"; - - /** The column that is used to count retries */ - public static final String COLUMN_FAILED_CONNECTIONS = "numfailed"; - - /** - * default value for {@link #COLUMN_LAST_UPDATESRC}. - * This value is used when this column's value is not relevant. - */ - public static final int LAST_UPDATESRC_NOT_RELEVANT = 0; - - /** - * One of the values taken by {@link #COLUMN_LAST_UPDATESRC}. - * This value is used when the update is NOT to be relayed to the DownloadService - * (and thus spare DownloadService from scanning the database when this change occurs) - */ - public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1; - - /* - * Lists the destinations that an application can specify for a download. - */ - - /** - * This download will be saved to the external storage. This is the - * default behavior, and should be used for any file that the user - * can freely access, copy, delete. Even with that destination, - * unencrypted DRM files are saved in secure internal storage. - * Downloads to the external destination only write files for which - * there is a registered handler. The resulting files are accessible - * by filename to all applications. - */ - public static final int DESTINATION_EXTERNAL = 0; - - /** - * This download will be saved to the download manager's private - * partition. This is the behavior used by applications that want to - * download private files that are used and deleted soon after they - * get downloaded. All file types are allowed, and only the initiating - * application can access the file (indirectly through a content - * provider). This requires the - * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission. - */ - public static final int DESTINATION_CACHE_PARTITION = 1; - - /** - * This download will be saved to the download manager's private - * partition and will be purged as necessary to make space. This is - * for private files (similar to CACHE_PARTITION) that aren't deleted - * immediately after they are used, and are kept around by the download - * manager as long as space is available. - */ - public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2; - - /** - * This download will be saved to the download manager's private - * partition, as with DESTINATION_CACHE_PARTITION, but the download - * will not proceed if the user is on a roaming data connection. - */ - public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3; - - /** - * This download will be saved to the location given by the file URI in - * {@link #COLUMN_FILE_NAME_HINT}. - */ - public static final int DESTINATION_FILE_URI = 4; - - /** - * This download will be saved to the system cache ("/cache") - * partition. This option is only used by system apps and so it requires - * android.permission.ACCESS_CACHE_FILESYSTEM permission. - */ - public static final int DESTINATION_SYSTEMCACHE_PARTITION = 5; - - /** - * This download was completed by the caller (i.e., NOT downloadmanager) - * and caller wants to have this download displayed in Downloads App. - */ - public static final int DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD = 6; - - /** - * This download is allowed to run. - */ - public static final int CONTROL_RUN = 0; - - /** - * This download must pause at the first opportunity. - */ - public static final int CONTROL_PAUSED = 1; - - /* - * Lists the states that the download manager can set on a download - * to notify applications of the download progress. - * The codes follow the HTTP families:
- * 1xx: informational
- * 2xx: success
- * 3xx: redirects (not used by the download manager)
- * 4xx: client errors
- * 5xx: server errors - */ - - /** - * Returns whether the status is informational (i.e. 1xx). - */ - public static boolean isStatusInformational(int status) { - return (status >= 100 && status < 200); - } - - /** - * Returns whether the status is a success (i.e. 2xx). - */ - public static boolean isStatusSuccess(int status) { - return (status >= 200 && status < 300); - } - - /** - * Returns whether the status is an error (i.e. 4xx or 5xx). - */ - public static boolean isStatusError(int status) { - return (status >= 400 && status < 600); - } - - /** - * Returns whether the status is a client error (i.e. 4xx). - */ - public static boolean isStatusClientError(int status) { - return (status >= 400 && status < 500); - } - - /** - * Returns whether the status is a server error (i.e. 5xx). - */ - public static boolean isStatusServerError(int status) { - return (status >= 500 && status < 600); - } - - /** - * this method determines if a notification should be displayed for a - * given {@link #COLUMN_VISIBILITY} value - * @param visibility the value of {@link #COLUMN_VISIBILITY}. - * @return true if the notification should be displayed. false otherwise. - */ - public static boolean isNotificationToBeDisplayed(int visibility) { - return visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED || - visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION; - } - - /** - * Returns whether the download has completed (either with success or - * error). - */ - public static boolean isStatusCompleted(int status) { - return (status >= 200 && status < 300) || (status >= 400 && status < 600); - } - - /** - * This download hasn't stated yet - */ - public static final int STATUS_PENDING = 190; - - /** - * This download has started - */ - public static final int STATUS_RUNNING = 192; - - /** - * This download has been paused by the owning app. - */ - public static final int STATUS_PAUSED_BY_APP = 193; - - /** - * This download encountered some network error and is waiting before retrying the request. - */ - public static final int STATUS_WAITING_TO_RETRY = 194; - - /** - * This download is waiting for network connectivity to proceed. - */ - public static final int STATUS_WAITING_FOR_NETWORK = 195; - - /** - * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi - * connection to proceed. - */ - public static final int STATUS_QUEUED_FOR_WIFI = 196; - - /** - * This download couldn't be completed due to insufficient storage - * space. Typically, this is because the SD card is full. - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 198; - - /** - * This download couldn't be completed because no external storage - * device was found. Typically, this is because the SD card is not - * mounted. - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 199; - - /** - * This download has successfully completed. - * Warning: there might be other status values that indicate success - * in the future. - * Use isSucccess() to capture the entire category. - */ - public static final int STATUS_SUCCESS = 200; - - /** - * This request couldn't be parsed. This is also used when processing - * requests with unknown/unsupported URI schemes. - */ - public static final int STATUS_BAD_REQUEST = 400; - - /** - * This download can't be performed because the content type cannot be - * handled. - */ - public static final int STATUS_NOT_ACCEPTABLE = 406; - - /** - * This download cannot be performed because the length cannot be - * determined accurately. This is the code for the HTTP error "Length - * Required", which is typically used when making requests that require - * a content length but don't have one, and it is also used in the - * client when a response is received whose length cannot be determined - * accurately (therefore making it impossible to know when a download - * completes). - */ - public static final int STATUS_LENGTH_REQUIRED = 411; - - /** - * This download was interrupted and cannot be resumed. - * This is the code for the HTTP error "Precondition Failed", and it is - * also used in situations where the client doesn't have an ETag at all. - */ - public static final int STATUS_PRECONDITION_FAILED = 412; - - /** - * The lowest-valued error status that is not an actual HTTP status code. - */ - public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. - * Warning: there will be other status values that indicate errors in - * the future. Use isStatusError() to capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. - * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} - * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP - * redirect response that the download manager couldn't - * handle. - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an - * unspecified unhandled HTTP code. - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an - * error receiving or processing data at the HTTP level. - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an - * HttpException while setting up the request. - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were - * too many redirects. - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download has failed because requesting application has been - * blocked by {@link NetworkPolicyManager}. - * - * @hide - * @deprecated since behavior now uses - * {@link #STATUS_WAITING_FOR_NETWORK} - */ - @Deprecated - public static final int STATUS_BLOCKED = 498; - - /** {@hide} */ - public static String statusToString(int status) { - switch (status) { - case STATUS_PENDING: return "PENDING"; - case STATUS_RUNNING: return "RUNNING"; - case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP"; - case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY"; - case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK"; - case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI"; - case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR"; - case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR"; - case STATUS_SUCCESS: return "SUCCESS"; - case STATUS_BAD_REQUEST: return "BAD_REQUEST"; - case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE"; - case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED"; - case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED"; - case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR"; - case STATUS_CANNOT_RESUME: return "CANNOT_RESUME"; - case STATUS_CANCELED: return "CANCELED"; - case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR"; - case STATUS_FILE_ERROR: return "FILE_ERROR"; - case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT"; - case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE"; - case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR"; - case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION"; - case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS"; - case STATUS_BLOCKED: return "BLOCKED"; - default: return Integer.toString(status); - } - } - - /** - * This download is visible but only shows in the notifications - * while it's in progress. - */ - public static final int VISIBILITY_VISIBLE = DownloadManager.Request.VISIBILITY_VISIBLE; - - /** - * This download is visible and shows in the notifications while - * in progress and after completion. - */ - public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = - DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; - - /** - * This download doesn't show in the UI or in the notifications. - */ - public static final int VISIBILITY_HIDDEN = DownloadManager.Request.VISIBILITY_HIDDEN; - - /** - * Constants related to HTTP request headers associated with each download. - */ - public static class RequestHeaders { - public static final String HEADERS_DB_TABLE = "request_headers"; - public static final String COLUMN_DOWNLOAD_ID = "download_id"; - public static final String COLUMN_HEADER = "header"; - public static final String COLUMN_VALUE = "value"; - - /** - * Path segment to add to a download URI to retrieve request headers - */ - public static final String URI_SEGMENT = "headers"; - - /** - * Prefix for ContentValues keys that contain HTTP header lines, to be passed to - * DownloadProvider.insert(). - */ - public static final String INSERT_KEY_PREFIX = "http_header_"; - } - } - - /** - * Query where clause for general querying. - */ - private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " - + Impl.COLUMN_NOTIFICATION_CLASS + "=?"; - - /** - * Delete all the downloads for a package/class pair. - */ - public static final void removeAllDownloadsByPackage( - Context context, String notificationPackage, String notificationClass) { - context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE, - new String[] { notificationPackage, notificationClass }); - } -} diff --git a/src/com/android/messaging/mmslib/InvalidHeaderValueException.java b/src/com/android/messaging/mmslib/InvalidHeaderValueException.java deleted file mode 100644 index c141591..0000000 --- a/src/com/android/messaging/mmslib/InvalidHeaderValueException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib; - -/** - * Thrown when an invalid header value was set. - */ -public class InvalidHeaderValueException extends MmsException { - private static final long serialVersionUID = -2053384496042052262L; - - /** - * Constructs an InvalidHeaderValueException with no detailed message. - */ - public InvalidHeaderValueException() { - super(); - } - - /** - * Constructs an InvalidHeaderValueException with the specified detailed message. - * - * @param message the detailed message. - */ - public InvalidHeaderValueException(String message) { - super(message); - } -} diff --git a/src/com/android/messaging/mmslib/MmsException.java b/src/com/android/messaging/mmslib/MmsException.java deleted file mode 100644 index 173511d..0000000 --- a/src/com/android/messaging/mmslib/MmsException.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib; - -/** - * A generic exception that is thrown by the Mms client. - */ -public class MmsException extends Exception { - private static final long serialVersionUID = -7323249827281485390L; - - /** - * Creates a new MmsException. - */ - public MmsException() { - super(); - } - - /** - * Creates a new MmsException with the specified detail message. - * - * @param message the detail message. - */ - public MmsException(String message) { - super(message); - } - - /** - * Creates a new MmsException with the specified cause. - * - * @param cause the cause. - */ - public MmsException(Throwable cause) { - super(cause); - } - - /** - * Creates a new MmsException with the specified detail message and cause. - * - * @param message the detail message. - * @param cause the cause. - */ - public MmsException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/com/android/messaging/mmslib/SqliteWrapper.java b/src/com/android/messaging/mmslib/SqliteWrapper.java deleted file mode 100644 index 8ef0e6f..0000000 --- a/src/com/android/messaging/mmslib/SqliteWrapper.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.net.Uri; - -import com.android.messaging.util.LogUtil; - -// Wrapper around content resolver methods to catch exceptions -public final class SqliteWrapper { - private static final String TAG = LogUtil.BUGLE_TAG; - - private SqliteWrapper() { - // Forbidden being instantiated. - } - - public static Cursor query(Context context, ContentResolver resolver, Uri uri, - String[] projection, String selection, String[] selectionArgs, String sortOrder) { - try { - return resolver.query(uri, projection, selection, selectionArgs, sortOrder); - } catch (SQLiteException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e); - return null; - } catch (IllegalArgumentException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e); - return null; - } - } - - public static int update(Context context, ContentResolver resolver, Uri uri, - ContentValues values, String where, String[] selectionArgs) { - try { - return resolver.update(uri, values, where, selectionArgs); - } catch (SQLiteException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e); - return -1; - } catch (IllegalArgumentException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e); - return -1; - } - } - - public static int delete(Context context, ContentResolver resolver, Uri uri, - String where, String[] selectionArgs) { - try { - return resolver.delete(uri, where, selectionArgs); - } catch (SQLiteException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e); - return -1; - } catch (IllegalArgumentException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e); - return -1; - } - } - - public static Uri insert(Context context, ContentResolver resolver, - Uri uri, ContentValues values) { - try { - return resolver.insert(uri, values); - } catch (SQLiteException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e); - return null; - } catch (IllegalArgumentException e) { - LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e); - return null; - } - } -} diff --git a/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java b/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java deleted file mode 100644 index 05494c0..0000000 --- a/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * M-Acknowledge.ind PDU. - */ -public class AcknowledgeInd extends GenericPdu { - /** - * Constructor, used when composing a M-Acknowledge.ind pdu. - * - * @param mmsVersion current viersion of mms - * @param transactionId the transaction-id value - * @throws InvalidHeaderValueException if parameters are invalid. - * @throws NullPointerException if transactionId is null. - */ - public AcknowledgeInd(int mmsVersion, byte[] transactionId) - throws InvalidHeaderValueException { - super(); - - setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - AcknowledgeInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Report-Allowed field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getReportAllowed() { - return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Report-Allowed field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReportAllowed(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/Base64.java b/src/com/android/messaging/mmslib/pdu/Base64.java deleted file mode 100644 index 2f27117..0000000 --- a/src/com/android/messaging/mmslib/pdu/Base64.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -public class Base64 { - /** - * Used to get the number of Quadruples. - */ - static final int FOURBYTE = 4; - - /** - * Byte used to pad output. - */ - static final byte PAD = (byte) '='; - - /** - * The base length. - */ - static final int BASELENGTH = 255; - - // Create arrays to hold the base64 characters - private static byte[] base64Alphabet = new byte[BASELENGTH]; - - // Populating the character arrays - static { - for (int i = 0; i < BASELENGTH; i++) { - base64Alphabet[i] = (byte) -1; - } - for (int i = 'Z'; i >= 'A'; i--) { - base64Alphabet[i] = (byte) (i - 'A'); - } - for (int i = 'z'; i >= 'a'; i--) { - base64Alphabet[i] = (byte) (i - 'a' + 26); - } - for (int i = '9'; i >= '0'; i--) { - base64Alphabet[i] = (byte) (i - '0' + 52); - } - - base64Alphabet['+'] = 62; - base64Alphabet['/'] = 63; - } - - /** - * Decodes Base64 data into octects - * - * @param base64Data Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(byte[] base64Data) { - // RFC 2045 requires that we discard ALL non-Base64 characters - base64Data = discardNonBase64(base64Data); - - // handle the edge case, so we don't have to worry about it later - if (base64Data.length == 0) { - return new byte[0]; - } - - int numberQuadruple = base64Data.length / FOURBYTE; - byte decodedData[] = null; - byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; - - // Throw away anything not in base64Data - - int encodedIndex = 0; - int dataIndex = 0; - { - // this sizes the output array properly - rlw - int lastData = base64Data.length; - // ignore the '=' padding - while (base64Data[lastData - 1] == PAD) { - if (--lastData == 0) { - return new byte[0]; - } - } - decodedData = new byte[lastData - numberQuadruple]; - } - - for (int i = 0; i < numberQuadruple; i++) { - dataIndex = i * 4; - marker0 = base64Data[dataIndex + 2]; - marker1 = base64Data[dataIndex + 3]; - - b1 = base64Alphabet[base64Data[dataIndex]]; - b2 = base64Alphabet[base64Data[dataIndex + 1]]; - - if (marker0 != PAD && marker1 != PAD) { - //No PAD e.g 3cQl - b3 = base64Alphabet[marker0]; - b4 = base64Alphabet[marker1]; - - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - decodedData[encodedIndex + 1] = - (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); - decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); - } else if (marker0 == PAD) { - //Two PAD e.g. 3c[Pad][Pad] - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - } else if (marker1 == PAD) { - //One PAD e.g. 3cQ[Pad] - b3 = base64Alphabet[marker0]; - - decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); - decodedData[encodedIndex + 1] = - (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); - } - encodedIndex += 3; - } - return decodedData; - } - - /** - * Check octect wheter it is a base64 encoding. - * - * @param octect to be checked byte - * @return ture if it is base64 encoding, false otherwise. - */ - private static boolean isBase64(byte octect) { - if (octect == PAD) { - return true; - } else if (base64Alphabet[octect] == -1) { - return false; - } else { - return true; - } - } - - /** - * Discards any characters outside of the base64 alphabet, per - * the requirements on page 25 of RFC 2045 - "Any characters - * outside of the base64 alphabet are to be ignored in base64 - * encoded data." - * - * @param data The base-64 encoded data to groom - * @return The data, less non-base64 characters (see RFC 2045). - */ - static byte[] discardNonBase64(byte[] data) { - byte groomedData[] = new byte[data.length]; - int bytesCopied = 0; - - for (int i = 0; i < data.length; i++) { - if (isBase64(data[i])) { - groomedData[bytesCopied++] = data[i]; - } - } - - byte packedData[] = new byte[bytesCopied]; - - System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); - - return packedData; - } -} diff --git a/src/com/android/messaging/mmslib/pdu/CharacterSets.java b/src/com/android/messaging/mmslib/pdu/CharacterSets.java deleted file mode 100644 index 0a9099b..0000000 --- a/src/com/android/messaging/mmslib/pdu/CharacterSets.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.support.v4.util.SimpleArrayMap; -import android.util.SparseArray; - -import java.io.UnsupportedEncodingException; - -public class CharacterSets { - /** - * IANA assigned MIB enum numbers. - * - * From wap-230-wsp-20010705-a.pdf - * Any-charset = - * Equivalent to the special RFC2616 charset value "*" - * - * Link to all the charsets: http://www.iana.org/assignments/character-sets/character-sets.xhtml - * Use Charset.availableCharsets() to see if a potential charset is supported in java: - * private void dumpCharsets() { - * logvv("dumpCharsets"); - * SortedMap charsets = Charset.availableCharsets(); - * for (Entry entry : charsets.entrySet()) { - * String key = entry.getKey(); - * Charset value = entry.getValue(); - * logvv("charset key: " + key + " value: " + value); - * } - * } - * - * As of March 21, 2014, here is the list from dumpCharsets: - * Adobe-Standard-Encoding value: java.nio.charset.CharsetICU[Adobe-Standard-Encoding] - * Big5 value: java.nio.charset.CharsetICU[Big5] - * Big5-HKSCS value: java.nio.charset.CharsetICU[Big5-HKSCS] - * BOCU-1 value: java.nio.charset.CharsetICU[BOCU-1] - * CESU-8 value: java.nio.charset.CharsetICU[CESU-8] - * cp1363 value: java.nio.charset.CharsetICU[cp1363] - * cp851 value: java.nio.charset.CharsetICU[cp851] - * cp864 value: java.nio.charset.CharsetICU[cp864] - * EUC-JP value: java.nio.charset.CharsetICU[EUC-JP] - * EUC-KR value: java.nio.charset.CharsetICU[EUC-KR] - * GB18030 value: java.nio.charset.CharsetICU[GB18030] - * GBK value: java.nio.charset.CharsetICU[GBK] - * hp-roman8 value: java.nio.charset.CharsetICU[hp-roman8] - * HZ-GB-2312 value: java.nio.charset.CharsetICU[HZ-GB-2312] - * IBM-Thai value: java.nio.charset.CharsetICU[IBM-Thai] - * IBM00858 value: java.nio.charset.CharsetICU[IBM00858] - * IBM01140 value: java.nio.charset.CharsetICU[IBM01140] - * IBM01141 value: java.nio.charset.CharsetICU[IBM01141] - * IBM01142 value: java.nio.charset.CharsetICU[IBM01142] - * IBM01143 value: java.nio.charset.CharsetICU[IBM01143] - * IBM01144 value: java.nio.charset.CharsetICU[IBM01144] - * IBM01145 value: java.nio.charset.CharsetICU[IBM01145] - * IBM01146 value: java.nio.charset.CharsetICU[IBM01146] - * IBM01147 value: java.nio.charset.CharsetICU[IBM01147] - * IBM01148 value: java.nio.charset.CharsetICU[IBM01148] - * IBM01149 value: java.nio.charset.CharsetICU[IBM01149] - * IBM037 value: java.nio.charset.CharsetICU[IBM037] - * IBM1026 value: java.nio.charset.CharsetICU[IBM1026] - * IBM1047 value: java.nio.charset.CharsetICU[IBM1047] - * IBM273 value: java.nio.charset.CharsetICU[IBM273] - * IBM277 value: java.nio.charset.CharsetICU[IBM277] - * IBM278 value: java.nio.charset.CharsetICU[IBM278] - * IBM280 value: java.nio.charset.CharsetICU[IBM280] - * IBM284 value: java.nio.charset.CharsetICU[IBM284] - * IBM285 value: java.nio.charset.CharsetICU[IBM285] - * IBM290 value: java.nio.charset.CharsetICU[IBM290] - * IBM297 value: java.nio.charset.CharsetICU[IBM297] - * IBM420 value: java.nio.charset.CharsetICU[IBM420] - * IBM424 value: java.nio.charset.CharsetICU[IBM424] - * IBM437 value: java.nio.charset.CharsetICU[IBM437] - * IBM500 value: java.nio.charset.CharsetICU[IBM500] - * IBM775 value: java.nio.charset.CharsetICU[IBM775] - * IBM850 value: java.nio.charset.CharsetICU[IBM850] - * IBM852 value: java.nio.charset.CharsetICU[IBM852] - * IBM855 value: java.nio.charset.CharsetICU[IBM855] - * IBM857 value: java.nio.charset.CharsetICU[IBM857] - * IBM860 value: java.nio.charset.CharsetICU[IBM860] - * IBM861 value: java.nio.charset.CharsetICU[IBM861] - * IBM862 value: java.nio.charset.CharsetICU[IBM862] - * IBM863 value: java.nio.charset.CharsetICU[IBM863] - * IBM865 value: java.nio.charset.CharsetICU[IBM865] - * IBM866 value: java.nio.charset.CharsetICU[IBM866] - * IBM868 value: java.nio.charset.CharsetICU[IBM868] - * IBM869 value: java.nio.charset.CharsetICU[IBM869] - * IBM870 value: java.nio.charset.CharsetICU[IBM870] - * IBM871 value: java.nio.charset.CharsetICU[IBM871] - * IBM918 value: java.nio.charset.CharsetICU[IBM918] - * ISO-2022-CN value: java.nio.charset.CharsetICU[ISO-2022-CN] - * ISO-2022-CN-EXT value: java.nio.charset.CharsetICU[ISO-2022-CN-EXT] - * ISO-2022-JP value: java.nio.charset.CharsetICU[ISO-2022-JP] - * ISO-2022-JP-1 value: java.nio.charset.CharsetICU[ISO-2022-JP-1] - * ISO-2022-JP-2 value: java.nio.charset.CharsetICU[ISO-2022-JP-2] - * ISO-2022-KR value: java.nio.charset.CharsetICU[ISO-2022-KR] - * ISO-8859-1 value: java.nio.charset.CharsetICU[ISO-8859-1] - * ISO-8859-10 value: java.nio.charset.CharsetICU[ISO-8859-10] - * ISO-8859-13 value: java.nio.charset.CharsetICU[ISO-8859-13] - * ISO-8859-14 value: java.nio.charset.CharsetICU[ISO-8859-14] - * ISO-8859-15 value: java.nio.charset.CharsetICU[ISO-8859-15] - * ISO-8859-2 value: java.nio.charset.CharsetICU[ISO-8859-2] - * ISO-8859-3 value: java.nio.charset.CharsetICU[ISO-8859-3] - * ISO-8859-4 value: java.nio.charset.CharsetICU[ISO-8859-4] - * ISO-8859-5 value: java.nio.charset.CharsetICU[ISO-8859-5] - * ISO-8859-6 value: java.nio.charset.CharsetICU[ISO-8859-6] - * ISO-8859-7 value: java.nio.charset.CharsetICU[ISO-8859-7] - * ISO-8859-8 value: java.nio.charset.CharsetICU[ISO-8859-8] - * ISO-8859-9 value: java.nio.charset.CharsetICU[ISO-8859-9] - * KOI8-R value: java.nio.charset.CharsetICU[KOI8-R] - * KOI8-U value: java.nio.charset.CharsetICU[KOI8-U] - * macintosh value: java.nio.charset.CharsetICU[macintosh] - * SCSU value: java.nio.charset.CharsetICU[SCSU] - * Shift_JIS value: java.nio.charset.CharsetICU[Shift_JIS] - * TIS-620 value: java.nio.charset.CharsetICU[TIS-620] - * US-ASCII value: java.nio.charset.CharsetICU[US-ASCII] - * UTF-16 value: java.nio.charset.CharsetICU[UTF-16] - * UTF-16BE value: java.nio.charset.CharsetICU[UTF-16BE] - * UTF-16LE value: java.nio.charset.CharsetICU[UTF-16LE] - * UTF-32 value: java.nio.charset.CharsetICU[UTF-32] - * UTF-32BE value: java.nio.charset.CharsetICU[UTF-32BE] - * UTF-32LE value: java.nio.charset.CharsetICU[UTF-32LE] - * UTF-7 value: java.nio.charset.CharsetICU[UTF-7] - * UTF-8 value: java.nio.charset.CharsetICU[UTF-8] - * windows-1250 value: java.nio.charset.CharsetICU[windows-1250] - * windows-1251 value: java.nio.charset.CharsetICU[windows-1251] - * windows-1252 value: java.nio.charset.CharsetICU[windows-1252] - * windows-1253 value: java.nio.charset.CharsetICU[windows-1253] - * windows-1254 value: java.nio.charset.CharsetICU[windows-1254] - * windows-1255 value: java.nio.charset.CharsetICU[windows-1255] - * windows-1256 value: java.nio.charset.CharsetICU[windows-1256] - * windows-1257 value: java.nio.charset.CharsetICU[windows-1257] - * windows-1258 value: java.nio.charset.CharsetICU[windows-1258] - * x-compound-text value: java.nio.charset.CharsetICU[x-compound-text] - * x-ebcdic-xml-us value: java.nio.charset.CharsetICU[x-ebcdic-xml-us] - * x-gsm-03.38-2000 value: java.nio.charset.CharsetICU[x-gsm-03.38-2000] - * x-ibm-1047-s390 value: java.nio.charset.CharsetICU[x-ibm-1047-s390] - * x-ibm-1125_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1125_P100-1997] - * x-ibm-1129_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1129_P100-1997] - * x-ibm-1130_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1130_P100-1997] - * x-ibm-1131_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1131_P100-1997] - * x-ibm-1132_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-1132_P100-1998] - * x-ibm-1133_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1133_P100-1997] - * x-ibm-1137_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1137_P100-1999] - * x-ibm-1140-s390 value: java.nio.charset.CharsetICU[x-ibm-1140-s390] - * x-ibm-1141-s390 value: java.nio.charset.CharsetICU[x-ibm-1141-s390] - * x-ibm-1142-s390 value: java.nio.charset.CharsetICU[x-ibm-1142-s390] - * x-ibm-1143-s390 value: java.nio.charset.CharsetICU[x-ibm-1143-s390] - * x-ibm-1144-s390 value: java.nio.charset.CharsetICU[x-ibm-1144-s390] - * x-ibm-1145-s390 value: java.nio.charset.CharsetICU[x-ibm-1145-s390] - * x-ibm-1146-s390 value: java.nio.charset.CharsetICU[x-ibm-1146-s390] - * x-ibm-1147-s390 value: java.nio.charset.CharsetICU[x-ibm-1147-s390] - * x-ibm-1148-s390 value: java.nio.charset.CharsetICU[x-ibm-1148-s390] - * x-ibm-1149-s390 value: java.nio.charset.CharsetICU[x-ibm-1149-s390] - * x-ibm-1153-s390 value: java.nio.charset.CharsetICU[x-ibm-1153-s390] - * x-ibm-1154_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1154_P100-1999] - * x-ibm-1155_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1155_P100-1999] - * x-ibm-1156_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1156_P100-1999] - * x-ibm-1157_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1157_P100-1999] - * x-ibm-1158_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1158_P100-1999] - * x-ibm-1160_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1160_P100-1999] - * x-ibm-1162_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1162_P100-1999] - * x-ibm-1164_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1164_P100-1999] - * x-ibm-1250_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1250_P100-1995] - * x-ibm-1251_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1251_P100-1995] - * x-ibm-1252_P100-2000 value: java.nio.charset.CharsetICU[x-ibm-1252_P100-2000] - * x-ibm-1253_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1253_P100-1995] - * x-ibm-1254_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1254_P100-1995] - * x-ibm-1255_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1255_P100-1995] - * x-ibm-1256_P110-1997 value: java.nio.charset.CharsetICU[x-ibm-1256_P110-1997] - * x-ibm-1257_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1257_P100-1995] - * x-ibm-1258_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1258_P100-1997] - * x-ibm-12712-s390 value: java.nio.charset.CharsetICU[x-ibm-12712-s390] - * x-ibm-12712_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-12712_P100-1998] - * x-ibm-1373_P100-2002 value: java.nio.charset.CharsetICU[x-ibm-1373_P100-2002] - * x-ibm-1383_P110-1999 value: java.nio.charset.CharsetICU[x-ibm-1383_P110-1999] - * x-ibm-1386_P100-2001 value: java.nio.charset.CharsetICU[x-ibm-1386_P100-2001] - * x-ibm-16684_P110-2003 value: java.nio.charset.CharsetICU[x-ibm-16684_P110-2003] - * x-ibm-16804-s390 value: java.nio.charset.CharsetICU[x-ibm-16804-s390] - * x-ibm-16804_X110-1999 value: java.nio.charset.CharsetICU[x-ibm-16804_X110-1999] - * x-ibm-25546 value: java.nio.charset.CharsetICU[x-ibm-25546] - * x-ibm-33722_P12A_P12A-2009_U2 value: - * java.nio.charset.CharsetICU[x-ibm-33722_P12A_P12A-2009_U2] - * x-ibm-37-s390 value: java.nio.charset.CharsetICU[x-ibm-37-s390] - * x-ibm-4517_P100-2005 value: java.nio.charset.CharsetICU[x-ibm-4517_P100-2005] - * x-ibm-4899_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-4899_P100-1998] - * x-ibm-4909_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4909_P100-1999] - * x-ibm-4971_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4971_P100-1999] - * x-ibm-5123_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-5123_P100-1999] - * x-ibm-5351_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5351_P100-1998] - * x-ibm-5352_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5352_P100-1998] - * x-ibm-5353_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5353_P100-1998] - * x-ibm-5478_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-5478_P100-1995] - * x-ibm-803_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-803_P100-1999] - * x-ibm-813_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-813_P100-1995] - * x-ibm-8482_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-8482_P100-1999] - * x-ibm-901_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-901_P100-1999] - * x-ibm-902_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-902_P100-1999] - * x-ibm-9067_X100-2005 value: java.nio.charset.CharsetICU[x-ibm-9067_X100-2005] - * x-ibm-916_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-916_P100-1995] - * x-IBM1006 value: java.nio.charset.CharsetICU[x-IBM1006] - * x-IBM1025 value: java.nio.charset.CharsetICU[x-IBM1025] - * x-IBM1097 value: java.nio.charset.CharsetICU[x-IBM1097] - * x-IBM1098 value: java.nio.charset.CharsetICU[x-IBM1098] - * x-IBM1112 value: java.nio.charset.CharsetICU[x-IBM1112] - * x-IBM1122 value: java.nio.charset.CharsetICU[x-IBM1122] - * x-IBM1123 value: java.nio.charset.CharsetICU[x-IBM1123] - * x-IBM1124 value: java.nio.charset.CharsetICU[x-IBM1124] - * x-IBM1153 value: java.nio.charset.CharsetICU[x-IBM1153] - * x-IBM1363 value: java.nio.charset.CharsetICU[x-IBM1363] - * x-IBM1364 value: java.nio.charset.CharsetICU[x-IBM1364] - * x-IBM1371 value: java.nio.charset.CharsetICU[x-IBM1371] - * x-IBM1388 value: java.nio.charset.CharsetICU[x-IBM1388] - * x-IBM1390 value: java.nio.charset.CharsetICU[x-IBM1390] - * x-IBM1399 value: java.nio.charset.CharsetICU[x-IBM1399] - * x-IBM33722 value: java.nio.charset.CharsetICU[x-IBM33722] - * x-IBM720 value: java.nio.charset.CharsetICU[x-IBM720] - * x-IBM737 value: java.nio.charset.CharsetICU[x-IBM737] - * x-IBM856 value: java.nio.charset.CharsetICU[x-IBM856] - * x-IBM867 value: java.nio.charset.CharsetICU[x-IBM867] - * x-IBM875 value: java.nio.charset.CharsetICU[x-IBM875] - * x-IBM922 value: java.nio.charset.CharsetICU[x-IBM922] - * x-IBM930 value: java.nio.charset.CharsetICU[x-IBM930] - * x-IBM933 value: java.nio.charset.CharsetICU[x-IBM933] - * x-IBM935 value: java.nio.charset.CharsetICU[x-IBM935] - * x-IBM937 value: java.nio.charset.CharsetICU[x-IBM937] - * x-IBM939 value: java.nio.charset.CharsetICU[x-IBM939] - * x-IBM942 value: java.nio.charset.CharsetICU[x-IBM942] - * x-IBM943 value: java.nio.charset.CharsetICU[x-IBM943] - * x-IBM949 value: java.nio.charset.CharsetICU[x-IBM949] - * x-IBM949C value: java.nio.charset.CharsetICU[x-IBM949C] - * x-IBM950 value: java.nio.charset.CharsetICU[x-IBM950] - * x-IBM954 value: java.nio.charset.CharsetICU[x-IBM954] - * x-IBM964 value: java.nio.charset.CharsetICU[x-IBM964] - * x-IBM970 value: java.nio.charset.CharsetICU[x-IBM970] - * x-IBM971 value: java.nio.charset.CharsetICU[x-IBM971] - * x-IMAP-mailbox-name value: java.nio.charset.CharsetICU[x-IMAP-mailbox-name] - * x-iscii-be value: java.nio.charset.CharsetICU[x-iscii-be] - * x-iscii-gu value: java.nio.charset.CharsetICU[x-iscii-gu] - * x-iscii-ka value: java.nio.charset.CharsetICU[x-iscii-ka] - * x-iscii-ma value: java.nio.charset.CharsetICU[x-iscii-ma] - * x-iscii-or value: java.nio.charset.CharsetICU[x-iscii-or] - * x-iscii-pa value: java.nio.charset.CharsetICU[x-iscii-pa] - * x-iscii-ta value: java.nio.charset.CharsetICU[x-iscii-ta] - * x-iscii-te value: java.nio.charset.CharsetICU[x-iscii-te] - * x-ISCII91 value: java.nio.charset.CharsetICU[x-ISCII91] - * x-ISO-2022-CN-CNS value: java.nio.charset.CharsetICU[x-ISO-2022-CN-CNS] - * x-iso-8859-11 value: java.nio.charset.CharsetICU[x-iso-8859-11] - * x-JavaUnicode value: java.nio.charset.CharsetICU[x-JavaUnicode] - * x-JavaUnicode2 value: java.nio.charset.CharsetICU[x-JavaUnicode2] - * x-JIS7 value: java.nio.charset.CharsetICU[x-JIS7] - * x-JIS8 value: java.nio.charset.CharsetICU[x-JIS8] - * x-LMBCS-1 value: java.nio.charset.CharsetICU[x-LMBCS-1] - * x-mac-centraleurroman value: java.nio.charset.CharsetICU[x-mac-centraleurroman] - * x-mac-cyrillic value: java.nio.charset.CharsetICU[x-mac-cyrillic] - * x-mac-greek value: java.nio.charset.CharsetICU[x-mac-greek] - * x-mac-turkish value: java.nio.charset.CharsetICU[x-mac-turkish] - * x-MS950-HKSCS value: java.nio.charset.CharsetICU[x-MS950-HKSCS] - * x-UnicodeBig value: java.nio.charset.CharsetICU[x-UnicodeBig] - * x-UTF-16LE-BOM value: java.nio.charset.CharsetICU[x-UTF-16LE-BOM] - * x-UTF16_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF16_OppositeEndian] - * x-UTF16_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF16_PlatformEndian] - * x-UTF32_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF32_OppositeEndian] - * x-UTF32_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF32_PlatformEndian] - * - */ - public static final int ANY_CHARSET = 0x00; - public static final int US_ASCII = 0x03; - public static final int ISO_8859_1 = 0x04; - public static final int ISO_8859_2 = 0x05; - public static final int ISO_8859_3 = 0x06; - public static final int ISO_8859_4 = 0x07; - public static final int ISO_8859_5 = 0x08; - public static final int ISO_8859_6 = 0x09; - public static final int ISO_8859_7 = 0x0A; - public static final int ISO_8859_8 = 0x0B; - public static final int ISO_8859_9 = 0x0C; - public static final int SHIFT_JIS = 0x11; - public static final int EUC_JP = 0x12; - public static final int EUC_KR = 0x26; - public static final int ISO_2022_JP = 0x27; - public static final int ISO_2022_JP_2 = 0x28; - public static final int UTF_8 = 0x6A; - public static final int GBK = 0x71; - public static final int GB18030 = 0x72; - public static final int GB2312 = 0x07E9; - public static final int BIG5 = 0x07EA; - public static final int UCS2 = 0x03E8; - public static final int UTF_16 = 0x03F7; - public static final int HZ_GB_2312 = 0x0825; - - /** - * If the encoding of given data is unsupported, use UTF_8 to decode it. - */ - public static final int DEFAULT_CHARSET = UTF_8; - - /** - * Array of MIB enum numbers. - */ - private static final int[] MIBENUM_NUMBERS = { - ANY_CHARSET, - US_ASCII, - ISO_8859_1, - ISO_8859_2, - ISO_8859_3, - ISO_8859_4, - ISO_8859_5, - ISO_8859_6, - ISO_8859_7, - ISO_8859_8, - ISO_8859_9, - SHIFT_JIS, - EUC_JP, - EUC_KR, - ISO_2022_JP, - ISO_2022_JP_2, - UTF_8, - GBK, - GB18030, - GB2312, - BIG5, - UCS2, - UTF_16, - HZ_GB_2312, - }; - - /** - * The Well-known-charset Mime name. - */ - public static final String MIMENAME_ANY_CHARSET = "*"; - public static final String MIMENAME_US_ASCII = "us-ascii"; - public static final String MIMENAME_ISO_8859_1 = "iso-8859-1"; - public static final String MIMENAME_ISO_8859_2 = "iso-8859-2"; - public static final String MIMENAME_ISO_8859_3 = "iso-8859-3"; - public static final String MIMENAME_ISO_8859_4 = "iso-8859-4"; - public static final String MIMENAME_ISO_8859_5 = "iso-8859-5"; - public static final String MIMENAME_ISO_8859_6 = "iso-8859-6"; - public static final String MIMENAME_ISO_8859_7 = "iso-8859-7"; - public static final String MIMENAME_ISO_8859_8 = "iso-8859-8"; - public static final String MIMENAME_ISO_8859_9 = "iso-8859-9"; - public static final String MIMENAME_SHIFT_JIS = "shift_JIS"; - public static final String MIMENAME_EUC_JP = "euc-jp"; - public static final String MIMENAME_EUC_KR = "euc-kr"; - public static final String MIMENAME_ISO_2022_JP = "iso-2022-jp"; - public static final String MIMENAME_ISO_2022_JP_2 = "iso-2022-jp-2"; - public static final String MIMENAME_UTF_8 = "utf-8"; - public static final String MIMENAME_GBK = "gbk"; - public static final String MIMENAME_GB18030 = "gb18030"; - public static final String MIMENAME_GB2312 = "gb2312"; - public static final String MIMENAME_BIG5 = "big5"; - public static final String MIMENAME_UCS2 = "iso-10646-ucs-2"; - public static final String MIMENAME_UTF_16 = "utf-16"; - public static final String MIMENAME_HZ_GB_2312 = "hz-gb-2312"; - - public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8; - - /** - * Array of the names of character sets. - */ - private static final String[] MIME_NAMES = { - MIMENAME_ANY_CHARSET, - MIMENAME_US_ASCII, - MIMENAME_ISO_8859_1, - MIMENAME_ISO_8859_2, - MIMENAME_ISO_8859_3, - MIMENAME_ISO_8859_4, - MIMENAME_ISO_8859_5, - MIMENAME_ISO_8859_6, - MIMENAME_ISO_8859_7, - MIMENAME_ISO_8859_8, - MIMENAME_ISO_8859_9, - MIMENAME_SHIFT_JIS, - MIMENAME_EUC_JP, - MIMENAME_EUC_KR, - MIMENAME_ISO_2022_JP, - MIMENAME_ISO_2022_JP_2, - MIMENAME_UTF_8, - MIMENAME_GBK, - MIMENAME_GB18030, - MIMENAME_GB2312, - MIMENAME_BIG5, - MIMENAME_UCS2, - MIMENAME_UTF_16, - MIMENAME_HZ_GB_2312, - }; - - private static final SparseArray MIBENUM_TO_NAME_MAP; - - private static final SimpleArrayMap NAME_TO_MIBENUM_MAP; - - static { - // Create the HashMaps. - MIBENUM_TO_NAME_MAP = new SparseArray(); - NAME_TO_MIBENUM_MAP = new SimpleArrayMap(); - assert (MIBENUM_NUMBERS.length == MIME_NAMES.length); - final int count = MIBENUM_NUMBERS.length - 1; - for (int i = 0; i <= count; i++) { - MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]); - NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]); - } - } - - private CharacterSets() { - } // Non-instantiatable - - /** - * Map an MIBEnum number to the name of the charset which this number - * is assigned to by IANA. - * - * @param mibEnumValue An IANA assigned MIBEnum number. - * @return The name string of the charset. - */ - public static String getMimeName(final int mibEnumValue) - throws UnsupportedEncodingException { - final String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue); - if (name == null) { - throw new UnsupportedEncodingException(); - } - return name; - } - - /** - * Map a well-known charset name to its assigned MIBEnum number. - * - * @param mimeName The charset name. - * @return The MIBEnum number assigned by IANA for this charset. - */ - public static int getMibEnumValue(final String mimeName) - throws UnsupportedEncodingException { - if (null == mimeName) { - return -1; - } - - final Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName); - if (mibEnumValue == null) { - throw new UnsupportedEncodingException(); - } - return mibEnumValue; - } -} diff --git a/src/com/android/messaging/mmslib/pdu/DeliveryInd.java b/src/com/android/messaging/mmslib/pdu/DeliveryInd.java deleted file mode 100644 index 5e1d873..0000000 --- a/src/com/android/messaging/mmslib/pdu/DeliveryInd.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * M-Delivery.Ind Pdu. - */ -public class DeliveryInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public DeliveryInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - DeliveryInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value, should not be null - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get Status value. - * - * @return the value - */ - public int getStatus() { - return mPduHeaders.getOctet(PduHeaders.STATUS); - } - - /** - * Set Status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.STATUS); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public EncodedStringValue getStatusText() {return null;} - * public void setStatusText(EncodedStringValue value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java b/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java deleted file mode 100644 index 1d3c9ea..0000000 --- a/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - -/** - * Encoded-string-value = Text-string | Value-length Char-set Text-string - */ -public class EncodedStringValue implements Cloneable { - private static final String TAG = "EncodedStringValue"; - private static final boolean DEBUG = false; - private static final boolean LOCAL_LOGV = false; - - /** - * The Char-set value. - */ - private int mCharacterSet; - - /** - * The Text-string value. - */ - private byte[] mData; - - /** - * Constructor. - * - * @param charset the Char-set value - * @param data the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public EncodedStringValue(int charset, byte[] data) { - // TODO: CharSet needs to be validated against MIBEnum. - if (null == data) { - throw new NullPointerException("EncodedStringValue: Text-string is null."); - } - - mCharacterSet = charset; - mData = new byte[data.length]; - System.arraycopy(data, 0, mData, 0, data.length); - } - - /** - * Constructor. - * - * @param data the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public EncodedStringValue(byte[] data) { - this(CharacterSets.DEFAULT_CHARSET, data); - } - - public EncodedStringValue(String data) { - this(CharacterSets.DEFAULT_CHARSET, data); - } - - /** - * Constructor - * - * @param charset - * @param data The text in Java String - * @throws NullPointerException if Text-string value is null. - */ - public EncodedStringValue(int charset, String data) { - if (null == data) { - throw new NullPointerException("EncodedStringValue: Text-string is null"); - } - mCharacterSet = charset; - try { - mData = data.getBytes(CharacterSets.getMimeName(charset)); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Input encoding " + charset + " must be supported.", e); - mData = data.getBytes(); - } - } - - /** - * Get Char-set value. - * - * @return the value - */ - public int getCharacterSet() { - return mCharacterSet; - } - - /** - * Set Char-set value. - * - * @param charset the Char-set value - */ - public void setCharacterSet(int charset) { - // TODO: CharSet needs to be validated against MIBEnum. - mCharacterSet = charset; - } - - /** - * Get Text-string value. - * - * @return the value - */ - public byte[] getTextString() { - byte[] byteArray = new byte[mData.length]; - - System.arraycopy(mData, 0, byteArray, 0, mData.length); - return byteArray; - } - - /** - * Set Text-string value. - * - * @param textString the Text-string value - * @throws NullPointerException if Text-string value is null. - */ - public void setTextString(byte[] textString) { - if (null == textString) { - throw new NullPointerException("EncodedStringValue: Text-string is null."); - } - - mData = new byte[textString.length]; - System.arraycopy(textString, 0, mData, 0, textString.length); - } - - /** - * Convert this object to a {@link java.lang.String}. If the encoding of - * the EncodedStringValue is null or unsupported, it will be - * treated as iso-8859-1 encoding. - * - * @return The decoded String. - */ - public String getString() { - if (CharacterSets.ANY_CHARSET == mCharacterSet) { - return new String(mData); // system default encoding. - } else { - try { - String name = CharacterSets.getMimeName(mCharacterSet); - return new String(mData, name); - } catch (UnsupportedEncodingException e) { - if (LOCAL_LOGV) { - Log.v(TAG, e.getMessage(), e); - } - try { - return new String(mData, CharacterSets.MIMENAME_ISO_8859_1); - } catch (UnsupportedEncodingException exception) { - return new String(mData); // system default encoding. - } - } - } - } - - /** - * Append to Text-string. - * - * @param textString the textString to append - * @throws NullPointerException if the text String is null - * or an IOException occured. - */ - public void appendTextString(byte[] textString) { - if (null == textString) { - throw new NullPointerException("Text-string is null."); - } - - if (null == mData) { - mData = new byte[textString.length]; - System.arraycopy(textString, 0, mData, 0, textString.length); - } else { - ByteArrayOutputStream newTextString = new ByteArrayOutputStream(); - try { - newTextString.write(mData); - newTextString.write(textString); - } catch (IOException e) { - e.printStackTrace(); - throw new NullPointerException( - "appendTextString: failed when write a new Text-string"); - } - - mData = newTextString.toByteArray(); - } - } - - /* - * (non-Javadoc) - * @see java.lang.Object#clone() - */ - @Override - public Object clone() throws CloneNotSupportedException { - super.clone(); - int len = mData.length; - byte[] dstBytes = new byte[len]; - System.arraycopy(mData, 0, dstBytes, 0, len); - - try { - return new EncodedStringValue(mCharacterSet, dstBytes); - } catch (Exception e) { - Log.e(TAG, "failed to clone an EncodedStringValue: " + this); - e.printStackTrace(); - throw new CloneNotSupportedException(e.getMessage()); - } - } - - /** - * Split this encoded string around matches of the given pattern. - * - * @param pattern the delimiting pattern - * @return the array of encoded strings computed by splitting this encoded - * string around matches of the given pattern - */ - public EncodedStringValue[] split(String pattern) { - String[] temp = getString().split(pattern); - EncodedStringValue[] ret = new EncodedStringValue[temp.length]; - for (int i = 0; i < ret.length; ++i) { - try { - ret[i] = new EncodedStringValue(mCharacterSet, - temp[i].getBytes()); - } catch (NullPointerException exception) { - // Can't arrive here - return null; - } - } - return ret; - } - - /** - * Extract an EncodedStringValue[] from a given String. - */ - public static EncodedStringValue[] extract(String src) { - String[] values = src.split(";"); - - ArrayList list = new ArrayList(); - for (int i = 0; i < values.length; i++) { - if (values[i].length() > 0) { - list.add(new EncodedStringValue(values[i])); - } - } - - int len = list.size(); - if (len > 0) { - return list.toArray(new EncodedStringValue[len]); - } else { - return null; - } - } - - /** - * Concatenate an EncodedStringValue[] into a single String. - */ - public static String concat(EncodedStringValue[] addr) { - StringBuilder sb = new StringBuilder(); - int maxIndex = addr.length - 1; - for (int i = 0; i <= maxIndex; i++) { - sb.append(addr[i].getString()); - if (i < maxIndex) { - sb.append(";"); - } - } - - return sb.toString(); - } - - public static EncodedStringValue copy(EncodedStringValue value) { - if (value == null) { - return null; - } - - return new EncodedStringValue(value.mCharacterSet, value.mData); - } - - public static EncodedStringValue[] encodeStrings(String[] array) { - int count = array.length; - if (count > 0) { - EncodedStringValue[] encodedArray = new EncodedStringValue[count]; - for (int i = 0; i < count; i++) { - encodedArray[i] = new EncodedStringValue(array[i]); - } - return encodedArray; - } - return null; - } -} diff --git a/src/com/android/messaging/mmslib/pdu/GenericPdu.java b/src/com/android/messaging/mmslib/pdu/GenericPdu.java deleted file mode 100644 index 178dc5a..0000000 --- a/src/com/android/messaging/mmslib/pdu/GenericPdu.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -public class GenericPdu { - /** - * The headers of pdu. - */ - PduHeaders mPduHeaders = null; - - /** - * Constructor. - */ - public GenericPdu() { - mPduHeaders = new PduHeaders(); - } - - /** - * Constructor. - * - * @param headers Headers for this PDU. - */ - GenericPdu(PduHeaders headers) { - mPduHeaders = headers; - } - - /** - * Get the headers of this PDU. - * - * @return A PduHeaders of this PDU. - */ - PduHeaders getPduHeaders() { - return mPduHeaders; - } - - /** - * Get X-Mms-Message-Type field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getMessageType() { - return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE); - } - - /** - * Set X-Mms-Message-Type field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if field's value is not Octet. - */ - public void setMessageType(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE); - } - - /** - * Get X-Mms-MMS-Version field value. - * - * @return the X-Mms-MMS-Version value - */ - public int getMmsVersion() { - return mPduHeaders.getOctet(PduHeaders.MMS_VERSION); - } - - /** - * Set X-Mms-MMS-Version field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if field's value is not Octet. - */ - public void setMmsVersion(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java b/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java deleted file mode 100644 index cb8c8c7..0000000 --- a/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * Multimedia message PDU. - */ -public class MultimediaMessagePdu extends GenericPdu { - /** - * The body. - */ - private PduBody mMessageBody; - - /** - * Constructor. - */ - public MultimediaMessagePdu() { - super(); - } - - /** - * Constructor. - * - * @param header the header of this PDU - * @param body the body of this PDU - */ - public MultimediaMessagePdu(PduHeaders header, PduBody body) { - super(header); - mMessageBody = body; - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - MultimediaMessagePdu(PduHeaders headers) { - super(headers); - } - - /** - * Get body of the PDU. - * - * @return the body - */ - public PduBody getBody() { - return mMessageBody; - } - - /** - * Set body of the PDU. - * - * @param body the body - */ - public void setBody(PduBody body) { - mMessageBody = body; - } - - /** - * Get subject. - * - * @return the value - */ - public EncodedStringValue getSubject() { - return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); - } - - /** - * Set subject. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setSubject(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Add a "To" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addTo(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO); - } - - /** - * Get X-Mms-Priority value. - * - * @return the value - */ - public int getPriority() { - return mPduHeaders.getOctet(PduHeaders.PRIORITY); - } - - /** - * Set X-Mms-Priority value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setPriority(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.PRIORITY); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value in seconds. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get the message size - * - * @return the size of the message - */ - public long getMessageSize() { - return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/NotificationInd.java b/src/com/android/messaging/mmslib/pdu/NotificationInd.java deleted file mode 100644 index 4cbbd30..0000000 --- a/src/com/android/messaging/mmslib/pdu/NotificationInd.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * M-Notification.ind PDU. - */ -public class NotificationInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - * @throws RuntimeException if an undeclared error occurs. - */ - public NotificationInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - NotificationInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Content-Class Value. - * - * @return the value - */ - public int getContentClass() { - return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS); - } - - /** - * Set X-Mms-Content-Class Value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setContentClass(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS); - } - - /** - * Get X-Mms-Content-Location value. - * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: - * Content-location-value = Uri-value - * - * @return the value - */ - public byte[] getContentLocation() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION); - } - - /** - * Set X-Mms-Content-Location value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setContentLocation(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION); - } - - /** - * Get X-Mms-Expiry value. - * - * Expiry-value = Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) - * - * @return the value - */ - public long getExpiry() { - return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); - } - - /** - * Set X-Mms-Expiry value. - * - * @param value the value - * @throws RuntimeException if an undeclared error occurs. - */ - public void setExpiry(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get X-Mms-Message-Size value. - * Message-size-value = Long-integer - * - * @return the value - */ - public long getMessageSize() { - return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); - } - - /** - * Set X-Mms-Message-Size value. - * - * @param value the value - * @throws RuntimeException if an undeclared error occurs. - */ - public void setMessageSize(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); - } - - /** - * Get subject. - * - * @return the value - */ - public EncodedStringValue getSubject() { - return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); - } - - /** - * Set subject. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setSubject(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); - } - - /** - * Get X-Mms-Transaction-Id. - * - * @return the value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /** - * Get X-Mms-Delivery-Report Value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report Value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public byte getDistributionIndicator() {return 0x00;} - * public void setDistributionIndicator(byte value) {} - * - * public ElementDescriptorValue getElementDescriptor() {return null;} - * public void getElementDescriptor(ElementDescriptorValue value) {} - * - * public byte getPriority() {return 0x00;} - * public void setPriority(byte value) {} - * - * public byte getRecommendedRetrievalMode() {return 0x00;} - * public void setRecommendedRetrievalMode(byte value) {} - * - * public byte getRecommendedRetrievalModeText() {return 0x00;} - * public void setRecommendedRetrievalModeText(byte value) {} - * - * public byte[] getReplaceId() {return 0x00;} - * public void setReplaceId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - * - * public byte getStored() {return 0x00;} - * public void setStored(byte value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java b/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java deleted file mode 100644 index 7dabd89..0000000 --- a/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * M-NofifyResp.ind PDU. - */ -public class NotifyRespInd extends GenericPdu { - /** - * Constructor, used when composing a M-NotifyResp.ind pdu. - * - * @param mmsVersion current version of mms - * @param transactionId the transaction-id value - * @param status the status value - * @throws InvalidHeaderValueException if parameters are invalid. - * @throws NullPointerException if transactionId is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public NotifyRespInd(int mmsVersion, - byte[] transactionId, - int status) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - setStatus(status); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - NotifyRespInd(PduHeaders headers) { - super(headers); - } - - /** - * Get X-Mms-Report-Allowed field value. - * - * @return the X-Mms-Report-Allowed value - */ - public int getReportAllowed() { - return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Report-Allowed field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setReportAllowed(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); - } - - /** - * Set X-Mms-Status field value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.STATUS); - } - - /** - * GetX-Mms-Status field value. - * - * @return the X-Mms-Status value - */ - public int getStatus() { - return mPduHeaders.getOctet(PduHeaders.STATUS); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - * @throws RuntimeException if an undeclared error occurs. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/PduBody.java b/src/com/android/messaging/mmslib/pdu/PduBody.java deleted file mode 100644 index 8cbb2e6..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduBody.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import java.util.Vector; - -public class PduBody { - private Vector mParts = null; - - /** - * Constructor. - */ - public PduBody() { - mParts = new Vector(); - } - - /** - * Appends the specified part to the end of this body. - * - * @param part part to be appended - * @return true when success, false when fail - * @throws NullPointerException when part is null - */ - public boolean addPart(PduPart part) { - if (null == part) { - throw new NullPointerException(); - } - - return mParts.add(part); - } - - /** - * Inserts the specified part at the specified position. - * - * @param index index at which the specified part is to be inserted - * @param part part to be inserted - * @throws NullPointerException when part is null - */ - public void addPart(int index, PduPart part) { - if (null == part) { - throw new NullPointerException(); - } - - mParts.add(index, part); - } - - /** - * Remove all of the parts. - */ - public void removeAll() { - mParts.clear(); - } - - /** - * Get the part at the specified position. - * - * @param index index of the part to return - * @return part at the specified index - */ - public PduPart getPart(int index) { - return mParts.get(index); - } - - /** - * Get the number of parts. - * - * @return the number of parts - */ - public int getPartsNum() { - return mParts.size(); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/PduComposer.java b/src/com/android/messaging/mmslib/pdu/PduComposer.java deleted file mode 100644 index d05a198..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduComposer.java +++ /dev/null @@ -1,1260 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.content.ContentResolver; -import android.content.Context; -import android.support.v4.util.SimpleArrayMap; -import android.text.TextUtils; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; - -public class PduComposer { - /** - * Address type. - */ - private static final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1; - private static final int PDU_EMAIL_ADDRESS_TYPE = 2; - private static final int PDU_IPV4_ADDRESS_TYPE = 3; - private static final int PDU_IPV6_ADDRESS_TYPE = 4; - private static final int PDU_UNKNOWN_ADDRESS_TYPE = 5; - - /** - * Address regular expression string. - */ - static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+"; - - static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" + - "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}"; - - static final String REGEXP_IPV6_ADDRESS_TYPE = - "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + - "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + - "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}"; - - static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" + - "[0-9]{1,3}\\.{1}[0-9]{1,3}"; - - /** - * The postfix strings of address. - */ - static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN"; - static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4"; - static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6"; - - /** - * Error values. - */ - private static final int PDU_COMPOSE_SUCCESS = 0; - private static final int PDU_COMPOSE_CONTENT_ERROR = 1; - private static final int PDU_COMPOSE_FIELD_NOT_SET = 2; - private static final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3; - - /** - * WAP values defined in WSP spec. - */ - private static final int QUOTED_STRING_FLAG = 34; - private static final int END_STRING_FLAG = 0; - private static final int LENGTH_QUOTE = 31; - private static final int TEXT_MAX = 127; - private static final int SHORT_INTEGER_MAX = 127; - private static final int LONG_INTEGER_LENGTH_MAX = 8; - - /** - * Block size when read data from InputStream. - */ - private static final int PDU_COMPOSER_BLOCK_SIZE = 1024; - - /** - * The output message. - */ - protected ByteArrayOutputStream mMessage = null; - - /** - * The PDU. - */ - private GenericPdu mPdu = null; - - /** - * Current visiting position of the mMessage. - */ - protected int mPosition = 0; - - /** - * Message compose buffer stack. - */ - private BufferStack mStack = null; - - /** - * Content resolver. - */ - private final ContentResolver mResolver; - - /** - * Header of this pdu. - */ - private PduHeaders mPduHeader = null; - - /** - * Map of all content type - */ - private static SimpleArrayMap mContentTypeMap = null; - - static { - mContentTypeMap = new SimpleArrayMap(); - - int i; - for (i = 0; i < PduContentTypes.contentTypes.length; i++) { - mContentTypeMap.put(PduContentTypes.contentTypes[i], i); - } - } - - /** - * Constructor. - * - * @param context the context - * @param pdu the pdu to be composed - */ - public PduComposer(final Context context, final GenericPdu pdu) { - mPdu = pdu; - mResolver = context.getContentResolver(); - mPduHeader = pdu.getPduHeaders(); - mStack = new BufferStack(); - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - /** - * Make the message. No need to check whether mandatory fields are set, - * because the constructors of outgoing pdus are taking care of this. - * - * @return OutputStream of maked message. Return null if - * the PDU is invalid. - */ - public byte[] make() { - // Get Message-type. - final int type = mPdu.getMessageType(); - - /* make the message */ - switch (type) { - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) { - return null; - } - break; - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) { - return null; - } - break; - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - if (makeAckInd() != PDU_COMPOSE_SUCCESS) { - return null; - } - break; - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) { - return null; - } - break; - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - if (makeNotificationInd() != PDU_COMPOSE_SUCCESS) { - return null; - } - break; - default: - return null; - } - - return mMessage.toByteArray(); - } - - /** - * Copy buf to mMessage. - */ - protected void arraycopy(final byte[] buf, final int pos, final int length) { - mMessage.write(buf, pos, length); - mPosition = mPosition + length; - } - - /** - * Append a byte to mMessage. - */ - protected void append(final int value) { - mMessage.write(value); - mPosition++; - } - - /** - * Append short integer value to mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendShortInteger(final int value) { - /* - * From WAP-230-WSP-20010705-a: - * Short-integer = OCTET - * ; Integers in range 0-127 shall be encoded as a one octet value - * ; with the most significant bit set to one (1xxx xxxx) and with - * ; the value in the remaining least significant bits. - * In our implementation, only low 7 bits are stored and otherwise - * bits are ignored. - */ - append((value | 0x80) & 0xff); - } - - /** - * Append an octet number between 128 and 255 into mMessage. - * NOTE: - * A value between 0 and 127 should be appended by using appendShortInteger. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendOctet(final int number) { - append(number); - } - - /** - * Append a short length into mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendShortLength(final int value) { - /* - * From WAP-230-WSP-20010705-a: - * Short-length = - */ - append(value); - } - - /** - * Append long integer into mMessage. it's used for really long integers. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendLongInteger(final long longInt) { - /* - * From WAP-230-WSP-20010705-a: - * Long-integer = Short-length Multi-octet-integer - * ; The Short-length indicates the length of the Multi-octet-integer - * Multi-octet-integer = 1*30 OCTET - * ; The content octets shall be an unsigned integer value with the - * ; most significant octet encoded first (big-endian representation). - * ; The minimum number of octets must be used to encode the value. - */ - int size; - long temp = longInt; - - // Count the length of the long integer. - for (size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) { - temp = (temp >>> 8); - } - - // Set Length. - appendShortLength(size); - - // Count and set the long integer. - int i; - int shift = (size - 1) * 8; - - for (i = 0; i < size; i++) { - append((int) ((longInt >>> shift) & 0xff)); - shift = shift - 8; - } - } - - /** - * Append text string into mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendTextString(final byte[] text) { - /* - * From WAP-230-WSP-20010705-a: - * Text-string = [Quote] *TEXT End-of-string - * ; If the first character in the TEXT is in the range of 128-255, - * ; a Quote character must precede it. Otherwise the Quote character - * ;must be omitted. The Quote is not part of the contents. - */ - if (((text[0]) & 0xff) > TEXT_MAX) { // No need to check for <= 255 - append(TEXT_MAX); - } - - arraycopy(text, 0, text.length); - append(0); - } - - /** - * Append text string into mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendTextString(final String str) { - /* - * From WAP-230-WSP-20010705-a: - * Text-string = [Quote] *TEXT End-of-string - * ; If the first character in the TEXT is in the range of 128-255, - * ; a Quote character must precede it. Otherwise the Quote character - * ;must be omitted. The Quote is not part of the contents. - */ - appendTextString(str.getBytes()); - } - - /** - * Append encoded string value to mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendEncodedString(final EncodedStringValue enStr) { - /* - * From OMA-TS-MMS-ENC-V1_3-20050927-C: - * Encoded-string-value = Text-string | Value-length Char-set Text-string - */ - assert (enStr != null); - - final int charset = enStr.getCharacterSet(); - final byte[] textString = enStr.getTextString(); - if (null == textString) { - return; - } - - /* - * In the implementation of EncodedStringValue, the charset field will - * never be 0. It will always be composed as - * Encoded-string-value = Value-length Char-set Text-string - */ - mStack.newbuf(); - final PositionMarker start = mStack.mark(); - - appendShortInteger(charset); - appendTextString(textString); - - final int len = start.getLength(); - mStack.pop(); - appendValueLength(len); - mStack.copy(); - } - - /** - * Append uintvar integer into mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendUintvarInteger(final long value) { - /* - * From WAP-230-WSP-20010705-a: - * To encode a large unsigned integer, split it into 7-bit fragments - * and place them in the payloads of multiple octets. The most significant - * bits are placed in the first octets with the least significant bits - * ending up in the last octet. All octets MUST set the Continue bit to 1 - * except the last octet, which MUST set the Continue bit to 0. - */ - int i; - long max = SHORT_INTEGER_MAX; - - for (i = 0; i < 5; i++) { - if (value < max) { - break; - } - - max = (max << 7) | 0x7fL; - } - - while (i > 0) { - long temp = value >>> (i * 7); - temp = temp & 0x7f; - - append((int) ((temp | 0x80) & 0xff)); - - i--; - } - - append((int) (value & 0x7f)); - } - - /** - * Append date value into mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendDateValue(final long date) { - /* - * From OMA-TS-MMS-ENC-V1_3-20050927-C: - * Date-value = Long-integer - */ - appendLongInteger(date); - } - - /** - * Append value length to mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendValueLength(final long value) { - /* - * From WAP-230-WSP-20010705-a: - * Value-length = Short-length | (Length-quote Length) - * ; Value length is used to indicate the length of the value to follow - * Short-length = - * Length-quote = - * Length = Uintvar-integer - */ - if (value < LENGTH_QUOTE) { - appendShortLength((int) value); - return; - } - - append(LENGTH_QUOTE); - appendUintvarInteger(value); - } - - /** - * Append quoted string to mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendQuotedString(final byte[] text) { - /* - * From WAP-230-WSP-20010705-a: - * Quoted-string = *TEXT End-of-string - * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing - * ;quotation-marks <"> removed. - */ - append(QUOTED_STRING_FLAG); - arraycopy(text, 0, text.length); - append(END_STRING_FLAG); - } - - /** - * Append quoted string to mMessage. - * This implementation doesn't check the validity of parameter, since it - * assumes that the values are validated in the GenericPdu setter methods. - */ - protected void appendQuotedString(final String str) { - /* - * From WAP-230-WSP-20010705-a: - * Quoted-string = *TEXT End-of-string - * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing - * ;quotation-marks <"> removed. - */ - appendQuotedString(str.getBytes()); - } - - private EncodedStringValue appendAddressType(final EncodedStringValue address) { - EncodedStringValue temp = null; - - try { - final int addressType = checkAddressType(address.getString()); - temp = EncodedStringValue.copy(address); - if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) { - // Phone number. - temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes()); - } else if (PDU_IPV4_ADDRESS_TYPE == addressType) { - // Ipv4 address. - temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes()); - } else if (PDU_IPV6_ADDRESS_TYPE == addressType) { - // Ipv6 address. - temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes()); - } - } catch (final NullPointerException e) { - return null; - } - - return temp; - } - - /** - * Append header to mMessage. - */ - private int appendHeader(final int field) { - switch (field) { - case PduHeaders.MMS_VERSION: - appendOctet(field); - - final int version = mPduHeader.getOctet(field); - if (0 == version) { - appendShortInteger(PduHeaders.CURRENT_MMS_VERSION); - } else { - appendShortInteger(version); - } - - break; - - case PduHeaders.MESSAGE_ID: - case PduHeaders.TRANSACTION_ID: - case PduHeaders.CONTENT_LOCATION: - final byte[] textString = mPduHeader.getTextString(field); - if (null == textString) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - appendTextString(textString); - break; - - case PduHeaders.TO: - case PduHeaders.BCC: - case PduHeaders.CC: - final EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field); - - if (null == addr) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - EncodedStringValue temp; - for (int i = 0; i < addr.length; i++) { - temp = appendAddressType(addr[i]); - if (temp == null) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - appendOctet(field); - appendEncodedString(temp); - } - break; - - case PduHeaders.FROM: - // Value-length (Address-present-token Encoded-string-value | Insert-address-token) - appendOctet(field); - - final EncodedStringValue from = mPduHeader.getEncodedStringValue(field); - if ((from == null) - || TextUtils.isEmpty(from.getString()) - || new String(from.getTextString()).equals( - PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { - // Length of from = 1 - append(1); - // Insert-address-token = - append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN); - } else { - mStack.newbuf(); - final PositionMarker fstart = mStack.mark(); - - // Address-present-token = - append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN); - - temp = appendAddressType(from); - if (temp == null) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - appendEncodedString(temp); - - final int flen = fstart.getLength(); - mStack.pop(); - appendValueLength(flen); - mStack.copy(); - } - break; - - case PduHeaders.READ_STATUS: - case PduHeaders.STATUS: - case PduHeaders.REPORT_ALLOWED: - case PduHeaders.PRIORITY: - case PduHeaders.DELIVERY_REPORT: - case PduHeaders.READ_REPORT: - final int octet = mPduHeader.getOctet(field); - if (0 == octet) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - appendOctet(octet); - break; - - case PduHeaders.DATE: - final long date = mPduHeader.getLongInteger(field); - if (-1 == date) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - appendDateValue(date); - break; - - case PduHeaders.SUBJECT: - final EncodedStringValue enString = - mPduHeader.getEncodedStringValue(field); - if (null == enString) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - appendEncodedString(enString); - break; - - case PduHeaders.MESSAGE_CLASS: - final byte[] messageClass = mPduHeader.getTextString(field); - if (null == messageClass) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - if (Arrays.equals(messageClass, - PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) { - appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT); - } else if (Arrays.equals(messageClass, - PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) { - appendOctet(PduHeaders.MESSAGE_CLASS_AUTO); - } else if (Arrays.equals(messageClass, - PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) { - appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL); - } else if (Arrays.equals(messageClass, - PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) { - appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL); - } else { - appendTextString(messageClass); - } - break; - - case PduHeaders.EXPIRY: - case PduHeaders.MESSAGE_SIZE: - final long value = mPduHeader.getLongInteger(field); - if (-1 == value) { - return PDU_COMPOSE_FIELD_NOT_SET; - } - - appendOctet(field); - - mStack.newbuf(); - final PositionMarker valueStart = mStack.mark(); - - append(PduHeaders.VALUE_RELATIVE_TOKEN); - appendLongInteger(value); - - final int valueLength = valueStart.getLength(); - mStack.pop(); - appendValueLength(valueLength); - mStack.copy(); - break; - - default: - return PDU_COMPOSE_FIELD_NOT_SUPPORTED; - } - - return PDU_COMPOSE_SUCCESS; - } - - /** - * Make ReadRec.Ind. - */ - private int makeReadRecInd() { - if (mMessage == null) { - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - // X-Mms-Message-Type - appendOctet(PduHeaders.MESSAGE_TYPE); - appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND); - - // X-Mms-MMS-Version - if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Message-ID - if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // To - if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // From - if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Date Optional - appendHeader(PduHeaders.DATE); - - // X-Mms-Read-Status - if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-Applic-ID Optional(not support) - // X-Mms-Reply-Applic-ID Optional(not support) - // X-Mms-Aux-Applic-Info Optional(not support) - - return PDU_COMPOSE_SUCCESS; - } - - /** - * Make NotifyResp.Ind. - */ - private int makeNotifyResp() { - if (mMessage == null) { - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - // X-Mms-Message-Type - appendOctet(PduHeaders.MESSAGE_TYPE); - appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); - - // X-Mms-Transaction-ID - if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-MMS-Version - if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-Status - if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-Report-Allowed Optional (not support) - return PDU_COMPOSE_SUCCESS; - } - - /** - * Make Acknowledge.Ind. - */ - private int makeAckInd() { - if (mMessage == null) { - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - // X-Mms-Message-Type - appendOctet(PduHeaders.MESSAGE_TYPE); - appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); - - // X-Mms-Transaction-ID - if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-MMS-Version - if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-Report-Allowed Optional - appendHeader(PduHeaders.REPORT_ALLOWED); - - return PDU_COMPOSE_SUCCESS; - } - - /** - * Make Acknowledge.Ind. - */ - private int makeNotificationInd() { - if (mMessage == null) { - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - // X-Mms-Message-Type - appendOctet(PduHeaders.MESSAGE_TYPE); - appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); - - // X-Mms-Transaction-ID - if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-MMS-Version - if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // From - if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Subject Optional - appendHeader(PduHeaders.SUBJECT); - - // Expiry - if (appendHeader(PduHeaders.MESSAGE_CLASS) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Expiry - if (appendHeader(PduHeaders.MESSAGE_SIZE) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Expiry - if (appendHeader(PduHeaders.EXPIRY) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // X-Mms-Content-Location - if (appendHeader(PduHeaders.CONTENT_LOCATION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - return PDU_COMPOSE_SUCCESS; - } - - /** - * Make Send.req. - */ - private int makeSendReqPdu() { - if (mMessage == null) { - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - // X-Mms-Message-Type - appendOctet(PduHeaders.MESSAGE_TYPE); - appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ); - - // X-Mms-Transaction-ID - appendOctet(PduHeaders.TRANSACTION_ID); - - final byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID); - if (trid == null) { - // Transaction-ID should be set(by Transaction) before make(). - throw new IllegalArgumentException("Transaction-ID is null."); - } - appendTextString(trid); - - // X-Mms-MMS-Version - if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Date Date-value Optional. - appendHeader(PduHeaders.DATE); - - // From - if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - boolean recipient = false; - - // To - if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) { - recipient = true; - } - - // Cc - if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) { - recipient = true; - } - - // Bcc - if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) { - recipient = true; - } - - // Need at least one of "cc", "bcc" and "to". - if (false == recipient) { - return PDU_COMPOSE_CONTENT_ERROR; - } - - // Subject Optional - appendHeader(PduHeaders.SUBJECT); - - // X-Mms-Message-Class Optional - // Message-class-value = Class-identifier | Token-text - appendHeader(PduHeaders.MESSAGE_CLASS); - - // X-Mms-Expiry Optional - appendHeader(PduHeaders.EXPIRY); - - // X-Mms-Priority Optional - appendHeader(PduHeaders.PRIORITY); - - // X-Mms-Delivery-Report Optional - appendHeader(PduHeaders.DELIVERY_REPORT); - - // X-Mms-Read-Report Optional - appendHeader(PduHeaders.READ_REPORT); - - // Content-Type - appendOctet(PduHeaders.CONTENT_TYPE); - - // Message body - return makeMessageBody(); - } - - /** - * Make message body. - */ - private int makeMessageBody() { - // 1. add body informations - mStack.newbuf(); // Switching buffer because we need to - - final PositionMarker ctStart = mStack.mark(); - - // This contentTypeIdentifier should be used for type of attachment... - final String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE)); - final Integer contentTypeIdentifier = mContentTypeMap.get(contentType); - if (contentTypeIdentifier == null) { - // content type is mandatory - return PDU_COMPOSE_CONTENT_ERROR; - } - - appendShortInteger(contentTypeIdentifier.intValue()); - - // content-type parameter: start - final PduBody body = ((SendReq) mPdu).getBody(); - if (null == body || body.getPartsNum() == 0) { - // empty message - appendUintvarInteger(0); - mStack.pop(); - mStack.copy(); - return PDU_COMPOSE_SUCCESS; - } - - PduPart part; - try { - part = body.getPart(0); - - final byte[] start = part.getContentId(); - if (start != null) { - appendOctet(PduPart.P_DEP_START); - if (('<' == start[0]) && ('>' == start[start.length - 1])) { - appendTextString(start); - } else { - appendTextString("<" + new String(start) + ">"); - } - } - - // content-type parameter: type - appendOctet(PduPart.P_CT_MR_TYPE); - appendTextString(part.getContentType()); - } catch (final ArrayIndexOutOfBoundsException e) { - e.printStackTrace(); - } - - final int ctLength = ctStart.getLength(); - mStack.pop(); - appendValueLength(ctLength); - mStack.copy(); - - // 3. add content - final int partNum = body.getPartsNum(); - appendUintvarInteger(partNum); - for (int i = 0; i < partNum; i++) { - part = body.getPart(i); - mStack.newbuf(); // Leaving space for header lengh and data length - final PositionMarker attachment = mStack.mark(); - - mStack.newbuf(); // Leaving space for Content-Type length - final PositionMarker contentTypeBegin = mStack.mark(); - - final byte[] partContentType = part.getContentType(); - - if (partContentType == null) { - // content type is mandatory - return PDU_COMPOSE_CONTENT_ERROR; - } - - // content-type value - final Integer partContentTypeIdentifier = - mContentTypeMap.get(new String(partContentType)); - if (partContentTypeIdentifier == null) { - appendTextString(partContentType); - } else { - appendShortInteger(partContentTypeIdentifier.intValue()); - } - - /* Content-type parameter : name. - * The value of name, filename, content-location is the same. - * Just one of them is enough for this PDU. - */ - byte[] name = part.getName(); - - if (null == name) { - name = part.getFilename(); - - if (null == name) { - name = part.getContentLocation(); - - if (null == name) { - /* at lease one of name, filename, Content-location - * should be available. - */ - // I found that an mms received from tmomail.net will include a SMIL part - // that has no name. That would cause the code here to return - // PDU_COMPOSE_CONTENT_ERROR when a user tried to forward the message. The - // message would never send and the user would be stuck in a retry - // situation. Simply jam in any old name here to fix the problem. - name = "smil.xml".getBytes(); - } - } - } - appendOctet(PduPart.P_DEP_NAME); - appendTextString(name); - - // content-type parameter : charset - final int charset = part.getCharset(); - if (charset != 0) { - appendOctet(PduPart.P_CHARSET); - appendShortInteger(charset); - } - - final int contentTypeLength = contentTypeBegin.getLength(); - mStack.pop(); - appendValueLength(contentTypeLength); - mStack.copy(); - - // content id - final byte[] contentId = part.getContentId(); - - if (null != contentId) { - appendOctet(PduPart.P_CONTENT_ID); - if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) { - appendQuotedString(contentId); - } else { - appendQuotedString("<" + new String(contentId) + ">"); - } - } - - // content-location - final byte[] contentLocation = part.getContentLocation(); - if (null != contentLocation) { - appendOctet(PduPart.P_CONTENT_LOCATION); - appendTextString(contentLocation); - } - - // content - final int headerLength = attachment.getLength(); - - int dataLength = 0; // Just for safety... - final byte[] partData = part.getData(); - - if (partData != null) { - arraycopy(partData, 0, partData.length); - dataLength = partData.length; - } else { - InputStream cr = null; - try { - final byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE]; - cr = mResolver.openInputStream(part.getDataUri()); - int len = 0; - while ((len = cr.read(buffer)) != -1) { - mMessage.write(buffer, 0, len); - mPosition += len; - dataLength += len; - } - } catch (final FileNotFoundException e) { - return PDU_COMPOSE_CONTENT_ERROR; - } catch (final IOException e) { - return PDU_COMPOSE_CONTENT_ERROR; - } catch (final RuntimeException e) { - return PDU_COMPOSE_CONTENT_ERROR; - } finally { - if (cr != null) { - try { - cr.close(); - } catch (final IOException e) { - // Nothing to do - } - } - } - } - - if (dataLength != (attachment.getLength() - headerLength)) { - throw new RuntimeException("BUG: Length sanity check failed"); - } - - mStack.pop(); - appendUintvarInteger(headerLength); - appendUintvarInteger(dataLength); - mStack.copy(); - } - - return PDU_COMPOSE_SUCCESS; - } - - /** - * Record current message informations. - */ - private static class LengthRecordNode { - - ByteArrayOutputStream currentMessage = null; - - public int currentPosition = 0; - - public LengthRecordNode next = null; - } - - /** - * Mark current message position and stact size. - */ - private class PositionMarker { - - private int c_pos; // Current position - - private int currentStackSize; // Current stack size - - int getLength() { - // If these assert fails, likely that you are finding the - // size of buffer that is deep in BufferStack you can only - // find the length of the buffer that is on top - if (currentStackSize != mStack.stackSize) { - throw new RuntimeException("BUG: Invalid call to getLength()"); - } - - return mPosition - c_pos; - } - } - - /** - * This implementation can be OPTIMIZED to use only - * 2 buffers. This optimization involves changing BufferStack - * only... Its usage (interface) will not change. - */ - private class BufferStack { - - private LengthRecordNode stack = null; - - private LengthRecordNode toCopy = null; - - int stackSize = 0; - - /** - * Create a new message buffer and push it into the stack. - */ - void newbuf() { - // You can't create a new buff when toCopy != null - // That is after calling pop() and before calling copy() - // If you do, it is a bug - if (toCopy != null) { - throw new RuntimeException("BUG: Invalid newbuf() before copy()"); - } - - final LengthRecordNode temp = new LengthRecordNode(); - - temp.currentMessage = mMessage; - temp.currentPosition = mPosition; - - temp.next = stack; - stack = temp; - - stackSize = stackSize + 1; - - mMessage = new ByteArrayOutputStream(); - mPosition = 0; - } - - /** - * Pop the message before and record current message in the stack. - */ - void pop() { - final ByteArrayOutputStream currentMessage = mMessage; - final int currentPosition = mPosition; - - mMessage = stack.currentMessage; - mPosition = stack.currentPosition; - - toCopy = stack; - // Re using the top element of the stack to avoid memory allocation - - stack = stack.next; - stackSize = stackSize - 1; - - toCopy.currentMessage = currentMessage; - toCopy.currentPosition = currentPosition; - } - - /** - * Append current message to the message before. - */ - void copy() { - arraycopy(toCopy.currentMessage.toByteArray(), 0, - toCopy.currentPosition); - - toCopy = null; - } - - /** - * Mark current message position - */ - PositionMarker mark() { - final PositionMarker m = new PositionMarker(); - - m.c_pos = mPosition; - m.currentStackSize = stackSize; - - return m; - } - } - - /** - * Check address type. - * - * @param address address string without the postfix stinng type, - * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4" - * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number, - * PDU_EMAIL_ADDRESS_TYPE if it is email address, - * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address, - * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address, - * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown. - */ - protected static int checkAddressType(final String address) { - /** - * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8. - * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode) - * e-mail = mailbox; to the definition of mailbox as described in - * section 3.4 of [RFC2822], but excluding the - * obsolete definitions as indicated by the "obs-" prefix. - * device-address = ( global-phone-number "/TYPE=PLMN" ) - * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" ) - * / ( escaped-value "/TYPE=" address-type ) - * - * global-phone-number = ["+"] 1*( DIGIT / written-sep ) - * written-sep =("-"/".") - * - * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value - * - * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373 - */ - - if (null == address) { - return PDU_UNKNOWN_ADDRESS_TYPE; - } - - if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) { - // Ipv4 address. - return PDU_IPV4_ADDRESS_TYPE; - } else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) { - // Phone number. - return PDU_PHONE_NUMBER_ADDRESS_TYPE; - } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) { - // Email address. - return PDU_EMAIL_ADDRESS_TYPE; - } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) { - // Ipv6 address. - return PDU_IPV6_ADDRESS_TYPE; - } else { - // Unknown address. - return PDU_UNKNOWN_ADDRESS_TYPE; - } - } -} diff --git a/src/com/android/messaging/mmslib/pdu/PduContentTypes.java b/src/com/android/messaging/mmslib/pdu/PduContentTypes.java deleted file mode 100644 index f2cebf1..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduContentTypes.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -public class PduContentTypes { - /** - * All content types. From: - * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm - */ - static final String[] contentTypes = { - "*/*", /* 0x00 */ - "text/*", /* 0x01 */ - "text/html", /* 0x02 */ - "text/plain", /* 0x03 */ - "text/x-hdml", /* 0x04 */ - "text/x-ttml", /* 0x05 */ - "text/x-vCalendar", /* 0x06 */ - "text/x-vCard", /* 0x07 */ - "text/vnd.wap.wml", /* 0x08 */ - "text/vnd.wap.wmlscript", /* 0x09 */ - "text/vnd.wap.wta-event", /* 0x0A */ - "multipart/*", /* 0x0B */ - "multipart/mixed", /* 0x0C */ - "multipart/form-data", /* 0x0D */ - "multipart/byterantes", /* 0x0E */ - "multipart/alternative", /* 0x0F */ - "application/*", /* 0x10 */ - "application/java-vm", /* 0x11 */ - "application/x-www-form-urlencoded", /* 0x12 */ - "application/x-hdmlc", /* 0x13 */ - "application/vnd.wap.wmlc", /* 0x14 */ - "application/vnd.wap.wmlscriptc", /* 0x15 */ - "application/vnd.wap.wta-eventc", /* 0x16 */ - "application/vnd.wap.uaprof", /* 0x17 */ - "application/vnd.wap.wtls-ca-certificate", /* 0x18 */ - "application/vnd.wap.wtls-user-certificate", /* 0x19 */ - "application/x-x509-ca-cert", /* 0x1A */ - "application/x-x509-user-cert", /* 0x1B */ - "image/*", /* 0x1C */ - "image/gif", /* 0x1D */ - "image/jpeg", /* 0x1E */ - "image/tiff", /* 0x1F */ - "image/png", /* 0x20 */ - "image/vnd.wap.wbmp", /* 0x21 */ - "application/vnd.wap.multipart.*", /* 0x22 */ - "application/vnd.wap.multipart.mixed", /* 0x23 */ - "application/vnd.wap.multipart.form-data", /* 0x24 */ - "application/vnd.wap.multipart.byteranges", /* 0x25 */ - "application/vnd.wap.multipart.alternative", /* 0x26 */ - "application/xml", /* 0x27 */ - "text/xml", /* 0x28 */ - "application/vnd.wap.wbxml", /* 0x29 */ - "application/x-x968-cross-cert", /* 0x2A */ - "application/x-x968-ca-cert", /* 0x2B */ - "application/x-x968-user-cert", /* 0x2C */ - "text/vnd.wap.si", /* 0x2D */ - "application/vnd.wap.sic", /* 0x2E */ - "text/vnd.wap.sl", /* 0x2F */ - "application/vnd.wap.slc", /* 0x30 */ - "text/vnd.wap.co", /* 0x31 */ - "application/vnd.wap.coc", /* 0x32 */ - "application/vnd.wap.multipart.related", /* 0x33 */ - "application/vnd.wap.sia", /* 0x34 */ - "text/vnd.wap.connectivity-xml", /* 0x35 */ - "application/vnd.wap.connectivity-wbxml", /* 0x36 */ - "application/pkcs7-mime", /* 0x37 */ - "application/vnd.wap.hashed-certificate", /* 0x38 */ - "application/vnd.wap.signed-certificate", /* 0x39 */ - "application/vnd.wap.cert-response", /* 0x3A */ - "application/xhtml+xml", /* 0x3B */ - "application/wml+xml", /* 0x3C */ - "text/css", /* 0x3D */ - "application/vnd.wap.mms-message", /* 0x3E */ - "application/vnd.wap.rollover-certificate", /* 0x3F */ - "application/vnd.wap.locc+wbxml", /* 0x40 */ - "application/vnd.wap.loc+xml", /* 0x41 */ - "application/vnd.syncml.dm+wbxml", /* 0x42 */ - "application/vnd.syncml.dm+xml", /* 0x43 */ - "application/vnd.syncml.notification", /* 0x44 */ - "application/vnd.wap.xhtml+xml", /* 0x45 */ - "application/vnd.wv.csp.cir", /* 0x46 */ - "application/vnd.oma.dd+xml", /* 0x47 */ - "application/vnd.oma.drm.message", /* 0x48 */ - "application/vnd.oma.drm.content", /* 0x49 */ - "application/vnd.oma.drm.rights+xml", /* 0x4A */ - "application/vnd.oma.drm.rights+wbxml", /* 0x4B */ - "application/vnd.wv.csp+xml", /* 0x4C */ - "application/vnd.wv.csp+wbxml", /* 0x4D */ - "application/vnd.syncml.ds.notification", /* 0x4E */ - "audio/*", /* 0x4F */ - "video/*", /* 0x50 */ - "application/vnd.oma.dd2+xml", /* 0x51 */ - "application/mikey" /* 0x52 */ - }; -} diff --git a/src/com/android/messaging/mmslib/pdu/PduHeaders.java b/src/com/android/messaging/mmslib/pdu/PduHeaders.java deleted file mode 100644 index 96d18ee..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduHeaders.java +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.util.SparseArray; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -import java.util.ArrayList; - -public class PduHeaders { - /** - * All pdu header fields. - */ - public static final int BCC = 0x81; - public static final int CC = 0x82; - public static final int CONTENT_LOCATION = 0x83; - public static final int CONTENT_TYPE = 0x84; - public static final int DATE = 0x85; - public static final int DELIVERY_REPORT = 0x86; - public static final int DELIVERY_TIME = 0x87; - public static final int EXPIRY = 0x88; - public static final int FROM = 0x89; - public static final int MESSAGE_CLASS = 0x8A; - public static final int MESSAGE_ID = 0x8B; - public static final int MESSAGE_TYPE = 0x8C; - public static final int MMS_VERSION = 0x8D; - public static final int MESSAGE_SIZE = 0x8E; - public static final int PRIORITY = 0x8F; - - public static final int READ_REPLY = 0x90; - public static final int READ_REPORT = 0x90; - public static final int REPORT_ALLOWED = 0x91; - public static final int RESPONSE_STATUS = 0x92; - public static final int RESPONSE_TEXT = 0x93; - public static final int SENDER_VISIBILITY = 0x94; - public static final int STATUS = 0x95; - public static final int SUBJECT = 0x96; - public static final int TO = 0x97; - public static final int TRANSACTION_ID = 0x98; - public static final int RETRIEVE_STATUS = 0x99; - public static final int RETRIEVE_TEXT = 0x9A; - public static final int READ_STATUS = 0x9B; - public static final int REPLY_CHARGING = 0x9C; - public static final int REPLY_CHARGING_DEADLINE = 0x9D; - public static final int REPLY_CHARGING_ID = 0x9E; - public static final int REPLY_CHARGING_SIZE = 0x9F; - - public static final int PREVIOUSLY_SENT_BY = 0xA0; - public static final int PREVIOUSLY_SENT_DATE = 0xA1; - public static final int STORE = 0xA2; - public static final int MM_STATE = 0xA3; - public static final int MM_FLAGS = 0xA4; - public static final int STORE_STATUS = 0xA5; - public static final int STORE_STATUS_TEXT = 0xA6; - public static final int STORED = 0xA7; - public static final int ATTRIBUTES = 0xA8; - public static final int TOTALS = 0xA9; - public static final int MBOX_TOTALS = 0xAA; - public static final int QUOTAS = 0xAB; - public static final int MBOX_QUOTAS = 0xAC; - public static final int MESSAGE_COUNT = 0xAD; - public static final int CONTENT = 0xAE; - public static final int START = 0xAF; - - public static final int ADDITIONAL_HEADERS = 0xB0; - public static final int DISTRIBUTION_INDICATOR = 0xB1; - public static final int ELEMENT_DESCRIPTOR = 0xB2; - public static final int LIMIT = 0xB3; - public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4; - public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5; - public static final int STATUS_TEXT = 0xB6; - public static final int APPLIC_ID = 0xB7; - public static final int REPLY_APPLIC_ID = 0xB8; - public static final int AUX_APPLIC_ID = 0xB9; - public static final int CONTENT_CLASS = 0xBA; - public static final int DRM_CONTENT = 0xBB; - public static final int ADAPTATION_ALLOWED = 0xBC; - public static final int REPLACE_ID = 0xBD; - public static final int CANCEL_ID = 0xBE; - public static final int CANCEL_STATUS = 0xBF; - - /** - * X-Mms-Message-Type field types. - */ - public static final int MESSAGE_TYPE_SEND_REQ = 0x80; - public static final int MESSAGE_TYPE_SEND_CONF = 0x81; - public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82; - public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83; - public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; - public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85; - public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86; - public static final int MESSAGE_TYPE_READ_REC_IND = 0x87; - public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88; - public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89; - public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A; - public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B; - public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C; - public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D; - public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E; - public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F; - public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90; - public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91; - public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92; - public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93; - public static final int MESSAGE_TYPE_DELETE_REQ = 0x94; - public static final int MESSAGE_TYPE_DELETE_CONF = 0x95; - public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96; - public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97; - - /** - * X-Mms-Delivery-Report | - * X-Mms-Read-Report | - * X-Mms-Report-Allowed | - * X-Mms-Sender-Visibility | - * X-Mms-Store | - * X-Mms-Stored | - * X-Mms-Totals | - * X-Mms-Quotas | - * X-Mms-Distribution-Indicator | - * X-Mms-DRM-Content | - * X-Mms-Adaptation-Allowed | - * field types. - */ - public static final int VALUE_YES = 0x80; - public static final int VALUE_NO = 0x81; - - /** - * Delivery-Time | - * Expiry and Reply-Charging-Deadline | - * field type components. - */ - public static final int VALUE_ABSOLUTE_TOKEN = 0x80; - public static final int VALUE_RELATIVE_TOKEN = 0x81; - - /** - * X-Mms-MMS-Version field types. - */ - public static final int MMS_VERSION_1_3 = ((1 << 4) | 3); - public static final int MMS_VERSION_1_2 = ((1 << 4) | 2); - public static final int MMS_VERSION_1_1 = ((1 << 4) | 1); - public static final int MMS_VERSION_1_0 = ((1 << 4) | 0); - - // Current version is 1.2. - public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2; - - /** - * From field type components. - */ - public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80; - public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81; - - public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token"; - - public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token"; - - /** - * X-Mms-Status Field. - */ - public static final int STATUS_EXPIRED = 0x80; - public static final int STATUS_RETRIEVED = 0x81; - public static final int STATUS_REJECTED = 0x82; - public static final int STATUS_DEFERRED = 0x83; - public static final int STATUS_UNRECOGNIZED = 0x84; - public static final int STATUS_INDETERMINATE = 0x85; - public static final int STATUS_FORWARDED = 0x86; - public static final int STATUS_UNREACHABLE = 0x87; - - /** - * MM-Flags field type components. - */ - public static final int MM_FLAGS_ADD_TOKEN = 0x80; - public static final int MM_FLAGS_REMOVE_TOKEN = 0x81; - public static final int MM_FLAGS_FILTER_TOKEN = 0x82; - - /** - * X-Mms-Message-Class field types. - */ - public static final int MESSAGE_CLASS_PERSONAL = 0x80; - public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81; - public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82; - public static final int MESSAGE_CLASS_AUTO = 0x83; - - public static final String MESSAGE_CLASS_PERSONAL_STR = "personal"; - - public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement"; - - public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational"; - - public static final String MESSAGE_CLASS_AUTO_STR = "auto"; - - /** - * X-Mms-Priority field types. - */ - public static final int PRIORITY_LOW = 0x80; - public static final int PRIORITY_NORMAL = 0x81; - public static final int PRIORITY_HIGH = 0x82; - - /** - * X-Mms-Response-Status field types. - */ - public static final int RESPONSE_STATUS_OK = 0x80; - public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81; - public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82; - - public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83; - public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84; - - public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85; - public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86; - public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87; - public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3; - public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4; - - public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE - = 0xE0; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED - = 0xE1; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT - = 0xE2; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED - = 0xE3; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND - = 0xE4; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED - = 0xE5; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET - = 0xE6; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED - = 0xE7; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED - = 0xE8; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED - = 0xE9; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED - = 0xEA; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID - = 0xEB; - public static final int RESPONSE_STATUS_ERROR_PERMANENT_END - = 0xFF; - - /** - * X-Mms-Retrieve-Status field types. - */ - public static final int RETRIEVE_STATUS_OK = 0x80; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1; - public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2; - public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3; - public static final int RETRIEVE_STATUS_ERROR_END = 0xFF; - - /** - * X-Mms-Sender-Visibility field types. - */ - public static final int SENDER_VISIBILITY_HIDE = 0x80; - public static final int SENDER_VISIBILITY_SHOW = 0x81; - - /** - * X-Mms-Read-Status field types. - */ - public static final int READ_STATUS_READ = 0x80; - public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81; - - /** - * X-Mms-Cancel-Status field types. - */ - public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80; - public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81; - - /** - * X-Mms-Reply-Charging field types. - */ - public static final int REPLY_CHARGING_REQUESTED = 0x80; - public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81; - public static final int REPLY_CHARGING_ACCEPTED = 0x82; - public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83; - - /** - * X-Mms-MM-State field types. - */ - public static final int MM_STATE_DRAFT = 0x80; - public static final int MM_STATE_SENT = 0x81; - public static final int MM_STATE_NEW = 0x82; - public static final int MM_STATE_RETRIEVED = 0x83; - public static final int MM_STATE_FORWARDED = 0x84; - - /** - * X-Mms-Recommended-Retrieval-Mode field types. - */ - public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80; - - /** - * X-Mms-Content-Class field types. - */ - public static final int CONTENT_CLASS_TEXT = 0x80; - public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81; - public static final int CONTENT_CLASS_IMAGE_RICH = 0x82; - public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83; - public static final int CONTENT_CLASS_VIDEO_RICH = 0x84; - public static final int CONTENT_CLASS_MEGAPIXEL = 0x85; - public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86; - public static final int CONTENT_CLASS_CONTENT_RICH = 0x87; - - /** - * X-Mms-Store-Status field types. - */ - public static final int STORE_STATUS_SUCCESS = 0x80; - public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; - public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1; - public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; - public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; - public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; - public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3; - public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4; - public static final int STORE_STATUS_ERROR_END = 0xFF; - - /** - * The map contains the value of all headers. - */ - private SparseArray mHeaderMap = null; - - /** - * Constructor of PduHeaders. - */ - public PduHeaders() { - mHeaderMap = new SparseArray(); - } - - /** - * Get octet value by header field. - * - * @param field the field - * @return the octet value of the pdu header - * with specified header field. Return 0 if - * the value is not set. - */ - protected int getOctet(int field) { - Integer octet = (Integer) mHeaderMap.get(field); - if (null == octet) { - return 0; - } - - return octet; - } - - /** - * Set octet value to pdu header by header field. - * - * @param value the value - * @param field the field - * @throws InvalidHeaderValueException if the value is invalid. - */ - protected void setOctet(int value, int field) - throws InvalidHeaderValueException { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - switch (field) { - case REPORT_ALLOWED: - case ADAPTATION_ALLOWED: - case DELIVERY_REPORT: - case DRM_CONTENT: - case DISTRIBUTION_INDICATOR: - case QUOTAS: - case READ_REPORT: - case STORE: - case STORED: - case TOTALS: - case SENDER_VISIBILITY: - if ((VALUE_YES != value) && (VALUE_NO != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case READ_STATUS: - if ((READ_STATUS_READ != value) && - (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case CANCEL_STATUS: - if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) && - (CANCEL_STATUS_REQUEST_CORRUPTED != value)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case PRIORITY: - if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case STATUS: - if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case REPLY_CHARGING: - if ((value < REPLY_CHARGING_REQUESTED) - || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case MM_STATE: - if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case RECOMMENDED_RETRIEVAL_MODE: - if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case CONTENT_CLASS: - if ((value < CONTENT_CLASS_TEXT) - || (value > CONTENT_CLASS_CONTENT_RICH)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - case RETRIEVE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value. - if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && - (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) && - (value <= RETRIEVE_STATUS_ERROR_END)) { - value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; - } else if ((value < RETRIEVE_STATUS_OK) || - ((value > RETRIEVE_STATUS_OK) && - (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > RETRIEVE_STATUS_ERROR_END)) { - value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case STORE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value. - if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && - (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = STORE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) && - (value <= STORE_STATUS_ERROR_END)) { - value = STORE_STATUS_ERROR_PERMANENT_FAILURE; - } else if ((value < STORE_STATUS_SUCCESS) || - ((value > STORE_STATUS_SUCCESS) && - (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > STORE_STATUS_ERROR_END)) { - value = STORE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case RESPONSE_STATUS: - // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value. - if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) && - (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) { - value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE; - } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) && - (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) || - (value < RESPONSE_STATUS_OK) || - ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) && - (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) || - (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) { - value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE; - } - break; - case MMS_VERSION: - if ((value < MMS_VERSION_1_0) || (value > MMS_VERSION_1_3)) { - value = CURRENT_MMS_VERSION; // Current version is the default value. - } - break; - case MESSAGE_TYPE: - if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) { - // Invalid value. - throw new InvalidHeaderValueException("Invalid Octet value!"); - } - break; - default: - // This header value should not be Octect. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } - - /** - * Get TextString value by header field. - * - * @param field the field - * @return the TextString value of the pdu header - * with specified header field - */ - protected byte[] getTextString(int field) { - return (byte[]) mHeaderMap.get(field); - } - - /** - * Set TextString value to pdu header by header field. - * - * @param value the value - * @param field the field - * @return the TextString value of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setTextString(byte[] value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case TRANSACTION_ID: - case REPLY_CHARGING_ID: - case AUX_APPLIC_ID: - case APPLIC_ID: - case REPLY_APPLIC_ID: - case MESSAGE_ID: - case REPLACE_ID: - case CANCEL_ID: - case CONTENT_LOCATION: - case MESSAGE_CLASS: - case CONTENT_TYPE: - break; - default: - // This header value should not be Text-String. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } - - /** - * Get EncodedStringValue value by header field. - * - * @param field the field - * @return the EncodedStringValue value of the pdu header - * with specified header field - */ - protected EncodedStringValue getEncodedStringValue(int field) { - return (EncodedStringValue) mHeaderMap.get(field); - } - - /** - * Get TO, CC or BCC header value. - * - * @param field the field - * @return the EncodeStringValue array of the pdu header - * with specified header field - */ - protected EncodedStringValue[] getEncodedStringValues(int field) { - ArrayList list = - (ArrayList) mHeaderMap.get(field); - if (null == list) { - return null; - } - EncodedStringValue[] values = new EncodedStringValue[list.size()]; - return list.toArray(values); - } - - /** - * Set EncodedStringValue value to pdu header by header field. - * - * @param value the value - * @param field the field - * @return the EncodedStringValue value of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setEncodedStringValue(EncodedStringValue value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case SUBJECT: - case RECOMMENDED_RETRIEVAL_MODE_TEXT: - case RETRIEVE_TEXT: - case STATUS_TEXT: - case STORE_STATUS_TEXT: - case RESPONSE_TEXT: - case FROM: - case PREVIOUSLY_SENT_BY: - case MM_FLAGS: - break; - default: - // This header value should not be Encoded-String-Value. - throw new RuntimeException("Invalid header field!"); - } - - mHeaderMap.put(field, value); - } - - /** - * Set TO, CC or BCC header value. - * - * @param value the value - * @param field the field - * @return the EncodedStringValue value array of the pdu header - * with specified header field - * @throws NullPointerException if the value is null. - */ - protected void setEncodedStringValues(EncodedStringValue[] value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case BCC: - case CC: - case TO: - break; - default: - // This header value should not be Encoded-String-Value. - throw new RuntimeException("Invalid header field!"); - } - - ArrayList list = new ArrayList(); - for (int i = 0; i < value.length; i++) { - list.add(value[i]); - } - mHeaderMap.put(field, list); - } - - /** - * Append one EncodedStringValue to another. - * - * @param value the EncodedStringValue to append - * @param field the field - * @throws NullPointerException if the value is null. - */ - protected void appendEncodedStringValue(EncodedStringValue value, - int field) { - if (null == value) { - throw new NullPointerException(); - } - - switch (field) { - case BCC: - case CC: - case TO: - break; - default: - throw new RuntimeException("Invalid header field!"); - } - - ArrayList list = - (ArrayList) mHeaderMap.get(field); - if (null == list) { - list = new ArrayList(); - } - list.add(value); - mHeaderMap.put(field, list); - } - - /** - * Get LongInteger value by header field. - * - * @param field the field - * @return the LongInteger value of the pdu header - * with specified header field. if return -1, the - * field is not existed in pdu header. - */ - protected long getLongInteger(int field) { - Long longInteger = (Long) mHeaderMap.get(field); - if (null == longInteger) { - return -1; - } - - return longInteger.longValue(); - } - - /** - * Set LongInteger value to pdu header by header field. - * - * @param value the value - * @param field the field - */ - protected void setLongInteger(long value, int field) { - /** - * Check whether this field can be set for specific - * header and check validity of the field. - */ - switch (field) { - case DATE: - case REPLY_CHARGING_SIZE: - case MESSAGE_SIZE: - case MESSAGE_COUNT: - case START: - case LIMIT: - case DELIVERY_TIME: - case EXPIRY: - case REPLY_CHARGING_DEADLINE: - case PREVIOUSLY_SENT_DATE: - break; - default: - // This header value should not be LongInteger. - throw new RuntimeException("Invalid header field!"); - } - mHeaderMap.put(field, value); - } - - public boolean hasHeader(int field) { - return mHeaderMap.get(field, null) != null; - } -} diff --git a/src/com/android/messaging/mmslib/pdu/PduParser.java b/src/com/android/messaging/mmslib/pdu/PduParser.java deleted file mode 100755 index e392fb5..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduParser.java +++ /dev/null @@ -1,2044 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.util.Log; -import android.util.SparseArray; - -import com.android.messaging.mmslib.InvalidHeaderValueException; -import com.android.messaging.util.ContentType; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - -public class PduParser { - /** - * The log tag. - */ - private static final String LOG_TAG = "PduParser"; - - private static final boolean LOCAL_LOGV = false; - - /** - * The next are WAP values defined in WSP specification. - */ - private static final int QUOTE = 127; - - private static final int LENGTH_QUOTE = 31; - - private static final int TEXT_MIN = 32; - - private static final int TEXT_MAX = 127; - - private static final int SHORT_INTEGER_MAX = 127; - - private static final int SHORT_LENGTH_MAX = 30; - - private static final int LONG_INTEGER_LENGTH_MAX = 8; - - private static final int QUOTED_STRING_FLAG = 34; - - private static final int END_STRING_FLAG = 0x00; - - //The next two are used by the interface "parseWapString" to - //distinguish Text-String and Quoted-String. - private static final int TYPE_TEXT_STRING = 0; - - private static final int TYPE_QUOTED_STRING = 1; - - private static final int TYPE_TOKEN_STRING = 2; - - /** - * Specify the part position. - */ - private static final int THE_FIRST_PART = 0; - - private static final int THE_LAST_PART = 1; - - /** - * The pdu data. - */ - private ByteArrayInputStream mPduDataStream = null; - - /** - * Store pdu headers - */ - private PduHeaders mHeaders = null; - - /** - * Store pdu parts. - */ - private PduBody mBody = null; - - /** - * Store the "type" parameter in "Content-Type" header field. - */ - private static byte[] mTypeParam = null; - - /** - * Store the "start" parameter in "Content-Type" header field. - */ - private static byte[] mStartParam = null; - - /** - * Whether to parse content-disposition part header - */ - private final boolean mParseContentDisposition; - - /** - * Constructor. - * - * @param pduDataStream pdu data to be parsed - * @param parseContentDisposition whether to parse the Content-Disposition part header - */ - public PduParser(byte[] pduDataStream, boolean parseContentDisposition) { - mPduDataStream = new ByteArrayInputStream(pduDataStream); - mParseContentDisposition = parseContentDisposition; - } - - /** - * Parse the pdu. - * - * @return the pdu structure if parsing successfully. - * null if parsing error happened or mandatory fields are not set. - */ - public GenericPdu parse() { - if (mPduDataStream == null) { - return null; - } - - /* parse headers */ - mHeaders = parseHeaders(mPduDataStream); - if (null == mHeaders) { - // Parse headers failed. - return null; - } - - /* get the message type */ - int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE); - - /* check mandatory header fields */ - if (false == checkMandatoryHeader(mHeaders)) { - log("check mandatory headers failed!"); - return null; - } - /* - * Get retrieve status. If the header is not there, assuming it is OK status. - * Some carriers may choose to not send this header. - */ - int retrieveStatus = mHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ? - mHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK; - - if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) || - (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType && - retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK)) { - /* need to parse the parts */ - mBody = parseParts(mPduDataStream); - if (null == mBody) { - // Parse parts failed. - return null; - } - } - - switch (messageType) { - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ"); - } - SendReq sendReq = new SendReq(mHeaders, mBody); - return sendReq; - case PduHeaders.MESSAGE_TYPE_SEND_CONF: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF"); - } - SendConf sendConf = new SendConf(mHeaders); - return sendConf; - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND"); - } - NotificationInd notificationInd = - new NotificationInd(mHeaders); - return notificationInd; - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND"); - } - NotifyRespInd notifyRespInd = - new NotifyRespInd(mHeaders); - return notifyRespInd; - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF"); - } - RetrieveConf retrieveConf = - new RetrieveConf(mHeaders, mBody); - if (retrieveStatus != PduHeaders.RETRIEVE_STATUS_OK) { - // For failure only no need to check content type - return retrieveConf; - } - byte[] contentType = retrieveConf.getContentType(); - if (null == contentType) { - return null; - } - String ctTypeStr = new String(contentType); - if (ctTypeStr.equals(ContentType.MMS_MULTIPART_MIXED) - || ctTypeStr.equals(ContentType.MMS_MULTIPART_RELATED) - || ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) { - // The MMS content type must be "application/vnd.wap.multipart.mixed" - // or "application/vnd.wap.multipart.related" - // or "application/vnd.wap.multipart.alternative" - return retrieveConf; - } else if (ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) { - // "application/vnd.wap.multipart.alternative" - // should take only the first part. - PduPart firstPart = mBody.getPart(0); - mBody.removeAll(); - mBody.addPart(0, firstPart); - return retrieveConf; - } - return null; - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND"); - } - DeliveryInd deliveryInd = - new DeliveryInd(mHeaders); - return deliveryInd; - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND"); - } - AcknowledgeInd acknowledgeInd = - new AcknowledgeInd(mHeaders); - return acknowledgeInd; - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND"); - } - ReadOrigInd readOrigInd = - new ReadOrigInd(mHeaders); - return readOrigInd; - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND"); - } - ReadRecInd readRecInd = - new ReadRecInd(mHeaders); - return readRecInd; - default: - log("Parser doesn't support this message type in this version!"); - return null; - } - } - - /** - * Parse pdu headers. - * - * @param pduDataStream pdu data input stream - * @return headers in PduHeaders structure, null when parse fail - */ - protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream) { - if (pduDataStream == null) { - return null; - } - boolean keepParsing = true; - PduHeaders headers = new PduHeaders(); - - while (keepParsing && (pduDataStream.available() > 0)) { - pduDataStream.mark(1); - int headerField = extractByteValue(pduDataStream); - /* parse custom text header */ - if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { - pduDataStream.reset(); - byte[] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); - } - /* we should ignore it at the moment */ - continue; - } - switch (headerField) { - case PduHeaders.MESSAGE_TYPE: { - int messageType = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType + - " (" + Integer.toHexString(headerField) + ")"); - } - switch (messageType) { - // We don't support these kind of messages now. - case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: - case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: - case PduHeaders.MESSAGE_TYPE_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: - case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: - return null; - } - try { - headers.setOctet(messageType, headerField); - } catch (InvalidHeaderValueException e) { - log("Set invalid Octet value: " + messageType + - " into the header filed: " + headerField); - return null; - } catch (RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - /* Octect value */ - case PduHeaders.REPORT_ALLOWED: - case PduHeaders.ADAPTATION_ALLOWED: - case PduHeaders.DELIVERY_REPORT: - case PduHeaders.DRM_CONTENT: - case PduHeaders.DISTRIBUTION_INDICATOR: - case PduHeaders.QUOTAS: - case PduHeaders.READ_REPORT: - case PduHeaders.STORE: - case PduHeaders.STORED: - case PduHeaders.TOTALS: - case PduHeaders.SENDER_VISIBILITY: - case PduHeaders.READ_STATUS: - case PduHeaders.CANCEL_STATUS: - case PduHeaders.PRIORITY: - case PduHeaders.STATUS: - case PduHeaders.REPLY_CHARGING: - case PduHeaders.MM_STATE: - case PduHeaders.RECOMMENDED_RETRIEVAL_MODE: - case PduHeaders.CONTENT_CLASS: - case PduHeaders.RETRIEVE_STATUS: - case PduHeaders.STORE_STATUS: - /** - * The following field has a different value when - * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. - * For now we ignore this fact, since we do not support these PDUs - */ - case PduHeaders.RESPONSE_STATUS: { - int value = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") Octect value: " + - value); - } - - try { - headers.setOctet(value, headerField); - } catch (InvalidHeaderValueException e) { - log("Set invalid Octet value: " + value + - " into the header filed: " + headerField); - return null; - } catch (RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - - /* Long-Integer */ - case PduHeaders.DATE: - case PduHeaders.REPLY_CHARGING_SIZE: - case PduHeaders.MESSAGE_SIZE: { - try { - long value = parseLongInteger(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") longint value: " + - value); - } - headers.setLongInteger(value, headerField); - } catch (RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - /* Integer-Value */ - case PduHeaders.MESSAGE_COUNT: - case PduHeaders.START: - case PduHeaders.LIMIT: { - try { - long value = parseIntegerValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") integer value: " + - value); - } - headers.setLongInteger(value, headerField); - } catch (RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - /* Text-String */ - case PduHeaders.TRANSACTION_ID: - case PduHeaders.REPLY_CHARGING_ID: - case PduHeaders.AUX_APPLIC_ID: - case PduHeaders.APPLIC_ID: - case PduHeaders.REPLY_APPLIC_ID: - /** - * The next three header fields are email addresses - * as defined in RFC2822, - * not including the characters "<" and ">" - */ - case PduHeaders.MESSAGE_ID: - case PduHeaders.REPLACE_ID: - case PduHeaders.CANCEL_ID: - /** - * The following field has a different value when - * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. - * For now we ignore this fact, since we do not support these PDUs - */ - case PduHeaders.CONTENT_LOCATION: { - byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != value) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") string value: " - + - new String(value)); - } - headers.setTextString(value, headerField); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - break; - } - - /* Encoded-string-value */ - case PduHeaders.SUBJECT: - case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT: - case PduHeaders.RETRIEVE_TEXT: - case PduHeaders.STATUS_TEXT: - case PduHeaders.STORE_STATUS_TEXT: - /* the next one is not support - * M-Mbox-Delete.conf and M-Delete.conf now */ - case PduHeaders.RESPONSE_TEXT: { - EncodedStringValue value = - parseEncodedStringValue(pduDataStream); - if (null != value) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) - + ") encoded string: " + - value.getString()); - } - headers.setEncodedStringValue(value, headerField); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - /* Addressing model */ - case PduHeaders.BCC: - case PduHeaders.CC: - case PduHeaders.TO: { - EncodedStringValue value = - parseEncodedStringValue(pduDataStream); - if (null != value) { - byte[] address = value.getTextString(); - if (null != address) { - String str = new String(address); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField - + " value: " + str); - } - int endIndex = str.indexOf("/"); - if (endIndex > 0) { - str = str.substring(0, endIndex); - } - try { - value.setTextString(str.getBytes()); - } catch (NullPointerException e) { - log("null pointer error!"); - return null; - } - } - - try { - headers.appendEncodedStringValue(value, headerField); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - /* Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) */ - case PduHeaders.DELIVERY_TIME: - case PduHeaders.EXPIRY: - case PduHeaders.REPLY_CHARGING_DEADLINE: { - /* parse Value-length */ - parseValueLength(pduDataStream); - - /* Absolute-token or Relative-token */ - int token = extractByteValue(pduDataStream); - - /* Date-value or Delta-seconds-value */ - long timeValue; - try { - timeValue = parseLongInteger(pduDataStream); - } catch (RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - if (PduHeaders.VALUE_RELATIVE_TOKEN == token) { - /* need to convert the Delta-seconds-value - * into Date-value */ - timeValue = System.currentTimeMillis() / 1000 + timeValue; - } - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") time value: " + - timeValue); - } - headers.setLongInteger(timeValue, headerField); - } catch (RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - case PduHeaders.FROM: { - /* From-value = - * Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - */ - EncodedStringValue from = null; - parseValueLength(pduDataStream); /* parse value-length */ - - /* Address-present-token or Insert-address-token */ - int fromToken = extractByteValue(pduDataStream); - - /* Address-present-token or Insert-address-token */ - if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) { - /* Encoded-string-value */ - from = parseEncodedStringValue(pduDataStream); - if (null != from) { - byte[] address = from.getTextString(); - if (null != address) { - String str = new String(address); - int endIndex = str.indexOf("/"); - if (endIndex > 0) { - str = str.substring(0, endIndex); - } - try { - from.setTextString(str.getBytes()); - } catch (NullPointerException e) { - log("null pointer error!"); - return null; - } - } - } - } else { - try { - from = new EncodedStringValue( - PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()); - } catch (NullPointerException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") from address: " + - from.getString()); - } - headers.setEncodedStringValue(from, PduHeaders.FROM); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - break; - } - - case PduHeaders.MESSAGE_CLASS: { - /* Message-class-value = Class-identifier | Token-text */ - pduDataStream.mark(1); - int messageClass = extractByteValue(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") MESSAGE_CLASS: " + - messageClass); - } - - if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) { - /* Class-identifier */ - try { - if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) { - headers.setTextString( - PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(), - PduHeaders.MESSAGE_CLASS); - } - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } else { - /* Token-text */ - pduDataStream.reset(); - byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != messageClassString) { - try { - headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - } - break; - } - - case PduHeaders.MMS_VERSION: { - int version = parseShortInteger(pduDataStream); - - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") MMS_VERSION: " + - version); - } - headers.setOctet(version, PduHeaders.MMS_VERSION); - } catch (InvalidHeaderValueException e) { - log("Set invalid Octet value: " + version + - " into the header filed: " + headerField); - return null; - } catch (RuntimeException e) { - log(headerField + "is not Octet header field!"); - return null; - } - break; - } - - case PduHeaders.PREVIOUSLY_SENT_BY: { - /* Previously-sent-by-value = - * Value-length Forwarded-count-value Encoded-string-value */ - /* parse value-length */ - parseValueLength(pduDataStream); - - /* parse Forwarded-count-value */ - try { - parseIntegerValue(pduDataStream); - } catch (RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* parse Encoded-string-value */ - EncodedStringValue previouslySentBy = - parseEncodedStringValue(pduDataStream); - if (null != previouslySentBy) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + - ") PREVIOUSLY_SENT_BY: " + previouslySentBy.getString()); - } - headers.setEncodedStringValue(previouslySentBy, - PduHeaders.PREVIOUSLY_SENT_BY); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Encoded-String-Value header field!"); - return null; - } - } - break; - } - - case PduHeaders.PREVIOUSLY_SENT_DATE: { - /* Previously-sent-date-value = - * Value-length Forwarded-count-value Date-value */ - /* parse value-length */ - parseValueLength(pduDataStream); - - /* parse Forwarded-count-value */ - try { - parseIntegerValue(pduDataStream); - } catch (RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* Date-value */ - try { - long previouslySentDate = parseLongInteger(pduDataStream); - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + - ") PREVIOUSLY_SENT_DATE: " + previouslySentDate); - } - headers.setLongInteger(previouslySentDate, - PduHeaders.PREVIOUSLY_SENT_DATE); - } catch (RuntimeException e) { - log(headerField + "is not Long-Integer header field!"); - return null; - } - break; - } - - case PduHeaders.MM_FLAGS: { - /* MM-flags-value = - * Value-length - * ( Add-token | Remove-token | Filter-token ) - * Encoded-string-value - */ - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") MM_FLAGS: " + - " NOT REALLY SUPPORTED"); - } - - /* parse Value-length */ - parseValueLength(pduDataStream); - - /* Add-token | Remove-token | Filter-token */ - extractByteValue(pduDataStream); - - /* Encoded-string-value */ - parseEncodedStringValue(pduDataStream); - - /* not store this header filed in "headers", - * because now PduHeaders doesn't support it */ - break; - } - - /* Value-length - * (Message-total-token | Size-total-token) Integer-Value */ - case PduHeaders.MBOX_TOTALS: - case PduHeaders.MBOX_QUOTAS: { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") MBOX_"); - } - /* Value-length */ - parseValueLength(pduDataStream); - - /* Message-total-token | Size-total-token */ - extractByteValue(pduDataStream); - - /*Integer-Value*/ - try { - parseIntegerValue(pduDataStream); - } catch (RuntimeException e) { - log(headerField + " is not Integer-Value"); - return null; - } - - /* not store these headers filed in "headers", - because now PduHeaders doesn't support them */ - break; - } - - case PduHeaders.ELEMENT_DESCRIPTOR: { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") ELEMENT_DESCRIPTOR"); - } - parseContentType(pduDataStream, null); - - /* not store this header filed in "headers", - because now PduHeaders doesn't support it */ - break; - } - - case PduHeaders.CONTENT_TYPE: { - SparseArray map = new SparseArray(); - byte[] contentType = - parseContentType(pduDataStream, map); - - if (null != contentType) { - try { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField + - " (" + Integer.toHexString(headerField) + ") CONTENT_TYPE: " - + Arrays.toString(contentType)); - } - headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); - } catch (NullPointerException e) { - log("null pointer error!"); - } catch (RuntimeException e) { - log(headerField + "is not Text-String header field!"); - return null; - } - } - - /* get start parameter */ - mStartParam = (byte[]) map.get(PduPart.P_START); - - /* get charset parameter */ - mTypeParam = (byte[]) map.get(PduPart.P_TYPE); - - keepParsing = false; - break; - } - - case PduHeaders.CONTENT: - case PduHeaders.ADDITIONAL_HEADERS: - case PduHeaders.ATTRIBUTES: - default: { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField + - " (" + Integer.toHexString(headerField) + ")"); - } - log("Unknown header"); - } - } - } - - return headers; - } - - /** - * Parse pdu parts. - * - * @param pduDataStream pdu data input stream - * @return parts in PduBody structure - */ - protected PduBody parseParts(ByteArrayInputStream pduDataStream) { - if (pduDataStream == null) { - return null; - } - - int count = parseUnsignedInt(pduDataStream); // get the number of parts - PduBody body = new PduBody(); - - for (int i = 0; i < count; i++) { - int headerLength = parseUnsignedInt(pduDataStream); - int dataLength = parseUnsignedInt(pduDataStream); - PduPart part = new PduPart(); - int startPos = pduDataStream.available(); - if (startPos <= 0) { - // Invalid part. - return null; - } - - /* parse part's content-type */ - SparseArray map = new SparseArray(); - byte[] contentType = parseContentType(pduDataStream, map); - if (null != contentType) { - part.setContentType(contentType); - } else { - part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*" - } - - /* get name parameter */ - byte[] name = (byte[]) map.get(PduPart.P_NAME); - if (null != name) { - part.setName(name); - } - - /* get charset parameter */ - Integer charset = (Integer) map.get(PduPart.P_CHARSET); - if (null != charset) { - part.setCharset(charset); - } - - /* parse part's headers */ - int endPos = pduDataStream.available(); - int partHeaderLen = headerLength - (startPos - endPos); - if (partHeaderLen > 0) { - if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) { - // Parse part header faild. - return null; - } - } else if (partHeaderLen < 0) { - // Invalid length of content-type. - return null; - } - - /* TODO: check content-id, name, filename and content location, - * if not set anyone of them, generate a default content-location - */ - if ((null == part.getContentLocation()) - && (null == part.getName()) - && (null == part.getFilename()) - && (null == part.getContentId())) { - part.setContentLocation(Long.toOctalString( - System.currentTimeMillis()).getBytes()); - } - - /* get part's data */ - if (dataLength > 0) { - byte[] partData = new byte[dataLength]; - String partContentType = new String(part.getContentType()); - pduDataStream.read(partData, 0, dataLength); - if (partContentType.equalsIgnoreCase(ContentType.MMS_MULTIPART_ALTERNATIVE)) { - // parse "multipart/vnd.wap.multipart.alternative". - PduBody childBody = parseParts(new ByteArrayInputStream(partData)); - // take the first part of children. - part = childBody.getPart(0); - } else { - // Check Content-Transfer-Encoding. - byte[] partDataEncoding = part.getContentTransferEncoding(); - if (null != partDataEncoding) { - String encoding = new String(partDataEncoding); - if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { - // Decode "base64" into "binary". - partData = Base64.decodeBase64(partData); - } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { - // Decode "quoted-printable" into "binary". - partData = QuotedPrintable.decodeQuotedPrintable(partData); - } else { - // "binary" is the default encoding. - } - } - if (null == partData) { - log("Decode part data error!"); - return null; - } - part.setData(partData); - } - } - - /* add this part to body */ - if (THE_FIRST_PART == checkPartPosition(part)) { - /* this is the first part */ - body.addPart(0, part); - } else { - /* add the part to the end */ - body.addPart(part); - } - } - - return body; - } - - /** - * Log status. - * - * @param text log information - */ - private static void log(String text) { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, text); - } - } - - /** - * Parse unsigned integer. - * - * @param pduDataStream pdu data input stream - * @return the integer, -1 when failed - */ - protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * The maximum size of a uintvar is 32 bits. - * So it will be encoded in no more than 5 octets. - */ - assert (null != pduDataStream); - int result = 0; - int temp = pduDataStream.read(); - if (temp == -1) { - return temp; - } - - while ((temp & 0x80) != 0) { - result = result << 7; - result |= temp & 0x7F; - temp = pduDataStream.read(); - if (temp == -1) { - return temp; - } - } - - result = result << 7; - result |= temp & 0x7F; - - return result; - } - - /** - * Parse value length. - * - * @param pduDataStream pdu data input stream - * @return the integer - */ - protected static int parseValueLength(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Value-length = Short-length | (Length-quote Length) - * Short-length = - * Length-quote = - * Length = Uintvar-integer - * Uintvar-integer = 1*5 OCTET - */ - assert (null != pduDataStream); - int temp = pduDataStream.read(); - assert (-1 != temp); - int first = temp & 0xFF; - - if (first <= SHORT_LENGTH_MAX) { - return first; - } else if (first == LENGTH_QUOTE) { - return parseUnsignedInt(pduDataStream); - } - - throw new RuntimeException("Value length > LENGTH_QUOTE!"); - } - - /** - * Parse encoded string value. - * - * @param pduDataStream pdu data input stream - * @return the EncodedStringValue - */ - protected static EncodedStringValue parseEncodedStringValue( - ByteArrayInputStream pduDataStream) { - /** - * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf - * Encoded-string-value = Text-string | Value-length Char-set Text-string - */ - assert (null != pduDataStream); - pduDataStream.mark(1); - EncodedStringValue returnValue = null; - int charset = 0; - int temp = pduDataStream.read(); - assert (-1 != temp); - int first = temp & 0xFF; - if (first == 0) { - return null; // Blank subject, bail. - } - - pduDataStream.reset(); - if (first < TEXT_MIN) { - parseValueLength(pduDataStream); - - charset = parseShortInteger(pduDataStream); //get the "Charset" - } - - byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING); - - try { - if (0 != charset) { - returnValue = new EncodedStringValue(charset, textString); - } else { - returnValue = new EncodedStringValue(textString); - } - } catch (Exception e) { - return null; - } - - return returnValue; - } - - /** - * Parse Text-String or Quoted-String. - * - * @param pduDataStream pdu data input stream - * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING - * @return the string without End-of-string in byte array - */ - protected static byte[] parseWapString(ByteArrayInputStream pduDataStream, - int stringType) { - assert (null != pduDataStream); - /** - * From wap-230-wsp-20010705-a.pdf - * Text-string = [Quote] *TEXT End-of-string - * If the first character in the TEXT is in the range of 128-255, - * a Quote character must precede it. - * Otherwise the Quote character must be omitted. - * The Quote is not part of the contents. - * Quote = - * End-of-string = - * - * Quoted-string = *TEXT End-of-string - * - * Token-text = Token End-of-string - */ - - // Mark supposed beginning of Text-string - // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG - pduDataStream.mark(1); - - // Check first char - int temp = pduDataStream.read(); - assert (-1 != temp); - if ((TYPE_QUOTED_STRING == stringType) && - (QUOTED_STRING_FLAG == temp)) { - // Mark again if QUOTED_STRING_FLAG and ignore it - pduDataStream.mark(1); - } else if ((TYPE_TEXT_STRING == stringType) && - (QUOTE == temp)) { - // Mark again if QUOTE and ignore it - pduDataStream.mark(1); - } else { - // Otherwise go back to origin - pduDataStream.reset(); - } - - // We are now definitely at the beginning of string - /** - * Return *TOKEN or *TEXT (Text-String without QUOTE, - * Quoted-String without QUOTED_STRING_FLAG and without End-of-string) - */ - return getWapString(pduDataStream, stringType); - } - - /** - * Check TOKEN data defined in RFC2616. - * - * @param ch checking data - * @return true when ch is TOKEN, false when ch is not TOKEN - */ - protected static boolean isTokenCharacter(int ch) { - /** - * Token = 1* - * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64) - * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34) - * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61) - * | "{"(123) | "}"(125) | SP(32) | HT(9) - * CHAR = - * CTL = - * SP = - * HT = - */ - if ((ch < 33) || (ch > 126)) { - return false; - } - - switch (ch) { - case '"': /* '"' */ - case '(': /* '(' */ - case ')': /* ')' */ - case ',': /* ',' */ - case '/': /* '/' */ - case ':': /* ':' */ - case ';': /* ';' */ - case '<': /* '<' */ - case '=': /* '=' */ - case '>': /* '>' */ - case '?': /* '?' */ - case '@': /* '@' */ - case '[': /* '[' */ - case '\\': /* '\' */ - case ']': /* ']' */ - case '{': /* '{' */ - case '}': /* '}' */ - return false; - } - - return true; - } - - /** - * Check TEXT data defined in RFC2616. - * - * @param ch checking data - * @return true when ch is TEXT, false when ch is not TEXT - */ - protected static boolean isText(int ch) { - /** - * TEXT = - * CTL = - * LWS = [CRLF] 1*( SP | HT ) - * CRLF = CR LF - * CR = - * LF = - */ - if (((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) { - return true; - } - - switch (ch) { - case '\t': /* '\t' */ - case '\n': /* '\n' */ - case '\r': /* '\r' */ - return true; - } - - return false; - } - - protected static byte[] getWapString(ByteArrayInputStream pduDataStream, - int stringType) { - assert (null != pduDataStream); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int temp = pduDataStream.read(); - assert (-1 != temp); - while ((-1 != temp) && ('\0' != temp)) { - // check each of the character - if (stringType == TYPE_TOKEN_STRING) { - if (isTokenCharacter(temp)) { - out.write(temp); - } - } else { - if (isText(temp)) { - out.write(temp); - } - } - - temp = pduDataStream.read(); - assert (-1 != temp); - } - - if (out.size() > 0) { - return out.toByteArray(); - } - - return null; - } - - /** - * Extract a byte value from the input stream. - * - * @param pduDataStream pdu data input stream - * @return the byte - */ - protected static int extractByteValue(ByteArrayInputStream pduDataStream) { - assert (null != pduDataStream); - int temp = pduDataStream.read(); - assert (-1 != temp); - return temp & 0xFF; - } - - /** - * Parse Short-Integer. - * - * @param pduDataStream pdu data input stream - * @return the byte - */ - protected static int parseShortInteger(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Short-integer = OCTET - * Integers in range 0-127 shall be encoded as a one - * octet value with the most significant bit set to one (1xxx xxxx) - * and with the value in the remaining least significant bits. - */ - assert (null != pduDataStream); - int temp = pduDataStream.read(); - assert (-1 != temp); - return temp & 0x7F; - } - - /** - * Parse Long-Integer. - * - * @param pduDataStream pdu data input stream - * @return long integer - */ - protected static long parseLongInteger(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Long-integer = Short-length Multi-octet-integer - * The Short-length indicates the length of the Multi-octet-integer - * Multi-octet-integer = 1*30 OCTET - * The content octets shall be an unsigned integer value - * with the most significant octet encoded first (big-endian representation). - * The minimum number of octets must be used to encode the value. - * Short-length = - */ - assert (null != pduDataStream); - int temp = pduDataStream.read(); - assert (-1 != temp); - int count = temp & 0xFF; - - if (count > LONG_INTEGER_LENGTH_MAX) { - throw new RuntimeException("Octet count greater than 8 and I can't represent that!"); - } - - long result = 0; - - for (int i = 0; i < count; i++) { - temp = pduDataStream.read(); - assert (-1 != temp); - result <<= 8; - result += (temp & 0xFF); - } - - return result; - } - - /** - * Parse Integer-Value. - * - * @param pduDataStream pdu data input stream - * @return long integer - */ - protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) { - /** - * From wap-230-wsp-20010705-a.pdf - * Integer-Value = Short-integer | Long-integer - */ - assert (null != pduDataStream); - pduDataStream.mark(1); - int temp = pduDataStream.read(); - assert (-1 != temp); - pduDataStream.reset(); - if (temp > SHORT_INTEGER_MAX) { - return parseShortInteger(pduDataStream); - } else { - return parseLongInteger(pduDataStream); - } - } - - /** - * To skip length of the wap value. - * - * @param pduDataStream pdu data input stream - * @param length area size - * @return the values in this area - */ - protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) { - assert (null != pduDataStream); - byte[] area = new byte[length]; - int readLen = pduDataStream.read(area, 0, length); - if (readLen < length) { //The actually read length is lower than the length - return -1; - } else { - return readLen; - } - } - - /** - * Parse content type parameters. For now we just support - * four parameters used in mms: "type", "start", "name", "charset". - * - * @param pduDataStream pdu data input stream - * @param map to store parameters of Content-Type field - * @param length length of all the parameters - */ - protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream, - SparseArray map, Integer length) { - /** - * From wap-230-wsp-20010705-a.pdf - * Parameter = Typed-parameter | Untyped-parameter - * Typed-parameter = Well-known-parameter-token Typed-value - * the actual expected type of the value is implied by the well-known parameter - * Well-known-parameter-token = Integer-value - * the code values used for parameters are specified in the Assigned Numbers appendix - * Typed-value = Compact-value | Text-value - * In addition to the expected type, there may be no value. - * If the value cannot be encoded using the expected type, it shall be encoded as text. - * Compact-value = Integer-value | - * Date-value | Delta-seconds-value | Q-value | Version-value | - * Uri-value - * Untyped-parameter = Token-text Untyped-value - * the type of the value is unknown, but it shall be encoded as an integer, - * if that is possible. - * Untyped-value = Integer-value | Text-value - */ - assert (null != pduDataStream); - assert (length > 0); - - int startPos = pduDataStream.available(); - int tempPos = 0; - int lastLen = length; - while (0 < lastLen) { - int param = pduDataStream.read(); - assert (-1 != param); - lastLen--; - - switch (param) { - /** - * From rfc2387, chapter 3.1 - * The type parameter must be specified and its value is the MIME media - * type of the "root" body part. It permits a MIME user agent to - * determine the content-type without reference to the enclosed body - * part. If the value of the type parameter and the root body part's - * content-type differ then the User Agent's behavior is undefined. - * - * From wap-230-wsp-20010705-a.pdf - * type = Constrained-encoding - * Constrained-encoding = Extension-Media | Short-integer - * Extension-media = *TEXT End-of-string - */ - case PduPart.P_TYPE: - case PduPart.P_CT_MR_TYPE: - pduDataStream.mark(1); - int first = extractByteValue(pduDataStream); - pduDataStream.reset(); - if (first > TEXT_MAX) { - // Short-integer (well-known type) - int index = parseShortInteger(pduDataStream); - - if (index < PduContentTypes.contentTypes.length) { - byte[] type = (PduContentTypes.contentTypes[index]).getBytes(); - map.put(PduPart.P_TYPE, type); - } else { - //not support this type, ignore it. - } - } else { - // Text-String (extension-media) - byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != type) && (null != map)) { - map.put(PduPart.P_TYPE, type); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3. - * Start Parameter Referring to Presentation - * - * From rfc2387, chapter 3.2 - * The start parameter, if given, is the content-ID of the compound - * object's "root". If not present the "root" is the first body part in - * the Multipart/Related entity. The "root" is the element the - * applications processes first. - * - * From wap-230-wsp-20010705-a.pdf - * start = Text-String - */ - case PduPart.P_START: - case PduPart.P_DEP_START: - byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != start) && (null != map)) { - map.put(PduPart.P_START, start); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf - * In creation, the character set SHALL be either us-ascii - * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode]. - * In retrieval, both us-ascii and utf-8 SHALL be supported. - * - * From wap-230-wsp-20010705-a.pdf - * charset = Well-known-charset|Text-String - * Well-known-charset = Any-charset | Integer-value - * Both are encoded using values from Character Set - * Assignments table in Assigned Numbers - * Any-charset = - * Equivalent to the special RFC2616 charset value "*" - */ - case PduPart.P_CHARSET: - pduDataStream.mark(1); - int firstValue = extractByteValue(pduDataStream); - pduDataStream.reset(); - //Check first char - if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) || - (END_STRING_FLAG == firstValue)) { - //Text-String (extension-charset) - byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING); - try { - int charsetInt = CharacterSets.getMibEnumValue( - new String(charsetStr)); - map.put(PduPart.P_CHARSET, charsetInt); - } catch (UnsupportedEncodingException e) { - // Not a well-known charset, use "*". - Log.e(LOG_TAG, Arrays.toString(charsetStr), e); - map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET); - } - } else { - //Well-known-charset - int charset = (int) parseIntegerValue(pduDataStream); - if (map != null) { - map.put(PduPart.P_CHARSET, charset); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - - /** - * From oma-ts-mms-conf-v1_3.pdf - * A name for multipart object SHALL be encoded using name-parameter - * for Content-Type header in WSP multipart headers. - * - * From wap-230-wsp-20010705-a.pdf - * name = Text-String - */ - case PduPart.P_DEP_NAME: - case PduPart.P_NAME: - byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if ((null != name) && (null != map)) { - map.put(PduPart.P_NAME, name); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - default: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Content-Type parameter"); - } - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Content-Type"); - } else { - lastLen = 0; - } - break; - } - } - - if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Content-Type"); - } - } - - /** - * Parse content type. - * - * @param pduDataStream pdu data input stream - * @param map to store parameters in Content-Type header field - * @return Content-Type value - */ - protected static byte[] parseContentType(ByteArrayInputStream pduDataStream, - SparseArray map) { - /** - * From wap-230-wsp-20010705-a.pdf - * Content-type-value = Constrained-media | Content-general-form - * Content-general-form = Value-length Media-type - * Media-type = (Well-known-media | Extension-Media) *(Parameter) - */ - assert (null != pduDataStream); - - byte[] contentType = null; - pduDataStream.mark(1); - int temp = pduDataStream.read(); - assert (-1 != temp); - pduDataStream.reset(); - - int cur = (temp & 0xFF); - - if (cur < TEXT_MIN) { - int length = parseValueLength(pduDataStream); - int startPos = pduDataStream.available(); - pduDataStream.mark(1); - temp = pduDataStream.read(); - assert (-1 != temp); - pduDataStream.reset(); - int first = (temp & 0xFF); - - if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) { - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } else if (first > TEXT_MAX) { - int index = parseShortInteger(pduDataStream); - - if (index < PduContentTypes.contentTypes.length) { //well-known type - contentType = (PduContentTypes.contentTypes[index]).getBytes(); - } else { - pduDataStream.reset(); - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } - } else { - Log.e(LOG_TAG, "Corrupt content-type"); - return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" - } - - int endPos = pduDataStream.available(); - int parameterLen = length - (startPos - endPos); - if (parameterLen > 0) {//have parameters - parseContentTypeParams(pduDataStream, map, parameterLen); - } - - if (parameterLen < 0) { - Log.e(LOG_TAG, "Corrupt MMS message"); - return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" - } - } else if (cur <= TEXT_MAX) { - contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); - } else { - contentType = - (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes(); - } - - return contentType; - } - - /** - * Parse part's headers. - * - * @param pduDataStream pdu data input stream - * @param part to store the header informations of the part - * @param length length of the headers - * @return true if parse successfully, false otherwise - */ - protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream, - PduPart part, int length) { - assert (null != pduDataStream); - assert (null != part); - assert (length > 0); - - /** - * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2. - * A name for multipart object SHALL be encoded using name-parameter - * for Content-Type header in WSP multipart headers. - * In decoding, name-parameter of Content-Type SHALL be used if available. - * If name-parameter of Content-Type is not available, - * filename parameter of Content-Disposition header SHALL be used if available. - * If neither name-parameter of Content-Type header nor filename parameter - * of Content-Disposition header is available, - * Content-Location header SHALL be used if available. - * - * Within SMIL part the reference to the media object parts SHALL use - * either Content-ID or Content-Location mechanism [RFC2557] - * and the corresponding WSP part headers in media object parts - * contain the corresponding definitions. - */ - int startPos = pduDataStream.available(); - int tempPos = 0; - int lastLen = length; - while (0 < lastLen) { - int header = pduDataStream.read(); - assert (-1 != header); - lastLen--; - - if (header > TEXT_MAX) { - // Number assigned headers. - switch (header) { - case PduPart.P_CONTENT_LOCATION: - /** - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-location-value = Uri-value - */ - byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING); - if (null != contentLocation) { - part.setContentLocation(contentLocation); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - case PduPart.P_CONTENT_ID: - /** - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-ID-value = Quoted-string - */ - byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING); - if (null != contentId) { - part.setContentId(contentId); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - break; - case PduPart.P_DEP_CONTENT_DISPOSITION: - case PduPart.P_CONTENT_DISPOSITION: - /* - * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 - * Content-disposition-value = Value-length Disposition *(Parameter) - * Disposition = Form-data | Attachment | Inline | Token-text - * Form-data = - * Attachment = - * Inline = - * - * some carrier mmsc servers do not support content_disposition - * field correctly - */ - if (mParseContentDisposition) { - int len = parseValueLength(pduDataStream); - pduDataStream.mark(1); - int thisStartPos = pduDataStream.available(); - int thisEndPos = 0; - int value = pduDataStream.read(); - - if (value == PduPart.P_DISPOSITION_FROM_DATA) { - part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA); - } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) { - part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT); - } else if (value == PduPart.P_DISPOSITION_INLINE) { - part.setContentDisposition(PduPart.DISPOSITION_INLINE); - } else { - pduDataStream.reset(); - /* Token-text */ - part.setContentDisposition(parseWapString(pduDataStream - , TYPE_TEXT_STRING)); - } - - /* get filename parameter and skip other parameters */ - thisEndPos = pduDataStream.available(); - if (thisStartPos - thisEndPos < len) { - value = pduDataStream.read(); - if (value == PduPart.P_FILENAME) { //filename is text-string - part.setFilename(parseWapString(pduDataStream - , TYPE_TEXT_STRING)); - } - - /* skip other parameters */ - thisEndPos = pduDataStream.available(); - if (thisStartPos - thisEndPos < len) { - int last = len - (thisStartPos - thisEndPos); - byte[] temp = new byte[last]; - pduDataStream.read(temp, 0, last); - } - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - } - break; - default: - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); - } - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - lastLen = 0; - break; - } - } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) { - // Not assigned header. - byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING); - byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING); - - // Check the header whether it is "Content-Transfer-Encoding". - if (true == - PduPart.CONTENT_TRANSFER_ENCODING - .equalsIgnoreCase(new String(tempHeader))) { - part.setContentTransferEncoding(tempValue); - } - - tempPos = pduDataStream.available(); - lastLen = length - (startPos - tempPos); - } else { - if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); - } - // Skip all headers of this part. - if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - lastLen = 0; - } - } - - if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Part headers"); - return false; - } - - return true; - } - - /** - * Check the position of a specified part. - * - * @param part the part to be checked - * @return part position, THE_FIRST_PART when it's the - * first one, THE_LAST_PART when it's the last one. - */ - private static int checkPartPosition(PduPart part) { - assert (null != part); - if ((null == mTypeParam) && - (null == mStartParam)) { - return THE_LAST_PART; - } - - /* check part's content-id */ - if (null != mStartParam) { - byte[] contentId = part.getContentId(); - if (null != contentId) { - if (true == Arrays.equals(mStartParam, contentId)) { - return THE_FIRST_PART; - } - } - // This is not the first part, so append to end (keeping the original order) - // Check b/19607294 for details of this change - return THE_LAST_PART; - } - - /* check part's content-type */ - if (null != mTypeParam) { - byte[] contentType = part.getContentType(); - if (null != contentType) { - if (true == Arrays.equals(mTypeParam, contentType)) { - return THE_FIRST_PART; - } - } - } - - return THE_LAST_PART; - } - - /** - * Check mandatory headers of a pdu. - * - * @param headers pdu headers - * @return true if the pdu has all of the mandatory headers, false otherwise. - */ - protected static boolean checkMandatoryHeader(PduHeaders headers) { - if (null == headers) { - return false; - } - - /* get message type */ - int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE); - - /* check Mms-Version field */ - int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION); - if (0 == mmsVersion) { - // Every message should have Mms-Version field. - return false; - } - - /* check mandatory header fields */ - switch (messageType) { - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - // Content-Type field. - byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); - if (null == srContentType) { - return false; - } - - // From field. - EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == srFrom) { - return false; - } - - // Transaction-Id field. - byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == srTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_SEND_CONF: - // Response-Status field. - int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS); - if (0 == scResponseStatus) { - return false; - } - - // Transaction-Id field. - byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == scTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - // Content-Location field. - byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION); - if (null == niContentLocation) { - return false; - } - - // Expiry field. - long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY); - if (-1 == niExpiry) { - return false; - } - - // Message-Class field. - byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS); - if (null == niMessageClass) { - return false; - } - - // Message-Size field. - long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE); - if (-1 == niMessageSize) { - return false; - } - - // Transaction-Id field. - byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == niTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - // Status field. - int nriStatus = headers.getOctet(PduHeaders.STATUS); - if (0 == nriStatus) { - return false; - } - - // Transaction-Id field. - byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == nriTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - // Content-Type field. - byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); - if (null == rcContentType) { - return false; - } - - // Date field. - long rcDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == rcDate) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - // Date field. - long diDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == diDate) { - return false; - } - - // Message-Id field. - byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == diMessageId) { - return false; - } - - // Status field. - int diStatus = headers.getOctet(PduHeaders.STATUS); - if (0 == diStatus) { - return false; - } - - // To field. - EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == diTo) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - // Transaction-Id field. - byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); - if (null == aiTransactionId) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: - // Date field. - long roDate = headers.getLongInteger(PduHeaders.DATE); - if (-1 == roDate) { - return false; - } - - // From field. - EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == roFrom) { - return false; - } - - // Message-Id field. - byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == roMessageId) { - return false; - } - - // Read-Status field. - int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS); - if (0 == roReadStatus) { - return false; - } - - // To field. - EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == roTo) { - return false; - } - - break; - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - // From field. - EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM); - if (null == rrFrom) { - return false; - } - - // Message-Id field. - byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); - if (null == rrMessageId) { - return false; - } - - // Read-Status field. - int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS); - if (0 == rrReadStatus) { - return false; - } - - // To field. - EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO); - if (null == rrTo) { - return false; - } - - break; - default: - // Parser doesn't support this message type in this version. - return false; - } - - return true; - } -} diff --git a/src/com/android/messaging/mmslib/pdu/PduPart.java b/src/com/android/messaging/mmslib/pdu/PduPart.java deleted file mode 100644 index dcdb7a6..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduPart.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.net.Uri; -import android.util.SparseArray; - -/** - * The pdu part. - */ -public class PduPart { - /** - * Well-Known Parameters. - */ - public static final int P_Q = 0x80; - public static final int P_CHARSET = 0x81; - public static final int P_LEVEL = 0x82; - public static final int P_TYPE = 0x83; - public static final int P_DEP_NAME = 0x85; - public static final int P_DEP_FILENAME = 0x86; - public static final int P_DIFFERENCES = 0x87; - public static final int P_PADDING = 0x88; - // This value of "TYPE" s used with Content-Type: multipart/related - public static final int P_CT_MR_TYPE = 0x89; - public static final int P_DEP_START = 0x8A; - public static final int P_DEP_START_INFO = 0x8B; - public static final int P_DEP_COMMENT = 0x8C; - public static final int P_DEP_DOMAIN = 0x8D; - public static final int P_MAX_AGE = 0x8E; - public static final int P_DEP_PATH = 0x8F; - public static final int P_SECURE = 0x90; - public static final int P_SEC = 0x91; - public static final int P_MAC = 0x92; - public static final int P_CREATION_DATE = 0x93; - public static final int P_MODIFICATION_DATE = 0x94; - public static final int P_READ_DATE = 0x95; - public static final int P_SIZE = 0x96; - public static final int P_NAME = 0x97; - public static final int P_FILENAME = 0x98; - public static final int P_START = 0x99; - public static final int P_START_INFO = 0x9A; - public static final int P_COMMENT = 0x9B; - public static final int P_DOMAIN = 0x9C; - public static final int P_PATH = 0x9D; - - /** - * Header field names. - */ - public static final int P_CONTENT_TYPE = 0x91; - public static final int P_CONTENT_LOCATION = 0x8E; - public static final int P_CONTENT_ID = 0xC0; - public static final int P_DEP_CONTENT_DISPOSITION = 0xAE; - public static final int P_CONTENT_DISPOSITION = 0xC5; - // The next header is unassigned header, use reserved header(0x48) value. - public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8; - - /** - * Content=Transfer-Encoding string. - */ - public static final String CONTENT_TRANSFER_ENCODING = - "Content-Transfer-Encoding"; - - /** - * Value of Content-Transfer-Encoding. - */ - public static final String P_BINARY = "binary"; - public static final String P_7BIT = "7bit"; - public static final String P_8BIT = "8bit"; - public static final String P_BASE64 = "base64"; - public static final String P_QUOTED_PRINTABLE = "quoted-printable"; - - /** - * Value of disposition can be set to PduPart when the value is octet in - * the PDU. - * "from-data" instead of Form-data. - * "attachment" instead of Attachment. - * "inline" instead of Inline. - */ - static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes(); - static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes(); - static final byte[] DISPOSITION_INLINE = "inline".getBytes(); - - /** - * Content-Disposition value. - */ - public static final int P_DISPOSITION_FROM_DATA = 0x80; - public static final int P_DISPOSITION_ATTACHMENT = 0x81; - public static final int P_DISPOSITION_INLINE = 0x82; - - /** - * Header of part. - */ - private SparseArray mPartHeader = null; - - /** - * Data uri. - */ - private Uri mUri = null; - - /** - * Part data. - */ - private byte[] mPartData = null; - - private static final String TAG = "PduPart"; - - /** - * Empty Constructor. - */ - public PduPart() { - mPartHeader = new SparseArray(); - } - - /** - * Set part data. The data are stored as byte array. - * - * @param data the data - */ - public void setData(final byte[] data) { - mPartData = data; - } - - /** - * @return The part data or null if the data wasn't set or - * the data is stored as Uri. - * @see #getDataUri - */ - public byte[] getData() { - return mPartData; - } - - /** - * Set data uri. The data are stored as Uri. - * - * @param uri the uri - */ - public void setDataUri(final Uri uri) { - mUri = uri; - } - - /** - * @return The Uri of the part data or null if the data wasn't set or - * the data is stored as byte array. - * @see #getData - */ - public Uri getDataUri() { - return mUri; - } - - /** - * Set Content-id value - * - * @param contentId the content-id value - * @throws NullPointerException if the value is null. - */ - public void setContentId(final byte[] contentId) { - if ((contentId == null) || (contentId.length == 0)) { - throw new IllegalArgumentException( - "Content-Id may not be null or empty."); - } - - if ((contentId.length > 1) - && ((char) contentId[0] == '<') - && ((char) contentId[contentId.length - 1] == '>')) { - mPartHeader.put(P_CONTENT_ID, contentId); - return; - } - - // Insert beginning '<' and trailing '>' for Content-Id. - final byte[] buffer = new byte[contentId.length + 2]; - buffer[0] = (byte) (0xff & '<'); - buffer[buffer.length - 1] = (byte) (0xff & '>'); - System.arraycopy(contentId, 0, buffer, 1, contentId.length); - mPartHeader.put(P_CONTENT_ID, buffer); - } - - /** - * Get Content-id value. - * - * @return the value - */ - public byte[] getContentId() { - return (byte[]) mPartHeader.get(P_CONTENT_ID); - } - - /** - * Set Char-set value. - * - * @param charset the value - */ - public void setCharset(final int charset) { - mPartHeader.put(P_CHARSET, charset); - } - - /** - * Get Char-set value - * - * @return the charset value. Return 0 if charset was not set. - */ - public int getCharset() { - final Integer charset = (Integer) mPartHeader.get(P_CHARSET); - if (charset == null) { - return 0; - } else { - return charset.intValue(); - } - } - - /** - * Set Content-Location value. - * - * @param contentLocation the value - * @throws NullPointerException if the value is null. - */ - public void setContentLocation(final byte[] contentLocation) { - if (contentLocation == null) { - throw new NullPointerException("null content-location"); - } - - mPartHeader.put(P_CONTENT_LOCATION, contentLocation); - } - - /** - * Get Content-Location value. - * - * @return the value - * return PduPart.disposition[0] instead of (Form-data). - * return PduPart.disposition[1] instead of (Attachment). - * return PduPart.disposition[2] instead of (Inline). - */ - public byte[] getContentLocation() { - return (byte[]) mPartHeader.get(P_CONTENT_LOCATION); - } - - /** - * Set Content-Disposition value. - * Use PduPart.disposition[0] instead of (Form-data). - * Use PduPart.disposition[1] instead of (Attachment). - * Use PduPart.disposition[2] instead of (Inline). - * - * @param contentDisposition the value - * @throws NullPointerException if the value is null. - */ - public void setContentDisposition(final byte[] contentDisposition) { - if (contentDisposition == null) { - throw new NullPointerException("null content-disposition"); - } - - mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition); - } - - /** - * Get Content-Disposition value. - * - * @return the value - */ - public byte[] getContentDisposition() { - return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION); - } - - /** - * Set Content-Type value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setContentType(final byte[] contentType) { - if (contentType == null) { - throw new NullPointerException("null content-type"); - } - - mPartHeader.put(P_CONTENT_TYPE, contentType); - } - - /** - * Get Content-Type value of part. - * - * @return the value - */ - public byte[] getContentType() { - return (byte[]) mPartHeader.get(P_CONTENT_TYPE); - } - - /** - * Set Content-Transfer-Encoding value - * - * @param contentId the content-id value - * @throws NullPointerException if the value is null. - */ - public void setContentTransferEncoding(final byte[] contentTransferEncoding) { - if (contentTransferEncoding == null) { - throw new NullPointerException("null content-transfer-encoding"); - } - - mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding); - } - - /** - * Get Content-Transfer-Encoding value. - * - * @return the value - */ - public byte[] getContentTransferEncoding() { - return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING); - } - - /** - * Set Content-type parameter: name. - * - * @param name the name value - * @throws NullPointerException if the value is null. - */ - public void setName(final byte[] name) { - if (null == name) { - throw new NullPointerException("null content-id"); - } - - mPartHeader.put(P_NAME, name); - } - - /** - * Get content-type parameter: name. - * - * @return the name - */ - public byte[] getName() { - return (byte[]) mPartHeader.get(P_NAME); - } - - /** - * Get Content-disposition parameter: filename - * - * @param fileName the filename value - * @throws NullPointerException if the value is null. - */ - public void setFilename(final byte[] fileName) { - if (null == fileName) { - throw new NullPointerException("null content-id"); - } - - mPartHeader.put(P_FILENAME, fileName); - } - - /** - * Set Content-disposition parameter: filename - * - * @return the filename - */ - public byte[] getFilename() { - return (byte[]) mPartHeader.get(P_FILENAME); - } - - public String generateLocation() { - // Assumption: At least one of the content-location / name / filename - // or content-id should be set. This is guaranteed by the PduParser - // for incoming messages and by MM composer for outgoing messages. - byte[] location = (byte[]) mPartHeader.get(P_NAME); - if (null == location) { - location = (byte[]) mPartHeader.get(P_FILENAME); - - if (null == location) { - location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION); - } - } - - if (null == location) { - final byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID); - return "cid:" + new String(contentId); - } else { - return new String(location); - } - } -} - diff --git a/src/com/android/messaging/mmslib/pdu/PduPersister.java b/src/com/android/messaging/mmslib/pdu/PduPersister.java deleted file mode 100644 index ceb6a85..0000000 --- a/src/com/android/messaging/mmslib/pdu/PduPersister.java +++ /dev/null @@ -1,1683 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteException; -import android.net.Uri; -import android.provider.MediaStore; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Mms.Addr; -import android.provider.Telephony.Mms.Part; -import android.provider.Telephony.MmsSms; -import android.provider.Telephony.MmsSms.PendingMessages; -import android.support.v4.util.ArrayMap; -import android.support.v4.util.SimpleArrayMap; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseIntArray; - -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.InvalidHeaderValueException; -import com.android.messaging.mmslib.MmsException; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.mmslib.util.DownloadDrmHelper; -import com.android.messaging.mmslib.util.DrmConvertSession; -import com.android.messaging.mmslib.util.PduCache; -import com.android.messaging.mmslib.util.PduCacheEntry; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Map; - -/** - * This class is the high-level manager of PDU storage. - */ -public class PduPersister { - private static final String TAG = "PduPersister"; - private static final boolean LOCAL_LOGV = false; - - /** - * The uri of temporary drm objects. - */ - public static final String TEMPORARY_DRM_OBJECT_URI = - "content://mms/" + Long.MAX_VALUE + "/part"; - - /** - * Indicate that we transiently failed to process a MM. - */ - public static final int PROC_STATUS_TRANSIENT_FAILURE = 1; - - /** - * Indicate that we permanently failed to process a MM. - */ - public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2; - - /** - * Indicate that we have successfully processed a MM. - */ - public static final int PROC_STATUS_COMPLETED = 3; - - public static final String BEGIN_VCARD = "BEGIN:VCARD"; - - private static PduPersister sPersister; - - private static final PduCache PDU_CACHE_INSTANCE; - - private static final int[] ADDRESS_FIELDS = new int[]{ - PduHeaders.BCC, - PduHeaders.CC, - PduHeaders.FROM, - PduHeaders.TO - }; - - public static final String[] PDU_PROJECTION = new String[]{ - Mms._ID, - Mms.MESSAGE_BOX, - Mms.THREAD_ID, - Mms.RETRIEVE_TEXT, - Mms.SUBJECT, - Mms.CONTENT_LOCATION, - Mms.CONTENT_TYPE, - Mms.MESSAGE_CLASS, - Mms.MESSAGE_ID, - Mms.RESPONSE_TEXT, - Mms.TRANSACTION_ID, - Mms.CONTENT_CLASS, - Mms.DELIVERY_REPORT, - Mms.MESSAGE_TYPE, - Mms.MMS_VERSION, - Mms.PRIORITY, - Mms.READ_REPORT, - Mms.READ_STATUS, - Mms.REPORT_ALLOWED, - Mms.RETRIEVE_STATUS, - Mms.STATUS, - Mms.DATE, - Mms.DELIVERY_TIME, - Mms.EXPIRY, - Mms.MESSAGE_SIZE, - Mms.SUBJECT_CHARSET, - Mms.RETRIEVE_TEXT_CHARSET, - Mms.READ, - Mms.SEEN, - }; - - public static final int PDU_COLUMN_ID = 0; - public static final int PDU_COLUMN_MESSAGE_BOX = 1; - public static final int PDU_COLUMN_THREAD_ID = 2; - public static final int PDU_COLUMN_RETRIEVE_TEXT = 3; - public static final int PDU_COLUMN_SUBJECT = 4; - public static final int PDU_COLUMN_CONTENT_LOCATION = 5; - public static final int PDU_COLUMN_CONTENT_TYPE = 6; - public static final int PDU_COLUMN_MESSAGE_CLASS = 7; - public static final int PDU_COLUMN_MESSAGE_ID = 8; - public static final int PDU_COLUMN_RESPONSE_TEXT = 9; - public static final int PDU_COLUMN_TRANSACTION_ID = 10; - public static final int PDU_COLUMN_CONTENT_CLASS = 11; - public static final int PDU_COLUMN_DELIVERY_REPORT = 12; - public static final int PDU_COLUMN_MESSAGE_TYPE = 13; - public static final int PDU_COLUMN_MMS_VERSION = 14; - public static final int PDU_COLUMN_PRIORITY = 15; - public static final int PDU_COLUMN_READ_REPORT = 16; - public static final int PDU_COLUMN_READ_STATUS = 17; - public static final int PDU_COLUMN_REPORT_ALLOWED = 18; - public static final int PDU_COLUMN_RETRIEVE_STATUS = 19; - public static final int PDU_COLUMN_STATUS = 20; - public static final int PDU_COLUMN_DATE = 21; - public static final int PDU_COLUMN_DELIVERY_TIME = 22; - public static final int PDU_COLUMN_EXPIRY = 23; - public static final int PDU_COLUMN_MESSAGE_SIZE = 24; - public static final int PDU_COLUMN_SUBJECT_CHARSET = 25; - public static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26; - public static final int PDU_COLUMN_READ = 27; - public static final int PDU_COLUMN_SEEN = 28; - - private static final String[] PART_PROJECTION = new String[] { - Part._ID, - Part.CHARSET, - Part.CONTENT_DISPOSITION, - Part.CONTENT_ID, - Part.CONTENT_LOCATION, - Part.CONTENT_TYPE, - Part.FILENAME, - Part.NAME, - Part.TEXT - }; - - private static final int PART_COLUMN_ID = 0; - private static final int PART_COLUMN_CHARSET = 1; - private static final int PART_COLUMN_CONTENT_DISPOSITION = 2; - private static final int PART_COLUMN_CONTENT_ID = 3; - private static final int PART_COLUMN_CONTENT_LOCATION = 4; - private static final int PART_COLUMN_CONTENT_TYPE = 5; - private static final int PART_COLUMN_FILENAME = 6; - private static final int PART_COLUMN_NAME = 7; - private static final int PART_COLUMN_TEXT = 8; - - private static final SimpleArrayMap MESSAGE_BOX_MAP; - - // These map are used for convenience in persist() and load(). - private static final SparseIntArray CHARSET_COLUMN_INDEX_MAP; - - private static final SparseIntArray ENCODED_STRING_COLUMN_INDEX_MAP; - - private static final SparseIntArray TEXT_STRING_COLUMN_INDEX_MAP; - - private static final SparseIntArray OCTET_COLUMN_INDEX_MAP; - - private static final SparseIntArray LONG_COLUMN_INDEX_MAP; - - private static final SparseArray CHARSET_COLUMN_NAME_MAP; - - private static final SparseArray ENCODED_STRING_COLUMN_NAME_MAP; - - private static final SparseArray TEXT_STRING_COLUMN_NAME_MAP; - - private static final SparseArray OCTET_COLUMN_NAME_MAP; - - private static final SparseArray LONG_COLUMN_NAME_MAP; - - static { - MESSAGE_BOX_MAP = new SimpleArrayMap(); - MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX); - MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT); - MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS); - MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX); - - CHARSET_COLUMN_INDEX_MAP = new SparseIntArray(); - CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET); - CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET); - - CHARSET_COLUMN_NAME_MAP = new SparseArray(); - CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET); - CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET); - - // Encoded string field code -> column index/name map. - ENCODED_STRING_COLUMN_INDEX_MAP = new SparseIntArray(); - ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT); - ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT); - - ENCODED_STRING_COLUMN_NAME_MAP = new SparseArray(); - ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT); - ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT); - - // Text string field code -> column index/name map. - TEXT_STRING_COLUMN_INDEX_MAP = new SparseIntArray(); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT); - TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID); - - TEXT_STRING_COLUMN_NAME_MAP = new SparseArray(); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT); - TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID); - - // Octet field code -> column index/name map. - OCTET_COLUMN_INDEX_MAP = new SparseIntArray(); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS); - OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS); - - OCTET_COLUMN_NAME_MAP = new SparseArray(); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS); - OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS); - - // Long field code -> column index/name map. - LONG_COLUMN_INDEX_MAP = new SparseIntArray(); - LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE); - LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME); - LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY); - LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE); - - LONG_COLUMN_NAME_MAP = new SparseArray(); - LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE); - LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME); - LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY); - LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE); - - PDU_CACHE_INSTANCE = PduCache.getInstance(); - } - - private final Context mContext; - - private final ContentResolver mContentResolver; - - private PduPersister(final Context context) { - mContext = context; - mContentResolver = context.getContentResolver(); - } - - /** Get(or create if not exist) an instance of PduPersister */ - public static PduPersister getPduPersister(final Context context) { - if ((sPersister == null) || !context.equals(sPersister.mContext)) { - sPersister = new PduPersister(context); - } - if (LOCAL_LOGV) { - LogUtil.v(TAG, "PduPersister getPduPersister"); - } - - return sPersister; - } - - private void setEncodedStringValueToHeaders( - final Cursor c, final int columnIndex, - final PduHeaders headers, final int mapColumn) { - final String s = c.getString(columnIndex); - if ((s != null) && (s.length() > 0)) { - final int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn); - final int charset = c.getInt(charsetColumnIndex); - final EncodedStringValue value = new EncodedStringValue( - charset, getBytes(s)); - headers.setEncodedStringValue(value, mapColumn); - } - } - - private void setTextStringToHeaders( - final Cursor c, final int columnIndex, - final PduHeaders headers, final int mapColumn) { - final String s = c.getString(columnIndex); - if (s != null) { - headers.setTextString(getBytes(s), mapColumn); - } - } - - private void setOctetToHeaders( - final Cursor c, final int columnIndex, - final PduHeaders headers, final int mapColumn) throws InvalidHeaderValueException { - if (!c.isNull(columnIndex)) { - final int b = c.getInt(columnIndex); - headers.setOctet(b, mapColumn); - } - } - - private void setLongToHeaders( - final Cursor c, final int columnIndex, - final PduHeaders headers, final int mapColumn) { - if (!c.isNull(columnIndex)) { - final long l = c.getLong(columnIndex); - headers.setLongInteger(l, mapColumn); - } - } - - private Integer getIntegerFromPartColumn(final Cursor c, final int columnIndex) { - if (!c.isNull(columnIndex)) { - return c.getInt(columnIndex); - } - return null; - } - - private byte[] getByteArrayFromPartColumn(final Cursor c, final int columnIndex) { - if (!c.isNull(columnIndex)) { - return getBytes(c.getString(columnIndex)); - } - return null; - } - - private PduPart[] loadParts(final long msgId) throws MmsException { - final Cursor c = SqliteWrapper.query(mContext, mContentResolver, - Uri.parse("content://mms/" + msgId + "/part"), - PART_PROJECTION, null, null, null); - - PduPart[] parts = null; - - try { - if ((c == null) || (c.getCount() == 0)) { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "loadParts(" + msgId + "): no part to load."); - } - return null; - } - - final int partCount = c.getCount(); - int partIdx = 0; - parts = new PduPart[partCount]; - while (c.moveToNext()) { - final PduPart part = new PduPart(); - final Integer charset = getIntegerFromPartColumn( - c, PART_COLUMN_CHARSET); - if (charset != null) { - part.setCharset(charset); - } - - final byte[] contentDisposition = getByteArrayFromPartColumn( - c, PART_COLUMN_CONTENT_DISPOSITION); - if (contentDisposition != null) { - part.setContentDisposition(contentDisposition); - } - - final byte[] contentId = getByteArrayFromPartColumn( - c, PART_COLUMN_CONTENT_ID); - if (contentId != null) { - part.setContentId(contentId); - } - - final byte[] contentLocation = getByteArrayFromPartColumn( - c, PART_COLUMN_CONTENT_LOCATION); - if (contentLocation != null) { - part.setContentLocation(contentLocation); - } - - final byte[] contentType = getByteArrayFromPartColumn( - c, PART_COLUMN_CONTENT_TYPE); - if (contentType != null) { - part.setContentType(contentType); - } else { - throw new MmsException("Content-Type must be set."); - } - - final byte[] fileName = getByteArrayFromPartColumn( - c, PART_COLUMN_FILENAME); - if (fileName != null) { - part.setFilename(fileName); - } - - final byte[] name = getByteArrayFromPartColumn( - c, PART_COLUMN_NAME); - if (name != null) { - part.setName(name); - } - - // Construct a Uri for this part. - final long partId = c.getLong(PART_COLUMN_ID); - final Uri partURI = Uri.parse("content://mms/part/" + partId); - part.setDataUri(partURI); - - // For images/audio/video, we won't keep their data in Part - // because their renderer accept Uri as source. - final String type = toIsoString(contentType); - if (!ContentType.isImageType(type) - && !ContentType.isAudioType(type) - && !ContentType.isVideoType(type)) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = null; - - // Store simple string values directly in the database instead of an - // external file. This makes the text searchable and retrieval slightly - // faster. - if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type) - || ContentType.TEXT_HTML.equals(type)) { - final String text = c.getString(PART_COLUMN_TEXT); - final byte[] blob = new EncodedStringValue( - charset != null ? charset : CharacterSets.DEFAULT_CHARSET, - text != null ? text : "") - .getTextString(); - baos.write(blob, 0, blob.length); - } else { - - try { - is = mContentResolver.openInputStream(partURI); - - final byte[] buffer = new byte[256]; - int len = is.read(buffer); - while (len >= 0) { - baos.write(buffer, 0, len); - len = is.read(buffer); - } - } catch (final IOException e) { - Log.e(TAG, "Failed to load part data", e); - c.close(); - throw new MmsException(e); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - Log.e(TAG, "Failed to close stream", e); - } // Ignore - } - } - } - part.setData(baos.toByteArray()); - } - parts[partIdx++] = part; - } - } finally { - if (c != null) { - c.close(); - } - } - - return parts; - } - - private void loadAddress(final long msgId, final PduHeaders headers) { - final Cursor c = SqliteWrapper.query(mContext, mContentResolver, - Uri.parse("content://mms/" + msgId + "/addr"), - new String[]{Addr.ADDRESS, Addr.CHARSET, Addr.TYPE}, - null, null, null); - - if (c != null) { - try { - while (c.moveToNext()) { - final String addr = c.getString(0); - if (!TextUtils.isEmpty(addr)) { - final int addrType = c.getInt(2); - switch (addrType) { - case PduHeaders.FROM: - headers.setEncodedStringValue( - new EncodedStringValue(c.getInt(1), getBytes(addr)), - addrType); - break; - case PduHeaders.TO: - case PduHeaders.CC: - case PduHeaders.BCC: - headers.appendEncodedStringValue( - new EncodedStringValue(c.getInt(1), getBytes(addr)), - addrType); - break; - default: - Log.e(TAG, "Unknown address type: " + addrType); - break; - } - } - } - } finally { - c.close(); - } - } - } - - /** - * Load a PDU from a given cursor - * - * @param c The cursor - * @return A parsed PDU from the database row - */ - public GenericPdu load(final Cursor c) throws MmsException { - final PduHeaders headers = new PduHeaders(); - final long msgId = c.getLong(PDU_COLUMN_ID); - // Fill in the headers from the PDU columns - loadHeadersFromCursor(c, headers); - // Load address information of the MM. - loadAddress(msgId, headers); - // Load parts for the PDU body - final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); - final PduBody body = loadBody(msgId, msgType); - return createPdu(msgType, headers, body); - } - - /** - * Load a PDU from storage by given Uri. - * - * @param uri The Uri of the PDU to be loaded. - * @return A generic PDU object, it may be cast to dedicated PDU. - * @throws MmsException Failed to load some fields of a PDU. - */ - public GenericPdu load(final Uri uri) throws MmsException { - GenericPdu pdu = null; - PduCacheEntry cacheEntry = null; - int msgBox = 0; - final long threadId = -1; - try { - synchronized (PDU_CACHE_INSTANCE) { - if (PDU_CACHE_INSTANCE.isUpdating(uri)) { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "load: " + uri + " blocked by isUpdating()"); - } - try { - PDU_CACHE_INSTANCE.wait(); - } catch (final InterruptedException e) { - Log.e(TAG, "load: ", e); - } - } - - // Check if the pdu is already loaded - cacheEntry = PDU_CACHE_INSTANCE.get(uri); - if (cacheEntry != null) { - return cacheEntry.getPdu(); - } - - // Tell the cache to indicate to other callers that this item - // is currently being updated. - PDU_CACHE_INSTANCE.setUpdating(uri, true); - } - - final Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, - PDU_PROJECTION, null, null, null); - final PduHeaders headers = new PduHeaders(); - final long msgId = ContentUris.parseId(uri); - - try { - if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { - return null; // MMS not found - } - - msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); - //threadId = c.getLong(PDU_COLUMN_THREAD_ID); - loadHeadersFromCursor(c, headers); - } finally { - if (c != null) { - c.close(); - } - } - - // Check whether 'msgId' has been assigned a valid value. - if (msgId == -1L) { - throw new MmsException("Error! ID of the message: -1."); - } - - // Load address information of the MM. - loadAddress(msgId, headers); - - final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); - final PduBody body = loadBody(msgId, msgType); - pdu = createPdu(msgType, headers, body); - } finally { - synchronized (PDU_CACHE_INSTANCE) { - if (pdu != null) { - Assert.isNull(PDU_CACHE_INSTANCE.get(uri), "Pdu exists for " + uri); - // Update the cache entry with the real info - cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); - PDU_CACHE_INSTANCE.put(uri, cacheEntry); - } - PDU_CACHE_INSTANCE.setUpdating(uri, false); - PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead - } - } - return pdu; - } - - private void loadHeadersFromCursor(final Cursor c, final PduHeaders headers) - throws InvalidHeaderValueException { - for (int i = ENCODED_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) { - setEncodedStringValueToHeaders( - c, ENCODED_STRING_COLUMN_INDEX_MAP.valueAt(i), headers, - ENCODED_STRING_COLUMN_INDEX_MAP.keyAt(i)); - } - for (int i = TEXT_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) { - setTextStringToHeaders( - c, TEXT_STRING_COLUMN_INDEX_MAP.valueAt(i), headers, - TEXT_STRING_COLUMN_INDEX_MAP.keyAt(i)); - } - for (int i = OCTET_COLUMN_INDEX_MAP.size(); --i >= 0; ) { - setOctetToHeaders( - c, OCTET_COLUMN_INDEX_MAP.valueAt(i), headers, - OCTET_COLUMN_INDEX_MAP.keyAt(i)); - } - for (int i = LONG_COLUMN_INDEX_MAP.size(); --i >= 0; ) { - setLongToHeaders( - c, LONG_COLUMN_INDEX_MAP.valueAt(i), headers, - LONG_COLUMN_INDEX_MAP.keyAt(i)); - } - } - - private GenericPdu createPdu(final int msgType, final PduHeaders headers, final PduBody body) - throws MmsException { - switch (msgType) { - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - return new NotificationInd(headers); - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - return new DeliveryInd(headers); - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: - return new ReadOrigInd(headers); - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - return new RetrieveConf(headers, body); - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - return new SendReq(headers, body); - case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: - return new AcknowledgeInd(headers); - case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: - return new NotifyRespInd(headers); - case PduHeaders.MESSAGE_TYPE_READ_REC_IND: - return new ReadRecInd(headers); - case PduHeaders.MESSAGE_TYPE_SEND_CONF: - case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: - case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: - case PduHeaders.MESSAGE_TYPE_DELETE_REQ: - case PduHeaders.MESSAGE_TYPE_DELETE_CONF: - case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: - case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: - throw new MmsException( - "Unsupported PDU type: " + Integer.toHexString(msgType)); - - default: - throw new MmsException( - "Unrecognized PDU type: " + Integer.toHexString(msgType)); - } - } - - private PduBody loadBody(final long msgId, final int msgType) throws MmsException { - final PduBody body = new PduBody(); - - // For PDU which type is M_retrieve.conf or Send.req, we should - // load multiparts and put them into the body of the PDU. - if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) - || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { - final PduPart[] parts = loadParts(msgId); - if (parts != null) { - final int partsNum = parts.length; - for (int i = 0; i < partsNum; i++) { - body.addPart(parts[i]); - } - } - } - - return body; - } - - private void persistAddress( - final long msgId, final int type, final EncodedStringValue[] array) { - final ContentValues values = new ContentValues(3); - - for (final EncodedStringValue addr : array) { - values.clear(); // Clear all values first. - values.put(Addr.ADDRESS, toIsoString(addr.getTextString())); - values.put(Addr.CHARSET, addr.getCharacterSet()); - values.put(Addr.TYPE, type); - - final Uri uri = Uri.parse("content://mms/" + msgId + "/addr"); - SqliteWrapper.insert(mContext, mContentResolver, uri, values); - } - } - - private static String getPartContentType(final PduPart part) { - return part.getContentType() == null ? null : toIsoString(part.getContentType()); - } - - private static void getValues(final PduPart part, final ContentValues values) { - byte[] bytes = part.getFilename(); - if (bytes != null) { - values.put(Part.FILENAME, new String(bytes)); - } - - bytes = part.getName(); - if (bytes != null) { - values.put(Part.NAME, new String(bytes)); - } - - bytes = part.getContentDisposition(); - if (bytes != null) { - values.put(Part.CONTENT_DISPOSITION, toIsoString(bytes)); - } - - bytes = part.getContentId(); - if (bytes != null) { - values.put(Part.CONTENT_ID, toIsoString(bytes)); - } - - bytes = part.getContentLocation(); - if (bytes != null) { - values.put(Part.CONTENT_LOCATION, toIsoString(bytes)); - } - } - - public Uri persistPart(final PduPart part, final long msgId, - final Map preOpenedFiles) throws MmsException { - final Uri uri = Uri.parse("content://mms/" + msgId + "/part"); - final ContentValues values = new ContentValues(8); - - final int charset = part.getCharset(); - if (charset != 0) { - values.put(Part.CHARSET, charset); - } - - String contentType = getPartContentType(part); - final byte[] data = part.getData(); - - if (LOCAL_LOGV) { - LogUtil.v(TAG, "PduPersister.persistPart part: " + uri + " contentType: " + - contentType); - } - - if (contentType != null) { - // There is no "image/jpg" in Android (and it's an invalid mimetype). - // Change it to "image/jpeg" - if (ContentType.IMAGE_JPG.equals(contentType)) { - contentType = ContentType.IMAGE_JPEG; - } - - // On somes phones, a vcard comes in as text/plain instead of text/v-card. - // Fix it if necessary. - if (ContentType.TEXT_PLAIN.equals(contentType) && data != null) { - // There might be a more efficient way to just check the beginning of the string - // without encoding the whole thing, but we're concerned that with various - // characters sets, just comparing the byte data to BEGIN_VCARD would not be - // reliable. - final String encodedDataString = new EncodedStringValue(charset, data).getString(); - if (encodedDataString != null && encodedDataString.startsWith(BEGIN_VCARD)) { - contentType = ContentType.TEXT_VCARD; - part.setContentType(contentType.getBytes()); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "PduPersister.persistPart part: " + uri + " contentType: " + - contentType + " changing to vcard"); - } - } - } - - values.put(Part.CONTENT_TYPE, contentType); - // To ensure the SMIL part is always the first part. - if (ContentType.APP_SMIL.equals(contentType)) { - values.put(Part.SEQ, -1); - } - } else { - throw new MmsException("MIME type of the part must be set."); - } - - getValues(part, values); - - Uri res = null; - - try { - res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); - } catch (IllegalStateException e) { - // Currently the MMS provider throws an IllegalStateException when it's out of space - LogUtil.e(TAG, "SqliteWrapper.insert threw: ", e); - } - - if (res == null) { - throw new MmsException("Failed to persist part, return null."); - } - - persistData(part, res, contentType, preOpenedFiles); - // After successfully store the data, we should update - // the dataUri of the part. - part.setDataUri(res); - - return res; - } - - /** - * Save data of the part into storage. The source data may be given - * by a byte[] or a Uri. If it's a byte[], directly save it - * into storage, otherwise load source data from the dataUri and then - * save it. If the data is an image, we may scale down it according - * to user preference. - * - * @param part The PDU part which contains data to be saved. - * @param uri The URI of the part. - * @param contentType The MIME type of the part. - * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. - * @throws MmsException Cannot find source data or error occurred - * while saving the data. - */ - private void persistData(final PduPart part, final Uri uri, - final String contentType, final Map preOpenedFiles) - throws MmsException { - OutputStream os = null; - InputStream is = null; - DrmConvertSession drmConvertSession = null; - Uri dataUri = null; - String path = null; - - try { - final byte[] data = part.getData(); - final int charset = part.getCharset(); - if (ContentType.TEXT_PLAIN.equals(contentType) - || ContentType.APP_SMIL.equals(contentType) - || ContentType.TEXT_HTML.equals(contentType)) { - // Some phone could send MMS with a text part having empty data - // Let's just skip those parts. - // EncodedStringValue() throws NPE if data is empty - if (data != null) { - final ContentValues cv = new ContentValues(); - cv.put(Mms.Part.TEXT, new EncodedStringValue(charset, data).getString()); - if (mContentResolver.update(uri, cv, null, null) != 1) { - throw new MmsException("unable to update " + uri.toString()); - } - } - } else { - final boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType); - if (isDrm) { - if (uri != null) { - try { - path = convertUriToPath(mContext, uri); - if (LOCAL_LOGV) { - LogUtil.v(TAG, "drm uri: " + uri + " path: " + path); - } - final File f = new File(path); - final long len = f.length(); - if (LOCAL_LOGV) { - LogUtil.v(TAG, "drm path: " + path + " len: " + len); - } - if (len > 0) { - // we're not going to re-persist and re-encrypt an already - // converted drm file - return; - } - } catch (final Exception e) { - Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); - } - } - // We haven't converted the file yet, start the conversion - drmConvertSession = DrmConvertSession.open(mContext, contentType); - if (drmConvertSession == null) { - throw new MmsException("Mimetype " + contentType + - " can not be converted."); - } - } - // uri can look like: - // content://mms/part/98 - os = mContentResolver.openOutputStream(uri); - if (os == null) { - throw new MmsException("Failed to create output stream on " + uri); - } - if (data == null) { - dataUri = part.getDataUri(); - if ((dataUri == null) || (dataUri == uri)) { - Log.w(TAG, "Can't find data for this part."); - return; - } - // dataUri can look like: - // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715 - if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) { - is = preOpenedFiles.get(dataUri); - } - if (is == null) { - is = mContentResolver.openInputStream(dataUri); - } - if (is == null) { - throw new MmsException("Failed to create input stream on " + dataUri); - } - if (LOCAL_LOGV) { - LogUtil.v(TAG, "Saving data to: " + uri); - } - - final byte[] buffer = new byte[8192]; - for (int len = 0; (len = is.read(buffer)) != -1; ) { - if (!isDrm) { - os.write(buffer, 0, len); - } else { - final byte[] convertedData = drmConvertSession.convert(buffer, len); - if (convertedData != null) { - os.write(convertedData, 0, convertedData.length); - } else { - throw new MmsException("Error converting drm data."); - } - } - } - } else { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "Saving data to: " + uri); - } - if (!isDrm) { - os.write(data); - } else { - dataUri = uri; - final byte[] convertedData = drmConvertSession.convert(data, data.length); - if (convertedData != null) { - os.write(convertedData, 0, convertedData.length); - } else { - throw new MmsException("Error converting drm data."); - } - } - } - } - } catch (final SQLiteException e) { - Log.e(TAG, "Failed with SQLiteException.", e); - throw new MmsException(e); - } catch (final FileNotFoundException e) { - Log.e(TAG, "Failed to open Input/Output stream.", e); - throw new MmsException(e); - } catch (final IOException e) { - Log.e(TAG, "Failed to read/write data.", e); - throw new MmsException(e); - } finally { - if (os != null) { - try { - os.close(); - } catch (final IOException e) { - Log.e(TAG, "IOException while closing: " + os, e); - } // Ignore - } - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - Log.e(TAG, "IOException while closing: " + is, e); - } // Ignore - } - if (drmConvertSession != null) { - drmConvertSession.close(path); - - // Reset the permissions on the encrypted part file so everyone has only read - // permission. - final File f = new File(path); - final ContentValues values = new ContentValues(0); - SqliteWrapper.update(mContext, mContentResolver, - Uri.parse("content://mms/resetFilePerm/" + f.getName()), - values, null, null); - } - } - } - - /** - * This method expects uri in the following format - * content://media// (or) - * file://sdcard/test.mp4 - * http://test.com/test.mp4 - * - * Here shall be "video" or "audio" or "images" - * the index of the content in given table - */ - public static String convertUriToPath(final Context context, final Uri uri) { - String path = null; - if (null != uri) { - final String scheme = uri.getScheme(); - if (null == scheme || scheme.equals("") || - scheme.equals(ContentResolver.SCHEME_FILE)) { - path = uri.getPath(); - - } else if (scheme.equals("http")) { - path = uri.toString(); - - } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { - final String[] projection = new String[] {MediaStore.MediaColumns.DATA}; - Cursor cursor = null; - try { - cursor = context.getContentResolver().query(uri, projection, null, - null, null); - if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) { - throw new IllegalArgumentException("Given Uri could not be found" + - " in media store"); - } - final int pathIndex = - cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - path = cursor.getString(pathIndex); - } catch (final SQLiteException e) { - throw new IllegalArgumentException("Given Uri is not formatted in a way " + - "so that it can be found in media store."); - } finally { - if (null != cursor) { - cursor.close(); - } - } - } else { - throw new IllegalArgumentException("Given Uri scheme is not supported"); - } - } - return path; - } - - private void updateAddress( - final long msgId, final int type, final EncodedStringValue[] array) { - // Delete old address information and then insert new ones. - SqliteWrapper.delete(mContext, mContentResolver, - Uri.parse("content://mms/" + msgId + "/addr"), - Addr.TYPE + "=" + type, null); - - persistAddress(msgId, type, array); - } - - /** - * Update headers of a SendReq. - * - * @param uri The PDU which need to be updated. - * @param pdu New headers. - * @throws MmsException Bad URI or updating failed. - */ - public void updateHeaders(final Uri uri, final SendReq sendReq) { - synchronized (PDU_CACHE_INSTANCE) { - // If the cache item is getting updated, wait until it's done updating before - // purging it. - if (PDU_CACHE_INSTANCE.isUpdating(uri)) { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()"); - } - try { - PDU_CACHE_INSTANCE.wait(); - } catch (final InterruptedException e) { - Log.e(TAG, "updateHeaders: ", e); - } - } - } - PDU_CACHE_INSTANCE.purge(uri); - - final ContentValues values = new ContentValues(10); - final byte[] contentType = sendReq.getContentType(); - if (contentType != null) { - values.put(Mms.CONTENT_TYPE, toIsoString(contentType)); - } - - final long date = sendReq.getDate(); - if (date != -1) { - values.put(Mms.DATE, date); - } - - final int deliveryReport = sendReq.getDeliveryReport(); - if (deliveryReport != 0) { - values.put(Mms.DELIVERY_REPORT, deliveryReport); - } - - final long expiry = sendReq.getExpiry(); - if (expiry != -1) { - values.put(Mms.EXPIRY, expiry); - } - - final byte[] msgClass = sendReq.getMessageClass(); - if (msgClass != null) { - values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass)); - } - - final int priority = sendReq.getPriority(); - if (priority != 0) { - values.put(Mms.PRIORITY, priority); - } - - final int readReport = sendReq.getReadReport(); - if (readReport != 0) { - values.put(Mms.READ_REPORT, readReport); - } - - final byte[] transId = sendReq.getTransactionId(); - if (transId != null) { - values.put(Mms.TRANSACTION_ID, toIsoString(transId)); - } - - final EncodedStringValue subject = sendReq.getSubject(); - if (subject != null) { - values.put(Mms.SUBJECT, toIsoString(subject.getTextString())); - values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet()); - } else { - values.put(Mms.SUBJECT, ""); - } - - final long messageSize = sendReq.getMessageSize(); - if (messageSize > 0) { - values.put(Mms.MESSAGE_SIZE, messageSize); - } - - final PduHeaders headers = sendReq.getPduHeaders(); - final HashSet recipients = new HashSet(); - for (final int addrType : ADDRESS_FIELDS) { - EncodedStringValue[] array = null; - if (addrType == PduHeaders.FROM) { - final EncodedStringValue v = headers.getEncodedStringValue(addrType); - if (v != null) { - array = new EncodedStringValue[1]; - array[0] = v; - } - } else { - array = headers.getEncodedStringValues(addrType); - } - - if (array != null) { - final long msgId = ContentUris.parseId(uri); - updateAddress(msgId, addrType, array); - if (addrType == PduHeaders.TO) { - for (final EncodedStringValue v : array) { - if (v != null) { - recipients.add(v.getString()); - } - } - } - } - } - if (!recipients.isEmpty()) { - final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients); - values.put(Mms.THREAD_ID, threadId); - } - - SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); - } - - - private void updatePart(final Uri uri, final PduPart part, - final Map preOpenedFiles) - throws MmsException { - final ContentValues values = new ContentValues(7); - - final int charset = part.getCharset(); - if (charset != 0) { - values.put(Part.CHARSET, charset); - } - - String contentType = null; - if (part.getContentType() != null) { - contentType = toIsoString(part.getContentType()); - values.put(Part.CONTENT_TYPE, contentType); - } else { - throw new MmsException("MIME type of the part must be set."); - } - - getValues(part, values); - - SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); - - // Only update the data when: - // 1. New binary data supplied or - // 2. The Uri of the part is different from the current one. - if ((part.getData() != null) - || (uri != part.getDataUri())) { - persistData(part, uri, contentType, preOpenedFiles); - } - } - - /** - * Update all parts of a PDU. - * - * @param uri The PDU which need to be updated. - * @param body New message body of the PDU. - * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. - * @throws MmsException Bad URI or updating failed. - */ - public void updateParts(final Uri uri, final PduBody body, - final Map preOpenedFiles) - throws MmsException { - try { - PduCacheEntry cacheEntry; - synchronized (PDU_CACHE_INSTANCE) { - if (PDU_CACHE_INSTANCE.isUpdating(uri)) { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "updateParts: " + uri + " blocked by isUpdating()"); - } - try { - PDU_CACHE_INSTANCE.wait(); - } catch (final InterruptedException e) { - Log.e(TAG, "updateParts: ", e); - } - cacheEntry = PDU_CACHE_INSTANCE.get(uri); - if (cacheEntry != null) { - ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); - } - } - // Tell the cache to indicate to other callers that this item - // is currently being updated. - PDU_CACHE_INSTANCE.setUpdating(uri, true); - } - - final ArrayList toBeCreated = new ArrayList(); - final ArrayMap toBeUpdated = new ArrayMap(); - - final int partsNum = body.getPartsNum(); - final StringBuilder filter = new StringBuilder().append('('); - for (int i = 0; i < partsNum; i++) { - final PduPart part = body.getPart(i); - final Uri partUri = part.getDataUri(); - if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) { - toBeCreated.add(part); - } else { - toBeUpdated.put(partUri, part); - - // Don't use 'i > 0' to determine whether we should append - // 'AND' since 'i = 0' may be skipped in another branch. - if (filter.length() > 1) { - filter.append(" AND "); - } - - filter.append(Part._ID); - filter.append("!="); - DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); - } - } - filter.append(')'); - - final long msgId = ContentUris.parseId(uri); - - // Remove the parts which doesn't exist anymore. - SqliteWrapper.delete(mContext, mContentResolver, - Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), - filter.length() > 2 ? filter.toString() : null, null); - - // Create new parts which didn't exist before. - for (final PduPart part : toBeCreated) { - persistPart(part, msgId, preOpenedFiles); - } - - // Update the modified parts. - for (final Map.Entry e : toBeUpdated.entrySet()) { - updatePart(e.getKey(), e.getValue(), preOpenedFiles); - } - } finally { - synchronized (PDU_CACHE_INSTANCE) { - PDU_CACHE_INSTANCE.setUpdating(uri, false); - PDU_CACHE_INSTANCE.notifyAll(); - } - } - } - - /** - * Persist a PDU object to specific location in the storage. - * - * @param pdu The PDU object to be stored. - * @param uri Where to store the given PDU object. - * @param subId Subscription id associated with this message. - * @param subPhoneNumber TODO - * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. - * @return A Uri which can be used to access the stored PDU. - */ - public Uri persist(final GenericPdu pdu, final Uri uri, final int subId, - final String subPhoneNumber, final Map preOpenedFiles) - throws MmsException { - if (uri == null) { - throw new MmsException("Uri may not be null."); - } - long msgId = -1; - try { - msgId = ContentUris.parseId(uri); - } catch (final NumberFormatException e) { - // the uri ends with "inbox" or something else like that - } - final boolean existingUri = msgId != -1; - - if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) { - throw new MmsException( - "Bad destination, must be one of " - + "content://mms/inbox, content://mms/sent, " - + "content://mms/drafts, content://mms/outbox, " - + "content://mms/temp." - ); - } - synchronized (PDU_CACHE_INSTANCE) { - // If the cache item is getting updated, wait until it's done updating before - // purging it. - if (PDU_CACHE_INSTANCE.isUpdating(uri)) { - if (LOCAL_LOGV) { - LogUtil.v(TAG, "persist: " + uri + " blocked by isUpdating()"); - } - try { - PDU_CACHE_INSTANCE.wait(); - } catch (final InterruptedException e) { - Log.e(TAG, "persist1: ", e); - } - } - } - PDU_CACHE_INSTANCE.purge(uri); - - final PduHeaders header = pdu.getPduHeaders(); - PduBody body = null; - ContentValues values = new ContentValues(); - - // Mark new messages as seen in the telephony database so that we don't have to - // do a global "set all messages as seen" since that occasionally seems to be - // problematic (i.e. very slow). See bug 18189471. - values.put(Mms.SEEN, 1); - - //Set> set; - - for (int i = ENCODED_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) { - final int field = ENCODED_STRING_COLUMN_NAME_MAP.keyAt(i); - final EncodedStringValue encodedString = header.getEncodedStringValue(field); - if (encodedString != null) { - final String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field); - values.put(ENCODED_STRING_COLUMN_NAME_MAP.valueAt(i), - toIsoString(encodedString.getTextString())); - values.put(charsetColumn, encodedString.getCharacterSet()); - } - } - - for (int i = TEXT_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) { - final byte[] text = header.getTextString(TEXT_STRING_COLUMN_NAME_MAP.keyAt(i)); - if (text != null) { - values.put(TEXT_STRING_COLUMN_NAME_MAP.valueAt(i), toIsoString(text)); - } - } - - for (int i = OCTET_COLUMN_NAME_MAP.size(); --i >= 0; ) { - final int b = header.getOctet(OCTET_COLUMN_NAME_MAP.keyAt(i)); - if (b != 0) { - values.put(OCTET_COLUMN_NAME_MAP.valueAt(i), b); - } - } - - for (int i = LONG_COLUMN_NAME_MAP.size(); --i >= 0; ) { - final long l = header.getLongInteger(LONG_COLUMN_NAME_MAP.keyAt(i)); - if (l != -1L) { - values.put(LONG_COLUMN_NAME_MAP.valueAt(i), l); - } - } - - final SparseArray addressMap = - new SparseArray(ADDRESS_FIELDS.length); - // Save address information. - for (final int addrType : ADDRESS_FIELDS) { - EncodedStringValue[] array = null; - if (addrType == PduHeaders.FROM) { - final EncodedStringValue v = header.getEncodedStringValue(addrType); - if (v != null) { - array = new EncodedStringValue[1]; - array[0] = v; - } - } else { - array = header.getEncodedStringValues(addrType); - } - addressMap.put(addrType, array); - } - - final HashSet recipients = new HashSet(); - final int msgType = pdu.getMessageType(); - // Here we only allocate thread ID for M-Notification.ind, - // M-Retrieve.conf and M-Send.req. - // Some of other PDU types may be allocated a thread ID outside - // this scope. - if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) - || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) - || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { - switch (msgType) { - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: - case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: - loadRecipients(PduHeaders.FROM, recipients, addressMap); - - // For received messages (whether group MMS is enabled or not) we want to - // associate this message with the thread composed of all the recipients - // EXCLUDING our own number. This includes the person who sent the - // message (the FROM field above) in addition to the other people the message - // was addressed TO (or CC fields to address group messaging compatibility - // issues with devices that place numbers in this field). Typically our own - // number is in the TO/CC field so we have to remove it in loadRecipients. - checkAndLoadToCcRecipients(recipients, addressMap, subPhoneNumber); - break; - case PduHeaders.MESSAGE_TYPE_SEND_REQ: - loadRecipients(PduHeaders.TO, recipients, addressMap); - break; - } - long threadId = -1L; - if (!recipients.isEmpty()) { - // Given all the recipients associated with this message, find (or create) the - // correct thread. - threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients); - } else { - LogUtil.w(TAG, "PduPersister.persist No recipients; persisting PDU to thread: " - + threadId); - } - values.put(Mms.THREAD_ID, threadId); - } - - // Save parts first to avoid inconsistent message is loaded - // while saving the parts. - final long dummyId = System.currentTimeMillis(); // Dummy ID of the msg. - - // Figure out if this PDU is a text-only message - boolean textOnly = true; - - // Get body if the PDU is a RetrieveConf or SendReq. - if (pdu instanceof MultimediaMessagePdu) { - body = ((MultimediaMessagePdu) pdu).getBody(); - // Start saving parts if necessary. - if (body != null) { - final int partsNum = body.getPartsNum(); - if (LOCAL_LOGV) { - LogUtil.v(TAG, "PduPersister.persist partsNum: " + partsNum); - } - if (partsNum > 2) { - // For a text-only message there will be two parts: 1-the SMIL, 2-the text. - // Down a few lines below we're checking to make sure we've only got SMIL or - // text. We also have to check then we don't have more than two parts. - // Otherwise, a slideshow with two text slides would be marked as textOnly. - textOnly = false; - } - for (int i = 0; i < partsNum; i++) { - final PduPart part = body.getPart(i); - persistPart(part, dummyId, preOpenedFiles); - - // If we've got anything besides text/plain or SMIL part, then we've got - // an mms message with some other type of attachment. - final String contentType = getPartContentType(part); - if (LOCAL_LOGV) { - LogUtil.v(TAG, "PduPersister.persist part: " + i + " contentType: " + - contentType); - } - if (contentType != null && !ContentType.APP_SMIL.equals(contentType) - && !ContentType.TEXT_PLAIN.equals(contentType)) { - textOnly = false; - } - } - } - } - // Record whether this mms message is a simple plain text or not. This is a hint for the - // UI. - if (OsUtil.isAtLeastJB_MR1()) { - values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0); - } - - if (OsUtil.isAtLeastL_MR1()) { - values.put(Mms.SUBSCRIPTION_ID, subId); - } else { - Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId); - } - - Uri res = null; - if (existingUri) { - res = uri; - SqliteWrapper.update(mContext, mContentResolver, res, values, null, null); - } else { - res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); - if (res == null) { - throw new MmsException("persist() failed: return null."); - } - // Get the real ID of the PDU and update all parts which were - // saved with the dummy ID. - msgId = ContentUris.parseId(res); - } - - values = new ContentValues(1); - values.put(Part.MSG_ID, msgId); - SqliteWrapper.update(mContext, mContentResolver, - Uri.parse("content://mms/" + dummyId + "/part"), - values, null, null); - // We should return the longest URI of the persisted PDU, for - // example, if input URI is "content://mms/inbox" and the _ID of - // persisted PDU is '8', we should return "content://mms/inbox/8" - // instead of "content://mms/8". - // TODO: Should the MmsProvider be responsible for this??? - if (!existingUri) { - res = Uri.parse(uri + "/" + msgId); - } - - // Save address information. - for (final int addrType : ADDRESS_FIELDS) { - final EncodedStringValue[] array = addressMap.get(addrType); - if (array != null) { - persistAddress(msgId, addrType, array); - } - } - - return res; - } - - /** - * For a given address type, extract the recipients from the headers. - * - * @param addressType can be PduHeaders.FROM or PduHeaders.TO - * @param recipients a HashSet that is loaded with the recipients from the FROM or TO - * headers - * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header - */ - private void loadRecipients(final int addressType, final HashSet recipients, - final SparseArray addressMap) { - final EncodedStringValue[] array = addressMap.get(addressType); - if (array == null) { - return; - } - for (final EncodedStringValue v : array) { - if (v != null) { - final String number = v.getString(); - if (!recipients.contains(number)) { - // Only add numbers which aren't already included. - recipients.add(number); - } - } - } - } - - /** - * For a given address type, extract the recipients from the headers. - * - * @param recipients a HashSet that is loaded with the recipients from the FROM or TO - * headers - * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header - * @param selfNumber self phone number - */ - private void checkAndLoadToCcRecipients(final HashSet recipients, - final SparseArray addressMap, final String selfNumber) { - final EncodedStringValue[] arrayTo = addressMap.get(PduHeaders.TO); - final EncodedStringValue[] arrayCc = addressMap.get(PduHeaders.CC); - final ArrayList numbers = new ArrayList(); - if (arrayTo != null) { - for (final EncodedStringValue v : arrayTo) { - if (v != null) { - numbers.add(v.getString()); - } - } - } - if (arrayCc != null) { - for (final EncodedStringValue v : arrayCc) { - if (v != null) { - numbers.add(v.getString()); - } - } - } - for (final String number : numbers) { - // Only add numbers which aren't my own number. - if (TextUtils.isEmpty(selfNumber) || !PhoneNumberUtils.compare(number, selfNumber)) { - if (!recipients.contains(number)) { - // Only add numbers which aren't already included. - recipients.add(number); - } - } - } - } - - /** - * Move a PDU object from one location to another. - * - * @param from Specify the PDU object to be moved. - * @param to The destination location, should be one of the following: - * "content://mms/inbox", "content://mms/sent", - * "content://mms/drafts", "content://mms/outbox", - * "content://mms/trash". - * @return New Uri of the moved PDU. - * @throws MmsException Error occurred while moving the message. - */ - public Uri move(final Uri from, final Uri to) throws MmsException { - // Check whether the 'msgId' has been assigned a valid value. - final long msgId = ContentUris.parseId(from); - if (msgId == -1L) { - throw new MmsException("Error! ID of the message: -1."); - } - - // Get corresponding int value of destination box. - final Integer msgBox = MESSAGE_BOX_MAP.get(to); - if (msgBox == null) { - throw new MmsException( - "Bad destination, must be one of " - + "content://mms/inbox, content://mms/sent, " - + "content://mms/drafts, content://mms/outbox, " - + "content://mms/temp." - ); - } - - final ContentValues values = new ContentValues(1); - values.put(Mms.MESSAGE_BOX, msgBox); - SqliteWrapper.update(mContext, mContentResolver, from, values, null, null); - return ContentUris.withAppendedId(to, msgId); - } - - /** - * Wrap a byte[] into a String. - */ - public static String toIsoString(final byte[] bytes) { - try { - return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); - } catch (final UnsupportedEncodingException e) { - // Impossible to reach here! - Log.e(TAG, "ISO_8859_1 must be supported!", e); - return ""; - } - } - - /** - * Unpack a given String into a byte[]. - */ - public static byte[] getBytes(final String data) { - try { - return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); - } catch (final UnsupportedEncodingException e) { - // Impossible to reach here! - Log.e(TAG, "ISO_8859_1 must be supported!", e); - return new byte[0]; - } - } - - /** - * Remove all objects in the temporary path. - */ - public void release() { - final Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI); - SqliteWrapper.delete(mContext, mContentResolver, uri, null, null); - } - - /** - * Find all messages to be sent or downloaded before certain time. - */ - public Cursor getPendingMessages(final long dueTime) { - final Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); - uriBuilder.appendQueryParameter("protocol", "mms"); - - final String selection = PendingMessages.ERROR_TYPE + " < ?" - + " AND " + PendingMessages.DUE_TIME + " <= ?"; - - final String[] selectionArgs = new String[] { - String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT), - String.valueOf(dueTime) - }; - - return SqliteWrapper.query(mContext, mContentResolver, - uriBuilder.build(), null, selection, selectionArgs, - PendingMessages.DUE_TIME); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java b/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java deleted file mode 100644 index 1ce9dc1..0000000 --- a/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import java.io.ByteArrayOutputStream; - -public class QuotedPrintable { - private static byte ESCAPE_CHAR = '='; - - /** - * Decodes an array quoted-printable characters into an array of original bytes. - * Escaped characters are converted back to their original representation. - * - *

- * This function implements a subset of - * quoted-printable encoding specification (rule #1 and rule #2) - * as defined in RFC 1521. - *

- * - * @param bytes array of quoted-printable characters - * @return array of original bytes, - * null if quoted-printable decoding is unsuccessful. - */ - public static final byte[] decodeQuotedPrintable(byte[] bytes) { - if (bytes == null) { - return null; - } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (int i = 0; i < bytes.length; i++) { - int b = bytes[i]; - if (b == ESCAPE_CHAR) { - try { - if ('\r' == (char) bytes[i + 1] && - '\n' == (char) bytes[i + 2]) { - i += 2; - continue; - } - int u = Character.digit((char) bytes[++i], 16); - int l = Character.digit((char) bytes[++i], 16); - if (u == -1 || l == -1) { - return null; - } - buffer.write((char) ((u << 4) + l)); - } catch (ArrayIndexOutOfBoundsException e) { - return null; - } - } else { - buffer.write(b); - } - } - return buffer.toByteArray(); - } -} diff --git a/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java b/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java deleted file mode 100644 index 198e4c3..0000000 --- a/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -public class ReadOrigInd extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public ReadOrigInd() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - ReadOrigInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-MMS-Read-status value. - * - * @return the value - */ - public int getReadStatus() { - return mPduHeaders.getOctet(PduHeaders.READ_STATUS); - } - - /** - * Set X-MMS-Read-status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/ReadRecInd.java b/src/com/android/messaging/mmslib/pdu/ReadRecInd.java deleted file mode 100644 index c1a9ae4..0000000 --- a/src/com/android/messaging/mmslib/pdu/ReadRecInd.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -public class ReadRecInd extends GenericPdu { - /** - * Constructor, used when composing a M-ReadRec.ind pdu. - * - * @param from the from value - * @param messageId the message ID value - * @param mmsVersion current viersion of mms - * @param readStatus the read status value - * @param to the to value - * @throws InvalidHeaderValueException if parameters are invalid. - * @throws NullPointerException if messageId or to is null. - */ - public ReadRecInd(EncodedStringValue from, - byte[] messageId, - int mmsVersion, - int readStatus, - EncodedStringValue[] to) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND); - setFrom(from); - setMessageId(messageId); - setMmsVersion(mmsVersion); - setTo(to); - setReadStatus(readStatus); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - ReadRecInd(PduHeaders headers) { - super(headers); - } - - /** - * Get Date value. - * - * @return the value - */ - public long getDate() { - return mPduHeaders.getLongInteger(PduHeaders.DATE); - } - - /** - * Set Date value. - * - * @param value the value - */ - public void setDate(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.DATE); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get To value. - * - * @return the value - */ - public EncodedStringValue[] getTo() { - return mPduHeaders.getEncodedStringValues(PduHeaders.TO); - } - - /** - * Set To value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /** - * Get X-MMS-Read-status value. - * - * @return the value - */ - public int getReadStatus() { - return mPduHeaders.getOctet(PduHeaders.READ_STATUS); - } - - /** - * Set X-MMS-Read-status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/RetrieveConf.java b/src/com/android/messaging/mmslib/pdu/RetrieveConf.java deleted file mode 100644 index 9e0faed..0000000 --- a/src/com/android/messaging/mmslib/pdu/RetrieveConf.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -/** - * M-Retrive.conf Pdu. - */ -public class RetrieveConf extends MultimediaMessagePdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public RetrieveConf() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - RetrieveConf(PduHeaders headers) { - super(headers); - } - - /** - * Constructor with given headers and body - * - * @param headers Headers for this PDU. - * @param body Body of this PDu. - */ - RetrieveConf(PduHeaders headers, PduBody body) { - super(headers, body); - } - - /** - * Get CC value. - * - * @return the value - */ - public EncodedStringValue[] getCc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.CC); - } - - /** - * Add a "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addCc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); - } - - /** - * Get Content-type value. - * - * @return the value - */ - public byte[] getContentType() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); - } - - /** - * Set Content-type value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setContentType(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); - } - - /** - * Get X-Mms-Delivery-Report value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /** - * Get From value. - * From-value = Value-length - * (Address-present-token Encoded-string-value | Insert-address-token) - * - * @return the value - */ - public EncodedStringValue getFrom() { - return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); - } - - /** - * Set From value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setFrom(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-Mms-Read-Report value. - * - * @return the value - */ - public int getReadReport() { - return mPduHeaders.getOctet(PduHeaders.READ_REPORT); - } - - /** - * Set X-Mms-Read-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); - } - - /** - * Get X-Mms-Retrieve-Status value. - * - * @return the value - */ - public int getRetrieveStatus() { - /* - * If the header is not there, assuming it is OK status. - * Some carriers may choose to not send this header. - */ - return mPduHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ? - mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK; - } - - /** - * Set X-Mms-Retrieve-Status value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setRetrieveStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS); - } - - /** - * Get X-Mms-Retrieve-Text value. - * - * @return the value - */ - public EncodedStringValue getRetrieveText() { - return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT); - } - - /** - * Set X-Mms-Retrieve-Text value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setRetrieveText(EncodedStringValue value) { - mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT); - } - - /** - * Get X-Mms-Transaction-Id. - * - * @return the value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getContentClass() {return 0x00;} - * public void setApplicId(byte value) {} - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public byte getDistributionIndicator() {return 0x00;} - * public void setDistributionIndicator(byte value) {} - * - * public PreviouslySentByValue getPreviouslySentBy() {return null;} - * public void setPreviouslySentBy(PreviouslySentByValue value) {} - * - * public PreviouslySentDateValue getPreviouslySentDate() {} - * public void setPreviouslySentDate(PreviouslySentDateValue value) {} - * - * public MmFlagsValue getMmFlags() {return null;} - * public void setMmFlags(MmFlagsValue value) {} - * - * public MmStateValue getMmState() {return null;} - * public void getMmState(MmStateValue value) {} - * - * public byte[] getReplaceId() {return 0x00;} - * public void setReplaceId(byte[] value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/SendConf.java b/src/com/android/messaging/mmslib/pdu/SendConf.java deleted file mode 100644 index cf1399e..0000000 --- a/src/com/android/messaging/mmslib/pdu/SendConf.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2007 Esmertec AG. - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -public class SendConf extends GenericPdu { - /** - * Empty constructor. - * Since the Pdu corresponding to this class is constructed - * by the Proxy-Relay server, this class is only instantiated - * by the Pdu Parser. - * - * @throws InvalidHeaderValueException if error occurs. - */ - public SendConf() throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - SendConf(PduHeaders headers) { - super(headers); - } - - /** - * Get Message-ID value. - * - * @return the value - */ - public byte[] getMessageId() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); - } - - /** - * Set Message-ID value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); - } - - /** - * Get X-Mms-Response-Status. - * - * @return the value - */ - public int getResponseStatus() { - return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS); - } - - /** - * Set X-Mms-Response-Status. - * - * @param value the values - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setResponseStatus(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte[] getContentLocation() {return null;} - * public void setContentLocation(byte[] value) {} - * - * public EncodedStringValue getResponseText() {return null;} - * public void setResponseText(EncodedStringValue value) {} - * - * public byte getStoreStatus() {return 0x00;} - * public void setStoreStatus(byte value) {} - * - * public byte[] getStoreStatusText() {return null;} - * public void setStoreStatusText(byte[] value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/pdu/SendReq.java b/src/com/android/messaging/mmslib/pdu/SendReq.java deleted file mode 100644 index d173c0b..0000000 --- a/src/com/android/messaging/mmslib/pdu/SendReq.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.pdu; - -import android.util.Log; - -import com.android.messaging.mmslib.InvalidHeaderValueException; - -public class SendReq extends MultimediaMessagePdu { - private static final String TAG = "SendReq"; - - public SendReq() { - super(); - - try { - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); - setMmsVersion(PduHeaders.CURRENT_MMS_VERSION); - // TODO: Content-type must be decided according to whether - // SMIL part present. - setContentType("application/vnd.wap.multipart.related".getBytes()); - setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes())); - setTransactionId(generateTransactionId()); - } catch (InvalidHeaderValueException e) { - // Impossible to reach here since all headers we set above are valid. - Log.e(TAG, "Unexpected InvalidHeaderValueException.", e); - throw new RuntimeException(e); - } - } - - private byte[] generateTransactionId() { - String transactionId = "T" + Long.toHexString(System.currentTimeMillis()); - return transactionId.getBytes(); - } - - /** - * Constructor, used when composing a M-Send.req pdu. - * - * @param contentType the content type value - * @param from the from value - * @param mmsVersion current viersion of mms - * @param transactionId the transaction-id value - * @throws InvalidHeaderValueException if parameters are invalid. - * NullPointerException if contentType, form or - * transactionId is null. - */ - public SendReq(byte[] contentType, - EncodedStringValue from, - int mmsVersion, - byte[] transactionId) throws InvalidHeaderValueException { - super(); - setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); - setContentType(contentType); - setFrom(from); - setMmsVersion(mmsVersion); - setTransactionId(transactionId); - } - - /** - * Constructor with given headers. - * - * @param headers Headers for this PDU. - */ - SendReq(PduHeaders headers) { - super(headers); - } - - /** - * Constructor with given headers and body - * - * @param headers Headers for this PDU. - * @param body Body of this PDu. - */ - SendReq(PduHeaders headers, PduBody body) { - super(headers, body); - } - - /** - * Get Bcc value. - * - * @return the value - */ - public EncodedStringValue[] getBcc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.BCC); - } - - /** - * Add a "BCC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addBcc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC); - } - - /** - * Set "BCC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setBcc(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC); - } - - /** - * Get CC value. - * - * @return the value - */ - public EncodedStringValue[] getCc() { - return mPduHeaders.getEncodedStringValues(PduHeaders.CC); - } - - /** - * Add a "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void addCc(EncodedStringValue value) { - mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); - } - - /** - * Set "CC" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setCc(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.CC); - } - - /** - * Get Content-type value. - * - * @return the value - */ - public byte[] getContentType() { - return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); - } - - /** - * Set Content-type value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setContentType(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); - } - - /** - * Get X-Mms-Delivery-Report value. - * - * @return the value - */ - public int getDeliveryReport() { - return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); - } - - /** - * Set X-Mms-Delivery-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setDeliveryReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); - } - - /** - * Get X-Mms-Expiry value. - * - * Expiry-value = Value-length - * (Absolute-token Date-value | Relative-token Delta-seconds-value) - * - * @return the value - */ - public long getExpiry() { - return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); - } - - /** - * Set X-Mms-Expiry value. - * - * @param value the value - */ - public void setExpiry(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); - } - - /** - * Get X-Mms-MessageSize value. - * - * Expiry-value = size of message - * - * @return the value - */ - public long getMessageSize() { - return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); - } - - /** - * Set X-Mms-MessageSize value. - * - * @param value the value - */ - public void setMessageSize(long value) { - mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); - } - - /** - * Get X-Mms-Message-Class value. - * Message-class-value = Class-identifier | Token-text - * Class-identifier = Personal | Advertisement | Informational | Auto - * - * @return the value - */ - public byte[] getMessageClass() { - return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); - } - - /** - * Set X-Mms-Message-Class value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setMessageClass(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); - } - - /** - * Get X-Mms-Read-Report value. - * - * @return the value - */ - public int getReadReport() { - return mPduHeaders.getOctet(PduHeaders.READ_REPORT); - } - - /** - * Set X-Mms-Read-Report value. - * - * @param value the value - * @throws InvalidHeaderValueException if the value is invalid. - */ - public void setReadReport(int value) throws InvalidHeaderValueException { - mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); - } - - /** - * Set "To" value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTo(EncodedStringValue[] value) { - mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); - } - - /** - * Get X-Mms-Transaction-Id field value. - * - * @return the X-Mms-Report-Allowed value - */ - public byte[] getTransactionId() { - return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); - } - - /** - * Set X-Mms-Transaction-Id field value. - * - * @param value the value - * @throws NullPointerException if the value is null. - */ - public void setTransactionId(byte[] value) { - mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); - } - - /* - * Optional, not supported header fields: - * - * public byte getAdaptationAllowed() {return 0}; - * public void setAdaptationAllowed(btye value) {}; - * - * public byte[] getApplicId() {return null;} - * public void setApplicId(byte[] value) {} - * - * public byte[] getAuxApplicId() {return null;} - * public void getAuxApplicId(byte[] value) {} - * - * public byte getContentClass() {return 0x00;} - * public void setApplicId(byte value) {} - * - * public long getDeliveryTime() {return 0}; - * public void setDeliveryTime(long value) {}; - * - * public byte getDrmContent() {return 0x00;} - * public void setDrmContent(byte value) {} - * - * public MmFlagsValue getMmFlags() {return null;} - * public void setMmFlags(MmFlagsValue value) {} - * - * public MmStateValue getMmState() {return null;} - * public void getMmState(MmStateValue value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getReplyCharging() {return 0x00;} - * public void setReplyCharging(byte value) {} - * - * public byte getReplyChargingDeadline() {return 0x00;} - * public void setReplyChargingDeadline(byte value) {} - * - * public byte[] getReplyChargingId() {return 0x00;} - * public void setReplyChargingId(byte[] value) {} - * - * public long getReplyChargingSize() {return 0;} - * public void setReplyChargingSize(long value) {} - * - * public byte[] getReplyApplicId() {return 0x00;} - * public void setReplyApplicId(byte[] value) {} - * - * public byte getStore() {return 0x00;} - * public void setStore(byte value) {} - */ -} diff --git a/src/com/android/messaging/mmslib/util/AbstractCache.java b/src/com/android/messaging/mmslib/util/AbstractCache.java deleted file mode 100644 index db98bb9..0000000 --- a/src/com/android/messaging/mmslib/util/AbstractCache.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.util; - -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; - -public abstract class AbstractCache { - private static final String TAG = "AbstractCache"; - private static final boolean LOCAL_LOGV = false; - - private static final int MAX_CACHED_ITEMS = 500; - - private final SimpleArrayMap> mCacheMap; - - protected AbstractCache() { - mCacheMap = new SimpleArrayMap>(); - } - - public boolean put(K key, V value) { - if (LOCAL_LOGV) { - Log.v(TAG, "Trying to put " + key + " into cache."); - } - - if (mCacheMap.size() >= MAX_CACHED_ITEMS) { - // TODO: Should remove the oldest or least hit cached entry - // and then cache the new one. - if (LOCAL_LOGV) { - Log.v(TAG, "Failed! size limitation reached."); - } - return false; - } - - if (key != null) { - CacheEntry cacheEntry = new CacheEntry(); - cacheEntry.value = value; - mCacheMap.put(key, cacheEntry); - - if (LOCAL_LOGV) { - Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total."); - } - return true; - } - return false; - } - - public V get(K key) { - if (LOCAL_LOGV) { - Log.v(TAG, "Trying to get " + key + " from cache."); - } - - if (key != null) { - CacheEntry cacheEntry = mCacheMap.get(key); - if (cacheEntry != null) { - cacheEntry.hit++; - if (LOCAL_LOGV) { - Log.v(TAG, key + " hit " + cacheEntry.hit + " times."); - } - return cacheEntry.value; - } - } - return null; - } - - public V purge(K key) { - if (LOCAL_LOGV) { - Log.v(TAG, "Trying to purge " + key); - } - - CacheEntry v = mCacheMap.remove(key); - - if (LOCAL_LOGV) { - Log.v(TAG, mCacheMap.size() + " items cached."); - } - - return v != null ? v.value : null; - } - - public void purgeAll() { - if (LOCAL_LOGV) { - Log.v(TAG, "Purging cache, " + mCacheMap.size() - + " items dropped."); - } - mCacheMap.clear(); - } - - public int size() { - return mCacheMap.size(); - } - - private static class CacheEntry { - - int hit; - - V value; - } -} diff --git a/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java b/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java deleted file mode 100644 index c38b179..0000000 --- a/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.messaging.mmslib.util; - -import android.content.Context; -import android.drm.DrmManagerClient; -import android.util.Log; - -public class DownloadDrmHelper { - private static final String TAG = "DownloadDrmHelper"; - - /** The MIME type of special DRM files */ - public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message"; - - /** The extensions of special DRM files */ - public static final String EXTENSION_DRM_MESSAGE = ".dm"; - - public static final String EXTENSION_INTERNAL_FWDL = ".fl"; - - /** - * Checks if the Media Type is a DRM Media Type - * - * @param drmManagerClient A DrmManagerClient - * @param mimetype Media Type to check - * @return True if the Media Type is DRM else false - */ - public static boolean isDrmMimeType(Context context, String mimetype) { - boolean result = false; - if (context != null) { - try { - DrmManagerClient drmClient = new DrmManagerClient(context); - if (drmClient != null && mimetype != null && mimetype.length() > 0) { - result = drmClient.canHandle("", mimetype); - } - } catch (IllegalArgumentException e) { - Log.w(TAG, - "DrmManagerClient instance could not be created, context is Illegal."); - } catch (IllegalStateException e) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); - } - } - return result; - } - - /** - * Checks if the Media Type needs to be DRM converted - * - * @param mimetype Media type of the content - * @return True if convert is needed else false - */ - public static boolean isDrmConvertNeeded(String mimetype) { - return MIMETYPE_DRM_MESSAGE.equals(mimetype); - } - - /** - * Modifies the file extension for a DRM Forward Lock file NOTE: This - * function shouldn't be called if the file shouldn't be DRM converted - */ - public static String modifyDrmFwLockFileExtension(String filename) { - if (filename != null) { - int extensionIndex; - extensionIndex = filename.lastIndexOf("."); - if (extensionIndex != -1) { - filename = filename.substring(0, extensionIndex); - } - filename = filename.concat(EXTENSION_INTERNAL_FWDL); - } - return filename; - } - - /** - * Gets the original mime type of DRM protected content. - * - * @param context The context - * @param path Path to the file - * @param containingMime The current mime type of of the file i.e. the - * containing mime type - * @return The original mime type of the file if DRM protected else the - * currentMime - */ - public static String getOriginalMimeType(Context context, String path, String containingMime) { - String result = containingMime; - DrmManagerClient drmClient = new DrmManagerClient(context); - try { - if (drmClient.canHandle(path, null)) { - result = drmClient.getOriginalMimeType(path); - } - } catch (IllegalArgumentException ex) { - Log.w(TAG, - "Can't get original mime type since path is null or empty string."); - } catch (IllegalStateException ex) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); - } - return result; - } -} diff --git a/src/com/android/messaging/mmslib/util/DrmConvertSession.java b/src/com/android/messaging/mmslib/util/DrmConvertSession.java deleted file mode 100644 index 604e391..0000000 --- a/src/com/android/messaging/mmslib/util/DrmConvertSession.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.android.messaging.mmslib.util; - -import android.content.Context; -import android.drm.DrmConvertedStatus; -import android.drm.DrmManagerClient; -import android.util.Log; - -import com.android.messaging.mmslib.Downloads; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; - - -public class DrmConvertSession { - private DrmManagerClient mDrmClient; - private int mConvertSessionId; - private static final String TAG = "DrmConvertSession"; - - private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) { - mDrmClient = drmClient; - mConvertSessionId = convertSessionId; - } - - /** - * Start of converting a file. - * - * @param context The context of the application running the convert session. - * @param mimeType Mimetype of content that shall be converted. - * @return A convert session or null in case an error occurs. - */ - public static DrmConvertSession open(Context context, String mimeType) { - DrmManagerClient drmClient = null; - int convertSessionId = -1; - if (context != null && mimeType != null && !mimeType.equals("")) { - try { - drmClient = new DrmManagerClient(context); - try { - convertSessionId = drmClient.openConvertSession(mimeType); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Conversion of Mimetype: " + mimeType - + " is not supported.", e); - } catch (IllegalStateException e) { - Log.w(TAG, "Could not access Open DrmFramework.", e); - } - } catch (IllegalArgumentException e) { - Log.w(TAG, - "DrmManagerClient instance could not be created, context is Illegal."); - } catch (IllegalStateException e) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); - } - } - - if (drmClient == null || convertSessionId < 0) { - return null; - } else { - return new DrmConvertSession(drmClient, convertSessionId); - } - } - - /** - * Convert a buffer of data to protected format. - * - * @param buffer Buffer filled with data to convert. - * @param size The number of bytes that shall be converted. - * @return A Buffer filled with converted data, if execution is ok, in all - * other case null. - */ - public byte[] convert(byte[] inBuffer, int size) { - byte[] result = null; - if (inBuffer != null) { - DrmConvertedStatus convertedStatus = null; - try { - if (size != inBuffer.length) { - byte[] buf = new byte[size]; - System.arraycopy(inBuffer, 0, buf, 0, size); - convertedStatus = mDrmClient.convertData(mConvertSessionId, buf); - } else { - convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer); - } - - if (convertedStatus != null && - convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK && - convertedStatus.convertedData != null) { - result = convertedStatus.convertedData; - } - } catch (IllegalArgumentException e) { - Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: " - + mConvertSessionId, e); - } catch (IllegalStateException e) { - Log.w(TAG, "Could not convert data. Convertsession: " + - mConvertSessionId, e); - } - } else { - throw new IllegalArgumentException("Parameter inBuffer is null"); - } - return result; - } - - /** - * Ends a conversion session of a file. - * - * @param fileName The filename of the converted file. - * @return Downloads.Impl.STATUS_SUCCESS if execution is ok. - * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not - * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem - * occurs when accessing drm framework. - * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred. - */ - public int close(String filename) { - DrmConvertedStatus convertedStatus = null; - int result = Downloads.Impl.STATUS_UNKNOWN_ERROR; - if (mDrmClient != null && mConvertSessionId >= 0) { - try { - convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId); - if (convertedStatus == null || - convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK || - convertedStatus.convertedData == null) { - result = Downloads.Impl.STATUS_NOT_ACCEPTABLE; - } else { - RandomAccessFile rndAccessFile = null; - try { - rndAccessFile = new RandomAccessFile(filename, "rw"); - rndAccessFile.seek(convertedStatus.offset); - rndAccessFile.write(convertedStatus.convertedData); - result = Downloads.Impl.STATUS_SUCCESS; - } catch (FileNotFoundException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; - Log.w(TAG, "File: " + filename + " could not be found.", e); - } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; - Log.w(TAG, "Could not access File: " + filename + " .", e); - } catch (IllegalArgumentException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; - Log.w(TAG, "Could not open file in mode: rw", e); - } catch (SecurityException e) { - Log.w(TAG, "Access to File: " + filename + - " was denied denied by SecurityManager.", e); - } finally { - if (rndAccessFile != null) { - try { - rndAccessFile.close(); - } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; - Log.w(TAG, "Failed to close File:" + filename - + ".", e); - } - } - } - } - } catch (IllegalStateException e) { - Log.w(TAG, "Could not close convertsession. Convertsession: " + - mConvertSessionId, e); - } - } - return result; - } -} diff --git a/src/com/android/messaging/mmslib/util/PduCache.java b/src/com/android/messaging/mmslib/util/PduCache.java deleted file mode 100644 index 9a400c0..0000000 --- a/src/com/android/messaging/mmslib/util/PduCache.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.util; - -import android.content.ContentUris; -import android.content.UriMatcher; -import android.net.Uri; -import android.provider.Telephony.Mms; -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import java.util.HashSet; - -public final class PduCache extends AbstractCache { - private static final String TAG = "PduCache"; - private static final boolean LOCAL_LOGV = false; - - private static final int MMS_ALL = 0; - private static final int MMS_ALL_ID = 1; - private static final int MMS_INBOX = 2; - private static final int MMS_INBOX_ID = 3; - private static final int MMS_SENT = 4; - private static final int MMS_SENT_ID = 5; - private static final int MMS_DRAFTS = 6; - private static final int MMS_DRAFTS_ID = 7; - private static final int MMS_OUTBOX = 8; - private static final int MMS_OUTBOX_ID = 9; - private static final int MMS_CONVERSATION = 10; - private static final int MMS_CONVERSATION_ID = 11; - - private static final UriMatcher URI_MATCHER; - private static final SparseArray MATCH_TO_MSGBOX_ID_MAP; - - private static PduCache sInstance; - - static { - URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); - URI_MATCHER.addURI("mms", null, MMS_ALL); - URI_MATCHER.addURI("mms", "#", MMS_ALL_ID); - URI_MATCHER.addURI("mms", "inbox", MMS_INBOX); - URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID); - URI_MATCHER.addURI("mms", "sent", MMS_SENT); - URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID); - URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS); - URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID); - URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX); - URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID); - URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION); - URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID); - - MATCH_TO_MSGBOX_ID_MAP = new SparseArray(); - MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX); - MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT); - MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS); - MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX); - } - - private final SparseArray> mMessageBoxes; - private final SimpleArrayMap> mThreads; - private final HashSet mUpdating; - - private PduCache() { - mMessageBoxes = new SparseArray>(); - mThreads = new SimpleArrayMap>(); - mUpdating = new HashSet(); - } - - public static final synchronized PduCache getInstance() { - if (sInstance == null) { - if (LOCAL_LOGV) { - Log.v(TAG, "Constructing new PduCache instance."); - } - sInstance = new PduCache(); - } - return sInstance; - } - - @Override - public synchronized boolean put(Uri uri, PduCacheEntry entry) { - int msgBoxId = entry.getMessageBox(); - HashSet msgBox = mMessageBoxes.get(msgBoxId); - if (msgBox == null) { - msgBox = new HashSet(); - mMessageBoxes.put(msgBoxId, msgBox); - } - - long threadId = entry.getThreadId(); - HashSet thread = mThreads.get(threadId); - if (thread == null) { - thread = new HashSet(); - mThreads.put(threadId, thread); - } - - Uri finalKey = normalizeKey(uri); - boolean result = super.put(finalKey, entry); - if (result) { - msgBox.add(finalKey); - thread.add(finalKey); - } - setUpdating(uri, false); - return result; - } - - public synchronized void setUpdating(Uri uri, boolean updating) { - if (updating) { - mUpdating.add(uri); - } else { - mUpdating.remove(uri); - } - } - - public synchronized boolean isUpdating(Uri uri) { - return mUpdating.contains(uri); - } - - @Override - public synchronized PduCacheEntry purge(Uri uri) { - int match = URI_MATCHER.match(uri); - switch (match) { - case MMS_ALL_ID: - return purgeSingleEntry(uri); - case MMS_INBOX_ID: - case MMS_SENT_ID: - case MMS_DRAFTS_ID: - case MMS_OUTBOX_ID: - String msgId = uri.getLastPathSegment(); - return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId)); - // Implicit batch of purge, return null. - case MMS_ALL: - case MMS_CONVERSATION: - purgeAll(); - return null; - case MMS_INBOX: - case MMS_SENT: - case MMS_DRAFTS: - case MMS_OUTBOX: - purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match)); - return null; - case MMS_CONVERSATION_ID: - purgeByThreadId(ContentUris.parseId(uri)); - return null; - default: - return null; - } - } - - private PduCacheEntry purgeSingleEntry(Uri key) { - mUpdating.remove(key); - PduCacheEntry entry = super.purge(key); - if (entry != null) { - removeFromThreads(key, entry); - removeFromMessageBoxes(key, entry); - return entry; - } - return null; - } - - @Override - public synchronized void purgeAll() { - super.purgeAll(); - - mMessageBoxes.clear(); - mThreads.clear(); - mUpdating.clear(); - } - - /** - * @param uri The Uri to be normalized. - * @return Uri The normalized key of cached entry. - */ - private Uri normalizeKey(Uri uri) { - int match = URI_MATCHER.match(uri); - Uri normalizedKey = null; - - switch (match) { - case MMS_ALL_ID: - normalizedKey = uri; - break; - case MMS_INBOX_ID: - case MMS_SENT_ID: - case MMS_DRAFTS_ID: - case MMS_OUTBOX_ID: - String msgId = uri.getLastPathSegment(); - normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId); - break; - default: - return null; - } - - if (LOCAL_LOGV) { - Log.v(TAG, uri + " -> " + normalizedKey); - } - return normalizedKey; - } - - private void purgeByMessageBox(Integer msgBoxId) { - if (LOCAL_LOGV) { - Log.v(TAG, "Purge cache in message box: " + msgBoxId); - } - - if (msgBoxId != null) { - HashSet msgBox = mMessageBoxes.get(msgBoxId); - mMessageBoxes.remove(msgBoxId); - if (msgBox != null) { - for (Uri key : msgBox) { - mUpdating.remove(key); - PduCacheEntry entry = super.purge(key); - if (entry != null) { - removeFromThreads(key, entry); - } - } - } - } - } - - private void removeFromThreads(Uri key, PduCacheEntry entry) { - HashSet thread = mThreads.get(entry.getThreadId()); - if (thread != null) { - thread.remove(key); - } - } - - private void purgeByThreadId(long threadId) { - if (LOCAL_LOGV) { - Log.v(TAG, "Purge cache in thread: " + threadId); - } - - HashSet thread = mThreads.remove(threadId); - if (thread != null) { - for (Uri key : thread) { - mUpdating.remove(key); - PduCacheEntry entry = super.purge(key); - if (entry != null) { - removeFromMessageBoxes(key, entry); - } - } - } - } - - private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) { - HashSet msgBox = mThreads.get(Long.valueOf(entry.getMessageBox())); - if (msgBox != null) { - msgBox.remove(key); - } - } -} diff --git a/src/com/android/messaging/mmslib/util/PduCacheEntry.java b/src/com/android/messaging/mmslib/util/PduCacheEntry.java deleted file mode 100644 index b287f00..0000000 --- a/src/com/android/messaging/mmslib/util/PduCacheEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2008 Esmertec AG. - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.mmslib.util; - -import com.android.messaging.mmslib.pdu.GenericPdu; - -public final class PduCacheEntry { - private final GenericPdu mPdu; - private final int mMessageBox; - private final long mThreadId; - - public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) { - mPdu = pdu; - mMessageBox = msgBox; - mThreadId = threadId; - } - - public GenericPdu getPdu() { - return mPdu; - } - - public int getMessageBox() { - return mMessageBox; - } - - public long getThreadId() { - return mThreadId; - } -} diff --git a/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java b/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java deleted file mode 100644 index 9e09a2a..0000000 --- a/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.provider.Telephony; - -import com.android.messaging.util.ContentType; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -/** - * This receiver is used to abort MMS WAP broadcasts pre-KLP when SMS is enabled. - */ -public class AbortMmsWapPushReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) - && ContentType.MMS_MESSAGE.equals(intent.getType())) { - // If we are enabled, it's our job to stop the broadcast from continuing. This - // receiver is not used on KLP but we do an extra check here just to make sure. - if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) { - abortBroadcast(); - } - } - } -} diff --git a/src/com/android/messaging/receiver/AbortSmsReceiver.java b/src/com/android/messaging/receiver/AbortSmsReceiver.java deleted file mode 100644 index f4491d8..0000000 --- a/src/com/android/messaging/receiver/AbortSmsReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -/** - * This receiver is used to abort SMS broadcasts pre-KLP when SMS is enabled. - */ -public final class AbortSmsReceiver extends BroadcastReceiver { - - @Override - public void onReceive(final Context context, final Intent intent) { - // If we are enabled, it's our job to stop the broadcast from continuing. This - // receiver is not used on KLP but we do an extra check here just to make sure. - if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) { - if (!SmsReceiver.shouldIgnoreMessage(intent)) { - abortBroadcast(); - } - } - } -} diff --git a/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java b/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java deleted file mode 100644 index be0d296..0000000 --- a/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.android.messaging.BugleApplication; -import com.android.messaging.Factory; -import com.android.messaging.datamodel.action.UpdateMessageNotificationAction; -import com.android.messaging.util.BuglePrefsKeys; -import com.android.messaging.util.LogUtil; - -/** - * Receives notification of boot completion and package replacement - */ -public class BootAndPackageReplacedReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) - || Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) { - // Repost unseen notifications - Factory.get().getApplicationPrefs().putLong( - BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE); - UpdateMessageNotificationAction.updateMessageNotification(); - - BugleApplication.updateAppConfig(context); - } else { - LogUtil.i(LogUtil.BUGLE_TAG, "BootAndPackageReplacedReceiver got unexpected action: " - + intent.getAction()); - } - } -} - diff --git a/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java b/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java deleted file mode 100644 index d5153a0..0000000 --- a/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.android.messaging.datamodel.ParticipantRefresh; - -/** - * Responds to default SMS subscription selection changes from system Settings. - */ -public class DefaultSmsSubscriptionChangeReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - ParticipantRefresh.refreshSelfParticipants(); - } -} diff --git a/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java b/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java deleted file mode 100644 index a5c247c..0000000 --- a/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.provider.Telephony; - -import com.android.messaging.util.ContentType; -import com.android.messaging.util.PhoneUtils; - -/** - * Class that handles MMS WAP push intent from telephony on KLP+ Devices. - */ -public class MmsWapPushDeliverReceiver extends BroadcastReceiver { - - @Override - public void onReceive(final Context context, final Intent intent) { - if (Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION.equals(intent.getAction()) - && ContentType.MMS_MESSAGE.equals(intent.getType())) { - // Always convert negative subIds into -1 - int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem( - intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION); - byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA); - MmsWapPushReceiver.mmsReceived(subId, data); - } - } -} diff --git a/src/com/android/messaging/receiver/MmsWapPushReceiver.java b/src/com/android/messaging/receiver/MmsWapPushReceiver.java deleted file mode 100644 index 29cf0db..0000000 --- a/src/com/android/messaging/receiver/MmsWapPushReceiver.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.provider.Telephony; - -import com.android.messaging.datamodel.action.ReceiveMmsMessageAction; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.PhoneUtils; - -/** - * Class that handles MMS WAP push intent from telephony on pre-KLP Devices. - */ -public class MmsWapPushReceiver extends BroadcastReceiver { - static final String EXTRA_SUBSCRIPTION = "subscription"; - static final String EXTRA_DATA = "data"; - - @Override - public void onReceive(final Context context, final Intent intent) { - if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) - && ContentType.MMS_MESSAGE.equals(intent.getType())) { - if (PhoneUtils.getDefault().isSmsEnabled()) { - // Always convert negative subIds into -1 - final int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem( - intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION); - final byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA); - mmsReceived(subId, data); - } - } - } - - static void mmsReceived(final int subId, final byte[] data) { - if (!PhoneUtils.getDefault().isSmsEnabled()) { - return; - } - - final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(subId, data); - action.start(); - } -} - diff --git a/src/com/android/messaging/receiver/NotificationReceiver.java b/src/com/android/messaging/receiver/NotificationReceiver.java deleted file mode 100644 index bbb847d..0000000 --- a/src/com/android/messaging/receiver/NotificationReceiver.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.action.MarkAsSeenAction; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.ConversationIdSet; -import com.android.messaging.util.LogUtil; - -// NotificationReceiver is used to handle delete intents from notifications. When a user -// clears all notifications or swipes a bugle notification away, the intent we pass in as -// the delete intent will get handled here. -public class NotificationReceiver extends BroadcastReceiver { - // Logging - public static final String TAG = LogUtil.BUGLE_TAG; - public static final boolean VERBOSE = false; - - @Override - public void onReceive(final Context context, final Intent intent) { - if (VERBOSE) { - LogUtil.v(TAG, "NotificationReceiver.onReceive: intent " + intent); - } - if (intent.getAction().equals(UIIntents.ACTION_RESET_NOTIFICATIONS)) { - final String conversationIdSetString = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID_SET); - final int notificationTargets = intent.getIntExtra( - UIIntents.UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, BugleNotifications.UPDATE_ALL); - if (conversationIdSetString == null) { - BugleNotifications.markAllMessagesAsSeen(); - } else { - for (final String conversationId : - ConversationIdSet.createSet(conversationIdSetString)) { - MarkAsSeenAction.markAsSeen(conversationId); - BugleNotifications.resetLastMessageDing(conversationId); - } - } - } - } -} \ No newline at end of file diff --git a/src/com/android/messaging/receiver/SendStatusReceiver.java b/src/com/android/messaging/receiver/SendStatusReceiver.java deleted file mode 100644 index fc0e8c9..0000000 --- a/src/com/android/messaging/receiver/SendStatusReceiver.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.telephony.SmsMessage; - -import com.android.messaging.datamodel.action.ProcessDeliveryReportAction; -import com.android.messaging.datamodel.action.ProcessDownloadedMmsAction; -import com.android.messaging.datamodel.action.ProcessSentMessageAction; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.sms.SmsSender; -import com.android.messaging.util.LogUtil; - -/** - * The SMS sent and delivery intent receiver. - * - * This class just simply forwards the intents to proper recipients for actual handling. - */ -public class SendStatusReceiver extends BroadcastReceiver { - public static final String MESSAGE_SENT_ACTION = - "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT"; - public static final String MESSAGE_DELIVERED_ACTION = - "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED"; - public static final String MMS_SENT_ACTION = - "com.android.messaging.receiver.SendStatusReceiver.MMS_SENT"; - public static final String MMS_DOWNLOADED_ACTION = - "com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED"; - - // Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage. - public static final String EXTRA_ERROR_CODE = "errorCode"; - - public static final String EXTRA_PART_ID = "partId"; - public static final String EXTRA_SUB_ID = "subId"; - - public static final int NO_ERROR_CODE = 0; - public static final int NO_PART_ID = -1; - - @Override - public void onReceive(final Context context, final Intent intent) { - // This will be called on the main thread (so it should exit quickly) - final String action = intent.getAction(); - final int resultCode = getResultCode(); - if (MESSAGE_SENT_ACTION.equals(action)) { - final Uri requestId = intent.getData(); - SmsSender.setResult( - requestId, - resultCode, - intent.getIntExtra(EXTRA_ERROR_CODE, NO_ERROR_CODE), - intent.getIntExtra(EXTRA_PART_ID, NO_PART_ID), - intent.getIntExtra(EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID)); - } else if (MMS_SENT_ACTION.equals(action)) { - final Uri messageUri = intent.getData(); - ProcessSentMessageAction.processMmsSent(resultCode, messageUri, - intent.getExtras()); - } else if (MMS_DOWNLOADED_ACTION.equals(action)) { - ProcessDownloadedMmsAction.processMessageDownloaded(resultCode, - intent.getExtras()); - } else if (MESSAGE_DELIVERED_ACTION.equals(action)) { - final SmsMessage smsMessage = MmsUtils.getSmsMessageFromDeliveryReport(intent); - final Uri smsMessageUri = intent.getData(); - if (smsMessage == null) { - LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: empty report message"); - return; - } - int status = 0; - try { - status = smsMessage.getStatus(); - } catch (final NullPointerException e) { - // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access - // the methods on it although the SmsMessage itself is not null. - LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: NPE inside SmsMessage"); - return; - } - ProcessDeliveryReportAction.deliveryReportReceived(smsMessageUri, status); - } - } -} diff --git a/src/com/android/messaging/receiver/SmsDeliverReceiver.java b/src/com/android/messaging/receiver/SmsDeliverReceiver.java deleted file mode 100644 index 6a9b66c..0000000 --- a/src/com/android/messaging/receiver/SmsDeliverReceiver.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - * Class that receives incoming SMS messages on KLP+ Devices. - */ -public final class SmsDeliverReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - SmsReceiver.deliverSmsIntent(context, intent); - } -} diff --git a/src/com/android/messaging/receiver/SmsReceiver.java b/src/com/android/messaging/receiver/SmsReceiver.java deleted file mode 100644 index db9b4bb..0000000 --- a/src/com/android/messaging/receiver/SmsReceiver.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.provider.Telephony; -import android.provider.Telephony.Sms; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationCompat.Builder; -import android.support.v4.app.NotificationCompat.Style; -import android.support.v4.app.NotificationManagerCompat; - -import java.util.ArrayList; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.BugleNotifications; -import com.android.messaging.datamodel.MessageNotificationState; -import com.android.messaging.datamodel.NoConfirmationSmsSendService; -import com.android.messaging.datamodel.action.ReceiveSmsMessageAction; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PendingIntentConstants; -import com.android.messaging.util.PhoneUtils; - -/** - * Class that receives incoming SMS messages through android.provider.Telephony.SMS_RECEIVED - * - * This class serves two purposes: - * - Process phone verification SMS messages - * - Handle SMS messages when the user has enabled us to be the default SMS app (Pre-KLP) - */ -public final class SmsReceiver extends BroadcastReceiver { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static ArrayList sIgnoreSmsPatterns; - - /** - * Enable or disable the SmsReceiver as appropriate. Pre-KLP we use this receiver for - * receiving incoming SMS messages. For KLP+ this receiver is not used when running as the - * primary user and the SmsDeliverReceiver is used for receiving incoming SMS messages. - * When running as a secondary user, this receiver is still used to trigger the incoming - * notification. - */ - public static void updateSmsReceiveHandler(final Context context) { - boolean smsReceiverEnabled; - boolean mmsWapPushReceiverEnabled; - boolean respondViaMessageEnabled; - boolean broadcastAbortEnabled; - - if (OsUtil.isAtLeastKLP()) { - // When we're running as the secondary user, we don't get the new SMS_DELIVER intent, - // only the primary user receives that. As secondary, we need to go old-school and - // listen for the SMS_RECEIVED intent. For the secondary user, use this SmsReceiver - // for both sms and mms notification. For the primary user on KLP (and above), we don't - // use the SmsReceiver. - smsReceiverEnabled = OsUtil.isSecondaryUser(); - // On KLP use the new deliver event for mms - mmsWapPushReceiverEnabled = false; - // On KLP we need to always enable this handler to show in the list of sms apps - respondViaMessageEnabled = true; - // On KLP we don't need to abort the broadcast - broadcastAbortEnabled = false; - } else { - // On JB we use the sms receiver for both sms/mms delivery - final boolean carrierSmsEnabled = PhoneUtils.getDefault().isSmsEnabled(); - smsReceiverEnabled = carrierSmsEnabled; - - // On JB we use the mms receiver when sms/mms is enabled - mmsWapPushReceiverEnabled = carrierSmsEnabled; - // On JB this is dynamic to make sure we don't show in dialer if sms is disabled - respondViaMessageEnabled = carrierSmsEnabled; - // On JB we need to abort broadcasts if SMS is enabled - broadcastAbortEnabled = carrierSmsEnabled; - } - - final PackageManager packageManager = context.getPackageManager(); - final boolean logv = LogUtil.isLoggable(TAG, LogUtil.VERBOSE); - if (smsReceiverEnabled) { - if (logv) { - LogUtil.v(TAG, "Enabling SMS message receiving"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, SmsReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - - } else { - if (logv) { - LogUtil.v(TAG, "Disabling SMS message receiving"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, SmsReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - } - if (mmsWapPushReceiverEnabled) { - if (logv) { - LogUtil.v(TAG, "Enabling MMS message receiving"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, MmsWapPushReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - } else { - if (logv) { - LogUtil.v(TAG, "Disabling MMS message receiving"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, MmsWapPushReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - } - if (broadcastAbortEnabled) { - if (logv) { - LogUtil.v(TAG, "Enabling SMS/MMS broadcast abort"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, AbortSmsReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - packageManager.setComponentEnabledSetting( - new ComponentName(context, AbortMmsWapPushReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - } else { - if (logv) { - LogUtil.v(TAG, "Disabling SMS/MMS broadcast abort"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, AbortSmsReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - packageManager.setComponentEnabledSetting( - new ComponentName(context, AbortMmsWapPushReceiver.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - } - if (respondViaMessageEnabled) { - if (logv) { - LogUtil.v(TAG, "Enabling respond via message intent"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, NoConfirmationSmsSendService.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - } else { - if (logv) { - LogUtil.v(TAG, "Disabling respond via message intent"); - } - packageManager.setComponentEnabledSetting( - new ComponentName(context, NoConfirmationSmsSendService.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - } - } - - private static final String EXTRA_ERROR_CODE = "errorCode"; - private static final String EXTRA_SUB_ID = "subscription"; - - public static void deliverSmsIntent(final Context context, final Intent intent) { - final android.telephony.SmsMessage[] messages = getMessagesFromIntent(intent); - - // Check messages for validity - if (messages == null || messages.length < 1) { - LogUtil.e(TAG, "processReceivedSms: null or zero or ignored message"); - return; - } - - final int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, 0); - // Always convert negative subIds into -1 - int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem( - intent, EXTRA_SUB_ID); - deliverSmsMessages(context, subId, errorCode, messages); - if (MmsUtils.isDumpSmsEnabled()) { - final String format = null; - DebugUtils.dumpSms(messages[0].getTimestampMillis(), messages, format); - } - } - - public static void deliverSmsMessages(final Context context, final int subId, - final int errorCode, final android.telephony.SmsMessage[] messages) { - final ContentValues messageValues = - MmsUtils.parseReceivedSmsMessage(context, messages, errorCode); - - LogUtil.v(TAG, "SmsReceiver.deliverSmsMessages"); - - final long nowInMillis = System.currentTimeMillis(); - final long receivedTimestampMs = MmsUtils.getMessageDate(messages[0], nowInMillis); - - messageValues.put(Sms.Inbox.DATE, receivedTimestampMs); - // Default to unread and unseen for us but ReceiveSmsMessageAction will override - // seen for the telephony db. - messageValues.put(Sms.Inbox.READ, 0); - messageValues.put(Sms.Inbox.SEEN, 0); - if (OsUtil.isAtLeastL_MR1()) { - messageValues.put(Sms.SUBSCRIPTION_ID, subId); - } - - if (messages[0].getMessageClass() == android.telephony.SmsMessage.MessageClass.CLASS_0 || - DebugUtils.debugClassZeroSmsEnabled()) { - Factory.get().getUIIntents().launchClassZeroActivity(context, messageValues); - } else { - final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(messageValues); - action.start(); - } - } - - @Override - public void onReceive(final Context context, final Intent intent) { - LogUtil.v(TAG, "SmsReceiver.onReceive " + intent); - // On KLP+ we only take delivery of SMS messages in SmsDeliverReceiver. - if (PhoneUtils.getDefault().isSmsEnabled()) { - final String action = intent.getAction(); - if (OsUtil.isSecondaryUser() && - (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action) || - // TODO: update this with the actual constant from Telephony - "android.provider.Telephony.MMS_DOWNLOADED".equals(action))) { - postNewMessageSecondaryUserNotification(); - } else if (!OsUtil.isAtLeastKLP()) { - deliverSmsIntent(context, intent); - } - } - } - - private static class SecondaryUserNotificationState extends MessageNotificationState { - SecondaryUserNotificationState() { - super(null); - } - - @Override - protected Style build(Builder builder) { - return null; - } - - @Override - public boolean getNotificationVibrate() { - return true; - } - } - - public static void postNewMessageSecondaryUserNotification() { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final PendingIntent pendingIntent = UIIntents.get() - .getPendingIntentForSecondaryUserNewMessageNotification(context); - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - builder.setContentTitle(resources.getString(R.string.secondary_user_new_message_title)) - .setTicker(resources.getString(R.string.secondary_user_new_message_ticker)) - .setSmallIcon(R.drawable.ic_sms_light) - // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker - // isn't displayed. - .setPriority(Notification.PRIORITY_HIGH) - .setContentIntent(pendingIntent); - - final NotificationCompat.BigTextStyle bigTextStyle = - new NotificationCompat.BigTextStyle(builder); - bigTextStyle.bigText(resources.getString(R.string.secondary_user_new_message_title)); - final Notification notification = bigTextStyle.build(); - - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - - int defaults = Notification.DEFAULT_LIGHTS; - if (BugleNotifications.shouldVibrate(new SecondaryUserNotificationState())) { - defaults |= Notification.DEFAULT_VIBRATE; - } - notification.defaults = defaults; - - notificationManager.notify(getNotificationTag(), - PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID, notification); - } - - /** - * Cancel the notification - */ - public static void cancelSecondaryUserNotification() { - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - notificationManager.cancel(getNotificationTag(), - PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID); - } - - private static String getNotificationTag() { - return Factory.get().getApplicationContext().getPackageName() + ":secondaryuser"; - } - - /** - * Compile all of the patterns we check for to ignore system SMS messages. - */ - private static void compileIgnoreSmsPatterns() { - // Get the pattern set from GServices - final String smsIgnoreRegex = BugleGservices.get().getString( - BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX, - BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT); - if (smsIgnoreRegex != null) { - final String[] ignoreSmsExpressions = smsIgnoreRegex.split("\n"); - if (ignoreSmsExpressions.length != 0) { - sIgnoreSmsPatterns = new ArrayList(); - for (int i = 0; i < ignoreSmsExpressions.length; i++) { - try { - sIgnoreSmsPatterns.add(Pattern.compile(ignoreSmsExpressions[i])); - } catch (PatternSyntaxException e) { - LogUtil.e(TAG, "compileIgnoreSmsPatterns: Skipping bad expression: " + - ignoreSmsExpressions[i]); - } - } - } - } - } - - /** - * Get the SMS messages from the specified SMS intent. - * @return the messages. If there is an error or the message should be ignored, return null. - */ - public static android.telephony.SmsMessage[] getMessagesFromIntent(Intent intent) { - final android.telephony.SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent); - - // Check messages for validity - if (messages == null || messages.length < 1) { - return null; - } - // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access - // the methods on it although the SmsMessage itself is not null. So do this check - // before we do anything on the parsed SmsMessages. - try { - final String messageBody = messages[0].getDisplayMessageBody(); - if (messageBody != null) { - // Compile patterns if necessary - if (sIgnoreSmsPatterns == null) { - compileIgnoreSmsPatterns(); - } - // Check against filters - for (final Pattern pattern : sIgnoreSmsPatterns) { - if (pattern.matcher(messageBody).matches()) { - return null; - } - } - } - } catch (final NullPointerException e) { - LogUtil.e(TAG, "shouldIgnoreMessage: NPE inside SmsMessage"); - return null; - } - return messages; - } - - - /** - * Check the specified SMS intent to see if the message should be ignored - * @return true if the message should be ignored - */ - public static boolean shouldIgnoreMessage(Intent intent) { - return getMessagesFromIntent(intent) == null; - } -} diff --git a/src/com/android/messaging/receiver/StorageStatusReceiver.java b/src/com/android/messaging/receiver/StorageStatusReceiver.java deleted file mode 100644 index bee899c..0000000 --- a/src/com/android/messaging/receiver/StorageStatusReceiver.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.android.messaging.sms.SmsStorageStatusManager; - -/** - * Receiver that listens on storage status changes - */ -public class StorageStatusReceiver extends BroadcastReceiver { - - @Override - public void onReceive(final Context context, final Intent intent) { - if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) { - SmsStorageStatusManager.handleStorageLow(); - } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) { - SmsStorageStatusManager.handleStorageOk(); - } - } -} diff --git a/src/com/android/messaging/sms/ApnDatabase.java b/src/com/android/messaging/sms/ApnDatabase.java deleted file mode 100644 index a8d0d0c..0000000 --- a/src/com/android/messaging/sms/ApnDatabase.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.provider.Telephony; -import android.text.TextUtils; -import android.util.Log; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.collect.Lists; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/* - * Database helper class for looking up APNs. This database has a single table - * which stores the APNs that are initially created from an xml file. - */ -public class ApnDatabase extends SQLiteOpenHelper { - private static final int DB_VERSION = 3; // added sub_id columns - - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final boolean DEBUG = false; - - private static Context sContext; - private static ApnDatabase sApnDatabase; - - private static final String APN_DATABASE_NAME = "apn.db"; - - /** table for carrier APN's */ - public static final String APN_TABLE = "apn"; - - // APN table - private static final String APN_TABLE_SQL = - "CREATE TABLE " + APN_TABLE + - "(_id INTEGER PRIMARY KEY," + - Telephony.Carriers.NAME + " TEXT," + - Telephony.Carriers.NUMERIC + " TEXT," + - Telephony.Carriers.MCC + " TEXT," + - Telephony.Carriers.MNC + " TEXT," + - Telephony.Carriers.APN + " TEXT," + - Telephony.Carriers.USER + " TEXT," + - Telephony.Carriers.SERVER + " TEXT," + - Telephony.Carriers.PASSWORD + " TEXT," + - Telephony.Carriers.PROXY + " TEXT," + - Telephony.Carriers.PORT + " TEXT," + - Telephony.Carriers.MMSPROXY + " TEXT," + - Telephony.Carriers.MMSPORT + " TEXT," + - Telephony.Carriers.MMSC + " TEXT," + - Telephony.Carriers.AUTH_TYPE + " INTEGER," + - Telephony.Carriers.TYPE + " TEXT," + - Telephony.Carriers.CURRENT + " INTEGER," + - Telephony.Carriers.PROTOCOL + " TEXT," + - Telephony.Carriers.ROAMING_PROTOCOL + " TEXT," + - Telephony.Carriers.CARRIER_ENABLED + " BOOLEAN," + - Telephony.Carriers.BEARER + " INTEGER," + - Telephony.Carriers.MVNO_TYPE + " TEXT," + - Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," + - Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " + - ParticipantData.DEFAULT_SELF_SUB_ID + ");"; - - public static final String[] APN_PROJECTION = { - Telephony.Carriers.TYPE, // 0 - Telephony.Carriers.MMSC, // 1 - Telephony.Carriers.MMSPROXY, // 2 - Telephony.Carriers.MMSPORT, // 3 - Telephony.Carriers._ID, // 4 - Telephony.Carriers.CURRENT, // 5 - Telephony.Carriers.NUMERIC, // 6 - Telephony.Carriers.NAME, // 7 - Telephony.Carriers.MCC, // 8 - Telephony.Carriers.MNC, // 9 - Telephony.Carriers.APN, // 10 - Telephony.Carriers.SUBSCRIPTION_ID // 11 - }; - - public static final int COLUMN_TYPE = 0; - public static final int COLUMN_MMSC = 1; - public static final int COLUMN_MMSPROXY = 2; - public static final int COLUMN_MMSPORT = 3; - public static final int COLUMN_ID = 4; - public static final int COLUMN_CURRENT = 5; - public static final int COLUMN_NUMERIC = 6; - public static final int COLUMN_NAME = 7; - public static final int COLUMN_MCC = 8; - public static final int COLUMN_MNC = 9; - public static final int COLUMN_APN = 10; - public static final int COLUMN_SUB_ID = 11; - - public static final String[] APN_FULL_PROJECTION = { - Telephony.Carriers.NAME, - Telephony.Carriers.MCC, - Telephony.Carriers.MNC, - Telephony.Carriers.APN, - Telephony.Carriers.USER, - Telephony.Carriers.SERVER, - Telephony.Carriers.PASSWORD, - Telephony.Carriers.PROXY, - Telephony.Carriers.PORT, - Telephony.Carriers.MMSC, - Telephony.Carriers.MMSPROXY, - Telephony.Carriers.MMSPORT, - Telephony.Carriers.AUTH_TYPE, - Telephony.Carriers.TYPE, - Telephony.Carriers.PROTOCOL, - Telephony.Carriers.ROAMING_PROTOCOL, - Telephony.Carriers.CARRIER_ENABLED, - Telephony.Carriers.BEARER, - Telephony.Carriers.MVNO_TYPE, - Telephony.Carriers.MVNO_MATCH_DATA, - Telephony.Carriers.CURRENT, - Telephony.Carriers.SUBSCRIPTION_ID, - }; - - private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL"; - - /** - * ApnDatabase is initialized asynchronously from the application.onCreate - * To ensure that it works in a testing environment it needs to never access the factory context - */ - public static void initializeAppContext(final Context context) { - sContext = context; - } - - private ApnDatabase() { - super(sContext, APN_DATABASE_NAME, null, DB_VERSION); - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase constructor"); - } - } - - public static ApnDatabase getApnDatabase() { - if (sApnDatabase == null) { - sApnDatabase = new ApnDatabase(); - } - return sApnDatabase; - } - - public static boolean doesDatabaseExist() { - final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME); - return dbFile.exists(); - } - - @Override - public void onCreate(final SQLiteDatabase db) { - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase onCreate"); - } - // Build the table using defaults (apn info bundled with the app) - rebuildTables(db); - } - - /** - * Get a copy of user changes in the old table - * - * @return The list of user changed apns - */ - public static List loadUserDataFromOldTable(final SQLiteDatabase db) { - Cursor cursor = null; - try { - cursor = db.query(APN_TABLE, - APN_FULL_PROJECTION, CURRENT_SELECTION, - null/*selectionArgs*/, - null/*groupBy*/, null/*having*/, null/*orderBy*/); - if (cursor != null) { - final List result = Lists.newArrayList(); - while (cursor.moveToNext()) { - final ContentValues row = cursorToValues(cursor); - if (row != null) { - result.add(row); - } - } - return result; - } - } catch (final SQLiteException e) { - LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID}; - - private static final String ID_SELECTION = Telephony.Carriers._ID + "=?"; - - /** - * Store use changes of old table into the new apn table - * - * @param data The user changes - */ - public static void saveUserDataFromOldTable( - final SQLiteDatabase db, final List data) { - if (data == null || data.size() < 1) { - return; - } - for (final ContentValues row : data) { - // Build query from the row data. It is an exact match, column by column, - // except the CURRENT column - final StringBuilder selectionBuilder = new StringBuilder(); - final ArrayList selectionArgs = Lists.newArrayList(); - for (final String key : row.keySet()) { - if (!Telephony.Carriers.CURRENT.equals(key)) { - if (selectionBuilder.length() > 0) { - selectionBuilder.append(" AND "); - } - final String value = row.getAsString(key); - if (TextUtils.isEmpty(value)) { - selectionBuilder.append(key).append(" IS NULL"); - } else { - selectionBuilder.append(key).append("=?"); - selectionArgs.add(value); - } - } - } - Cursor cursor = null; - try { - cursor = db.query(APN_TABLE, - ID_PROJECTION, - selectionBuilder.toString(), - selectionArgs.toArray(new String[0]), - null/*groupBy*/, null/*having*/, null/*orderBy*/); - if (cursor != null && cursor.moveToFirst()) { - db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)}); - } else { - // User APN does not exist, insert into the new table - row.put(Telephony.Carriers.NUMERIC, - PhoneUtils.canonicalizeMccMnc( - row.getAsString(Telephony.Carriers.MCC), - row.getAsString(Telephony.Carriers.MNC)) - ); - db.insert(APN_TABLE, null/*nullColumnHack*/, row); - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - } - - // Convert Cursor to ContentValues - private static ContentValues cursorToValues(final Cursor cursor) { - final int columnCount = cursor.getColumnCount(); - if (columnCount > 0) { - final ContentValues result = new ContentValues(); - for (int i = 0; i < columnCount; i++) { - final String name = cursor.getColumnName(i); - final String value = cursor.getString(i); - result.put(name, value); - } - return result; - } - return null; - } - - @Override - public void onOpen(final SQLiteDatabase db) { - super.onOpen(db); - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase onOpen"); - } - } - - @Override - public void close() { - super.close(); - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase close"); - } - } - - private void rebuildTables(final SQLiteDatabase db) { - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase rebuildTables"); - } - db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE + ";"); - db.execSQL(APN_TABLE_SQL); - loadApnTable(db); - } - - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase onUpgrade"); - } - rebuildTables(db); - } - - @Override - public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { - if (DEBUG) { - LogUtil.d(TAG, "ApnDatabase onDowngrade"); - } - rebuildTables(db); - } - - /** - * Load APN table from app resources - */ - private static void loadApnTable(final SQLiteDatabase db) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "ApnDatabase loadApnTable"); - } - final Resources r = sContext.getResources(); - final XmlResourceParser parser = r.getXml(R.xml.apns); - final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser); - processor.setApnHandler(new ApnsXmlProcessor.ApnHandler() { - @Override - public void process(final ContentValues apnValues) { - db.insert(APN_TABLE, null/*nullColumnHack*/, apnValues); - } - }); - try { - processor.process(); - } catch (final Exception e) { - Log.e(TAG, "Got exception while loading APN database.", e); - } finally { - parser.close(); - } - } - - public static void forceBuildAndLoadApnTables() { - final SQLiteDatabase db = getApnDatabase().getWritableDatabase(); - db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE); - // Table(s) always need for JB MR1 for APN support for MMS because JB MR1 throws - // a SecurityException when trying to access the carriers table (which holds the - // APNs). Some JB MR2 devices also throw the security exception, so we're building - // the table for JB MR2, too. - db.execSQL(APN_TABLE_SQL); - - loadApnTable(db); - } - - /** - * Clear all tables - */ - public static void clearTables() { - final SQLiteDatabase db = getApnDatabase().getWritableDatabase(); - db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE); - db.execSQL(APN_TABLE_SQL); - } -} diff --git a/src/com/android/messaging/sms/ApnsXmlProcessor.java b/src/com/android/messaging/sms/ApnsXmlProcessor.java deleted file mode 100644 index 976896c..0000000 --- a/src/com/android/messaging/sms/ApnsXmlProcessor.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.ContentValues; -import android.provider.Telephony; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.collect.Maps; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Map; - -/* - * XML processor for the following files: - * 1. res/xml/apns.xml - * 2. res/xml/mms_config.xml (or related overlay files) - */ -class ApnsXmlProcessor { - public interface ApnHandler { - public void process(ContentValues apnValues); - } - - public interface MmsConfigHandler { - public void process(String mccMnc, String key, String value, String type); - } - - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final Map APN_ATTRIBUTE_MAP = Maps.newHashMap(); - static { - APN_ATTRIBUTE_MAP.put("mcc", Telephony.Carriers.MCC); - APN_ATTRIBUTE_MAP.put("mnc", Telephony.Carriers.MNC); - APN_ATTRIBUTE_MAP.put("carrier", Telephony.Carriers.NAME); - APN_ATTRIBUTE_MAP.put("apn", Telephony.Carriers.APN); - APN_ATTRIBUTE_MAP.put("mmsc", Telephony.Carriers.MMSC); - APN_ATTRIBUTE_MAP.put("mmsproxy", Telephony.Carriers.MMSPROXY); - APN_ATTRIBUTE_MAP.put("mmsport", Telephony.Carriers.MMSPORT); - APN_ATTRIBUTE_MAP.put("type", Telephony.Carriers.TYPE); - APN_ATTRIBUTE_MAP.put("user", Telephony.Carriers.USER); - APN_ATTRIBUTE_MAP.put("password", Telephony.Carriers.PASSWORD); - APN_ATTRIBUTE_MAP.put("authtype", Telephony.Carriers.AUTH_TYPE); - APN_ATTRIBUTE_MAP.put("mvno_match_data", Telephony.Carriers.MVNO_MATCH_DATA); - APN_ATTRIBUTE_MAP.put("mvno_type", Telephony.Carriers.MVNO_TYPE); - APN_ATTRIBUTE_MAP.put("protocol", Telephony.Carriers.PROTOCOL); - APN_ATTRIBUTE_MAP.put("bearer", Telephony.Carriers.BEARER); - APN_ATTRIBUTE_MAP.put("server", Telephony.Carriers.SERVER); - APN_ATTRIBUTE_MAP.put("roaming_protocol", Telephony.Carriers.ROAMING_PROTOCOL); - APN_ATTRIBUTE_MAP.put("proxy", Telephony.Carriers.PROXY); - APN_ATTRIBUTE_MAP.put("port", Telephony.Carriers.PORT); - APN_ATTRIBUTE_MAP.put("carrier_enabled", Telephony.Carriers.CARRIER_ENABLED); - } - - private static final String TAG_APNS = "apns"; - private static final String TAG_APN = "apn"; - private static final String TAG_MMS_CONFIG = "mms_config"; - - // Handler to process one apn - private ApnHandler mApnHandler; - // Handler to process one mms_config key/value pair - private MmsConfigHandler mMmsConfigHandler; - - private final StringBuilder mLogStringBuilder = new StringBuilder(); - - private final XmlPullParser mInputParser; - - private ApnsXmlProcessor(XmlPullParser parser) { - mInputParser = parser; - mApnHandler = null; - mMmsConfigHandler = null; - } - - public static ApnsXmlProcessor get(XmlPullParser parser) { - Assert.notNull(parser); - return new ApnsXmlProcessor(parser); - } - - public ApnsXmlProcessor setApnHandler(ApnHandler handler) { - mApnHandler = handler; - return this; - } - - public ApnsXmlProcessor setMmsConfigHandler(MmsConfigHandler handler) { - mMmsConfigHandler = handler; - return this; - } - - /** - * Move XML parser forward to next event type or the end of doc - * - * @param eventType - * @return The final event type we meet - * @throws XmlPullParserException - * @throws IOException - */ - private int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException { - for (;;) { - int nextEvent = mInputParser.next(); - if (nextEvent == eventType - || nextEvent == XmlPullParser.END_DOCUMENT) { - return nextEvent; - } - } - } - - public void process() { - try { - // Find the first element - if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) { - throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @" - + xmlParserDebugContext()); - } - // A single ContentValues object for holding the parsing result of - // an apn element - final ContentValues values = new ContentValues(); - String tagName = mInputParser.getName(); - // Top level tag can be "apns" (apns.xml) - // or "mms_config" (mms_config.xml) - if (TAG_APNS.equals(tagName)) { - // For "apns", there could be "apn" or both "apn" and "mms_config" - for (;;) { - if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) { - break; - } - tagName = mInputParser.getName(); - if (TAG_APN.equals(tagName)) { - processApn(values); - } else if (TAG_MMS_CONFIG.equals(tagName)) { - processMmsConfig(); - } - } - } else if (TAG_MMS_CONFIG.equals(tagName)) { - // mms_config.xml resource - processMmsConfig(); - } - } catch (IOException e) { - LogUtil.e(TAG, "ApnsXmlProcessor: I/O failure " + e, e); - } catch (XmlPullParserException e) { - LogUtil.e(TAG, "ApnsXmlProcessor: parsing failure " + e, e); - } - } - - private Integer parseInt(String text, Integer defaultValue, String logHint) { - Integer value = defaultValue; - try { - value = Integer.parseInt(text); - } catch (Exception e) { - LogUtil.e(TAG, - "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext()); - } - return value; - } - - private Boolean parseBoolean(String text, Boolean defaultValue, String logHint) { - Boolean value = defaultValue; - try { - value = Boolean.parseBoolean(text); - } catch (Exception e) { - LogUtil.e(TAG, - "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext()); - } - return value; - } - - private static String xmlParserEventString(int event) { - switch (event) { - case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT"; - case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT"; - case XmlPullParser.START_TAG: return "START_TAG"; - case XmlPullParser.END_TAG: return "END_TAG"; - case XmlPullParser.TEXT: return "TEXT"; - } - return Integer.toString(event); - } - - /** - * @return The debugging information of the parser's current position - */ - private String xmlParserDebugContext() { - mLogStringBuilder.setLength(0); - if (mInputParser != null) { - try { - final int eventType = mInputParser.getEventType(); - mLogStringBuilder.append(xmlParserEventString(eventType)); - if (eventType == XmlPullParser.START_TAG - || eventType == XmlPullParser.END_TAG - || eventType == XmlPullParser.TEXT) { - mLogStringBuilder.append('<').append(mInputParser.getName()); - for (int i = 0; i < mInputParser.getAttributeCount(); i++) { - mLogStringBuilder.append(' ') - .append(mInputParser.getAttributeName(i)) - .append('=') - .append(mInputParser.getAttributeValue(i)); - } - mLogStringBuilder.append("/>"); - } - return mLogStringBuilder.toString(); - } catch (XmlPullParserException e) { - LogUtil.e(TAG, "xmlParserDebugContext: " + e, e); - } - } - return "Unknown"; - } - - /** - * Process one apn - * - * @param apnValues Where we store the parsed apn - * @throws IOException - * @throws XmlPullParserException - */ - private void processApn(ContentValues apnValues) throws IOException, XmlPullParserException { - Assert.notNull(apnValues); - apnValues.clear(); - // Collect all the attributes - for (int i = 0; i < mInputParser.getAttributeCount(); i++) { - final String key = APN_ATTRIBUTE_MAP.get(mInputParser.getAttributeName(i)); - if (key != null) { - apnValues.put(key, mInputParser.getAttributeValue(i)); - } - } - // Set numeric to be canonicalized mcc/mnc like "310120", always 6 digits - final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc( - apnValues.getAsString(Telephony.Carriers.MCC), - apnValues.getAsString(Telephony.Carriers.MNC)); - apnValues.put(Telephony.Carriers.NUMERIC, canonicalMccMnc); - // Some of the values should not be string type, converting them to desired types - final String authType = apnValues.getAsString(Telephony.Carriers.AUTH_TYPE); - if (authType != null) { - apnValues.put(Telephony.Carriers.AUTH_TYPE, parseInt(authType, -1, "apn authtype")); - } - final String carrierEnabled = apnValues.getAsString(Telephony.Carriers.CARRIER_ENABLED); - if (carrierEnabled != null) { - apnValues.put(Telephony.Carriers.CARRIER_ENABLED, - parseBoolean(carrierEnabled, null, "apn carrierEnabled")); - } - final String bearer = apnValues.getAsString(Telephony.Carriers.BEARER); - if (bearer != null) { - apnValues.put(Telephony.Carriers.BEARER, parseInt(bearer, 0, "apn bearer")); - } - // We are at the end tag - if (mInputParser.next() != XmlPullParser.END_TAG) { - throw new XmlPullParserException("Apn: expecting end tag @" - + xmlParserDebugContext()); - } - // We are done parsing one APN, call the handler - if (mApnHandler != null) { - mApnHandler.process(apnValues); - } - } - - /** - * Process one mms_config. - * - * @throws IOException - * @throws XmlPullParserException - */ - private void processMmsConfig() - throws IOException, XmlPullParserException { - // Get the mcc and mnc attributes - final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc( - mInputParser.getAttributeValue(null, "mcc"), - mInputParser.getAttributeValue(null, "mnc")); - // We are at the start tag - for (;;) { - int nextEvent; - // Skipping spaces - while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT) { - } - if (nextEvent == XmlPullParser.START_TAG) { - // Parse one mms config key/value - processMmsConfigKeyValue(canonicalMccMnc); - } else if (nextEvent == XmlPullParser.END_TAG) { - break; - } else { - throw new XmlPullParserException("MmsConfig: expecting start or end tag @" - + xmlParserDebugContext()); - } - } - } - - /** - * Process one mms_config key/value pair - * - * @param mccMnc The mcc and mnc of this mms_config - * @throws IOException - * @throws XmlPullParserException - */ - private void processMmsConfigKeyValue(String mccMnc) - throws IOException, XmlPullParserException { - final String key = mInputParser.getAttributeValue(null, "name"); - // We are at the start tag, the name of the tag is the type - // e.g. value - final String type = mInputParser.getName(); - int nextEvent = mInputParser.next(); - String value = null; - if (nextEvent == XmlPullParser.TEXT) { - value = mInputParser.getText(); - nextEvent = mInputParser.next(); - } - if (nextEvent != XmlPullParser.END_TAG) { - throw new XmlPullParserException("ApnsXmlProcessor: expecting end tag @" - + xmlParserDebugContext()); - } - // We are done parsing one mms_config key/value, call the handler - if (mMmsConfigHandler != null) { - mMmsConfigHandler.process(mccMnc, key, value, type); - } - } -} diff --git a/src/com/android/messaging/sms/BugleApnSettingsLoader.java b/src/com/android/messaging/sms/BugleApnSettingsLoader.java deleted file mode 100644 index 43c95ac..0000000 --- a/src/com/android/messaging/sms/BugleApnSettingsLoader.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.net.Uri; -import android.provider.Telephony; -import android.support.v7.mms.ApnSettingsLoader; -import android.support.v7.mms.MmsManager; -import android.text.TextUtils; -import android.util.SparseArray; - -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; - -/** - * APN loader for default SMS SIM - * - * This loader tries to load APNs from 3 sources in order: - * 1. Gservices setting - * 2. System APN table - * 3. Local APN table - */ -public class BugleApnSettingsLoader implements ApnSettingsLoader { - /** - * The base implementation of an APN - */ - private static class BaseApn implements Apn { - /** - * Create a base APN from parameters - * - * @param typesIn the APN type field - * @param mmscIn the APN mmsc field - * @param proxyIn the APN mmsproxy field - * @param portIn the APN mmsport field - * @return an instance of base APN, or null if any of the parameter is invalid - */ - public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn, - final String portIn) { - if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) { - return null; - } - String mmsc = trimWithNullCheck(mmscIn); - if (TextUtils.isEmpty(mmsc)) { - return null; - } - mmsc = trimV4AddrZeros(mmsc); - try { - new URI(mmsc); - } catch (final URISyntaxException e) { - return null; - } - String mmsProxy = trimWithNullCheck(proxyIn); - int mmsProxyPort = 80; - if (!TextUtils.isEmpty(mmsProxy)) { - mmsProxy = trimV4AddrZeros(mmsProxy); - final String portString = trimWithNullCheck(portIn); - if (portString != null) { - try { - mmsProxyPort = Integer.parseInt(portString); - } catch (final NumberFormatException e) { - // Ignore, just use 80 to try - } - } - } - return new BaseApn(mmsc, mmsProxy, mmsProxyPort); - } - - private final String mMmsc; - private final String mMmsProxy; - private final int mMmsProxyPort; - - public BaseApn(final String mmsc, final String proxy, final int port) { - mMmsc = mmsc; - mMmsProxy = proxy; - mMmsProxyPort = port; - } - - @Override - public String getMmsc() { - return mMmsc; - } - - @Override - public String getMmsProxy() { - return mMmsProxy; - } - - @Override - public int getMmsProxyPort() { - return mMmsProxyPort; - } - - @Override - public void setSuccess() { - // Do nothing - } - - public boolean equals(final BaseApn other) { - return TextUtils.equals(mMmsc, other.getMmsc()) && - TextUtils.equals(mMmsProxy, other.getMmsProxy()) && - mMmsProxyPort == other.getMmsProxyPort(); - } - } - - /** - * The APN represented by the local APN table row - */ - private static class DatabaseApn implements Apn { - private static final ContentValues CURRENT_NULL_VALUE; - private static final ContentValues CURRENT_SET_VALUE; - static { - CURRENT_NULL_VALUE = new ContentValues(1); - CURRENT_NULL_VALUE.putNull(Telephony.Carriers.CURRENT); - CURRENT_SET_VALUE = new ContentValues(1); - CURRENT_SET_VALUE.put(Telephony.Carriers.CURRENT, "1"); // 1 for auto selected APN - } - private static final String CLEAR_UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?"; - private static final String[] CLEAR_UPDATE_SELECTION_ARGS = new String[] { "1" }; - private static final String SET_UPDATE_SELECTION = Telephony.Carriers._ID + " =?"; - - /** - * Create an APN loaded from local database - * - * @param apns the in-memory APN list - * @param typesIn the APN type field - * @param mmscIn the APN mmsc field - * @param proxyIn the APN mmsproxy field - * @param portIn the APN mmsport field - * @param rowId the APN's row ID in database - * @param current the value of CURRENT column in database - * @return an in-memory APN instance for database APN row, null if parameter invalid - */ - public static DatabaseApn from(final List apns, final String typesIn, - final String mmscIn, final String proxyIn, final String portIn, - final long rowId, final int current) { - if (apns == null) { - return null; - } - final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn); - if (base == null) { - return null; - } - for (final ApnSettingsLoader.Apn apn : apns) { - if (apn instanceof DatabaseApn && ((DatabaseApn) apn).equals(base)) { - return null; - } - } - return new DatabaseApn(apns, base, rowId, current); - } - - private final List mApns; - private final BaseApn mBase; - private final long mRowId; - private int mCurrent; - - public DatabaseApn(final List apns, final BaseApn base, final long rowId, - final int current) { - mApns = apns; - mBase = base; - mRowId = rowId; - mCurrent = current; - } - - @Override - public String getMmsc() { - return mBase.getMmsc(); - } - - @Override - public String getMmsProxy() { - return mBase.getMmsProxy(); - } - - @Override - public int getMmsProxyPort() { - return mBase.getMmsProxyPort(); - } - - @Override - public void setSuccess() { - moveToListHead(); - setCurrentInDatabase(); - } - - /** - * Try to move this APN to the head of in-memory list - */ - private void moveToListHead() { - // If this is being marked as a successful APN, move it to the top of the list so - // next time it will be tried first - boolean moved = false; - synchronized (mApns) { - if (mApns.get(0) != this) { - mApns.remove(this); - mApns.add(0, this); - moved = true; - } - } - if (moved) { - LogUtil.d(LogUtil.BUGLE_TAG, "Set APN [" - + "MMSC=" + getMmsc() + ", " - + "PROXY=" + getMmsProxy() + ", " - + "PORT=" + getMmsProxyPort() + "] to be first"); - } - } - - /** - * Try to set the APN to be CURRENT in its database table - */ - private void setCurrentInDatabase() { - synchronized (this) { - if (mCurrent > 0) { - // Already current - return; - } - mCurrent = 1; - } - LogUtil.d(LogUtil.BUGLE_TAG, "Set APN @" + mRowId + " to be CURRENT in local db"); - final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase(); - database.beginTransaction(); - try { - // clear the previous current=1 apn - // we don't clear current=2 apn since it is manually selected by user - // and we should not override it. - database.update(ApnDatabase.APN_TABLE, CURRENT_NULL_VALUE, - CLEAR_UPDATE_SELECTION, CLEAR_UPDATE_SELECTION_ARGS); - // set this one to be current (1) - database.update(ApnDatabase.APN_TABLE, CURRENT_SET_VALUE, SET_UPDATE_SELECTION, - new String[] { Long.toString(mRowId) }); - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - } - - public boolean equals(final BaseApn other) { - if (other == null) { - return false; - } - return mBase.equals(other); - } - } - - /** - * APN_TYPE_ALL is a special type to indicate that this APN entry can - * service all data connections. - */ - public static final String APN_TYPE_ALL = "*"; - /** APN type for MMS traffic */ - public static final String APN_TYPE_MMS = "mms"; - - private static final String[] APN_PROJECTION_SYSTEM = { - Telephony.Carriers.TYPE, - Telephony.Carriers.MMSC, - Telephony.Carriers.MMSPROXY, - Telephony.Carriers.MMSPORT, - }; - private static final String[] APN_PROJECTION_LOCAL = { - Telephony.Carriers.TYPE, - Telephony.Carriers.MMSC, - Telephony.Carriers.MMSPROXY, - Telephony.Carriers.MMSPORT, - Telephony.Carriers.CURRENT, - Telephony.Carriers._ID, - }; - private static final int COLUMN_TYPE = 0; - private static final int COLUMN_MMSC = 1; - private static final int COLUMN_MMSPROXY = 2; - private static final int COLUMN_MMSPORT = 3; - private static final int COLUMN_CURRENT = 4; - private static final int COLUMN_ID = 5; - - private static final String SELECTION_APN = Telephony.Carriers.APN + "=?"; - private static final String SELECTION_CURRENT = Telephony.Carriers.CURRENT + " IS NOT NULL"; - private static final String SELECTION_NUMERIC = Telephony.Carriers.NUMERIC + "=?"; - private static final String ORDER_BY = Telephony.Carriers.CURRENT + " DESC"; - - private final Context mContext; - - // Cached APNs for subIds - private final SparseArray> mApnsCache; - - public BugleApnSettingsLoader(final Context context) { - mContext = context; - mApnsCache = new SparseArray<>(); - } - - @Override - public List get(final String apnName) { - final int subId = PhoneUtils.getDefault().getEffectiveSubId( - ParticipantData.DEFAULT_SELF_SUB_ID); - List apns; - boolean didLoad = false; - synchronized (this) { - apns = mApnsCache.get(subId); - if (apns == null) { - apns = new ArrayList<>(); - mApnsCache.put(subId, apns); - loadLocked(subId, apnName, apns); - didLoad = true; - } - } - if (didLoad) { - LogUtil.i(LogUtil.BUGLE_TAG, "Loaded " + apns.size() + " APNs"); - } - return apns; - } - - private void loadLocked(final int subId, final String apnName, final List apns) { - // Try Gservices first - loadFromGservices(apns); - if (apns.size() > 0) { - return; - } - // Try system APN table - loadFromSystem(subId, apnName, apns); - if (apns.size() > 0) { - return; - } - // Try local APN table - loadFromLocalDatabase(apnName, apns); - if (apns.size() <= 0) { - LogUtil.w(LogUtil.BUGLE_TAG, "Failed to load any APN"); - } - } - - /** - * Load from Gservices if APN setting is set in Gservices - * - * @param apns the list used to return results - */ - private void loadFromGservices(final List apns) { - final BugleGservices gservices = BugleGservices.get(); - final String mmsc = gservices.getString(BugleGservicesKeys.MMS_MMSC, null); - if (TextUtils.isEmpty(mmsc)) { - return; - } - LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from gservices"); - final String proxy = gservices.getString(BugleGservicesKeys.MMS_PROXY_ADDRESS, null); - final int port = gservices.getInt(BugleGservicesKeys.MMS_PROXY_PORT, -1); - final Apn apn = BaseApn.from("mms", mmsc, proxy, Integer.toString(port)); - if (apn != null) { - apns.add(apn); - } - } - - /** - * Load matching APNs from telephony provider. - * We try different combinations of the query to work around some platform quirks. - * - * @param subId the SIM subId - * @param apnName the APN name to match - * @param apns the list used to return results - */ - private void loadFromSystem(final int subId, final String apnName, final List apns) { - Uri uri; - if (OsUtil.isAtLeastL_MR1() && subId != MmsManager.DEFAULT_SUB_ID) { - uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId); - } else { - uri = Telephony.Carriers.CONTENT_URI; - } - Cursor cursor = null; - try { - for (; ; ) { - // Try different combinations of queries. Some would work on some platforms. - // So we query each combination until we find one returns non-empty result. - cursor = querySystem(uri, true/*checkCurrent*/, apnName); - if (cursor != null) { - break; - } - cursor = querySystem(uri, false/*checkCurrent*/, apnName); - if (cursor != null) { - break; - } - cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/); - if (cursor != null) { - break; - } - cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/); - break; - } - } catch (final SecurityException e) { - // Can't access platform APN table, return directly - return; - } - if (cursor == null) { - return; - } - try { - if (cursor.moveToFirst()) { - final ApnSettingsLoader.Apn apn = BaseApn.from( - cursor.getString(COLUMN_TYPE), - cursor.getString(COLUMN_MMSC), - cursor.getString(COLUMN_MMSPROXY), - cursor.getString(COLUMN_MMSPORT)); - if (apn != null) { - apns.add(apn); - } - } - } finally { - cursor.close(); - } - } - - /** - * Query system APN table - * - * @param uri The APN query URL to use - * @param checkCurrent If add "CURRENT IS NOT NULL" condition - * @param apnName The optional APN name for query condition - * @return A cursor of the query result. If a cursor is returned as not null, it is - * guaranteed to contain at least one row. - */ - private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) { - LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from system, " - + "checkCurrent=" + checkCurrent + " apnName=" + apnName); - final StringBuilder selectionBuilder = new StringBuilder(); - String[] selectionArgs = null; - if (checkCurrent) { - selectionBuilder.append(SELECTION_CURRENT); - } - apnName = trimWithNullCheck(apnName); - if (!TextUtils.isEmpty(apnName)) { - if (selectionBuilder.length() > 0) { - selectionBuilder.append(" AND "); - } - selectionBuilder.append(SELECTION_APN); - selectionArgs = new String[] { apnName }; - } - try { - final Cursor cursor = SqliteWrapper.query( - mContext, - mContext.getContentResolver(), - uri, - APN_PROJECTION_SYSTEM, - selectionBuilder.toString(), - selectionArgs, - null/*sortOrder*/); - if (cursor == null || cursor.getCount() < 1) { - if (cursor != null) { - cursor.close(); - } - LogUtil.w(LogUtil.BUGLE_TAG, "Query " + uri + " with apn " + apnName + " and " - + (checkCurrent ? "checking CURRENT" : "not checking CURRENT") - + " returned empty"); - return null; - } - return cursor; - } catch (final SQLiteException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "APN table query exception: " + e); - } catch (final SecurityException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Platform restricts APN table access: " + e); - throw e; - } - return null; - } - - /** - * Load matching APNs from local APN table. - * We try both using the APN name and not using the APN name. - * - * @param apnName the APN name - * @param apns the list of results to return - */ - private void loadFromLocalDatabase(final String apnName, final List apns) { - LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from local APN table"); - final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase(); - final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.getDefault().getMccMnc()); - Cursor cursor = null; - cursor = queryLocalDatabase(database, mccMnc, apnName); - if (cursor == null) { - cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/); - } - if (cursor == null) { - LogUtil.w(LogUtil.BUGLE_TAG, "Could not find any APN in local table"); - return; - } - try { - while (cursor.moveToNext()) { - final Apn apn = DatabaseApn.from(apns, - cursor.getString(COLUMN_TYPE), - cursor.getString(COLUMN_MMSC), - cursor.getString(COLUMN_MMSPROXY), - cursor.getString(COLUMN_MMSPORT), - cursor.getLong(COLUMN_ID), - cursor.getInt(COLUMN_CURRENT)); - if (apn != null) { - apns.add(apn); - } - } - } finally { - cursor.close(); - } - } - - /** - * Make a query of local APN table based on MCC/MNC and APN name, sorted by CURRENT - * column in descending order - * - * @param db the local database - * @param numeric the MCC/MNC string - * @param apnName the optional APN name to match - * @return the cursor of the query, null if no result - */ - private static Cursor queryLocalDatabase(final SQLiteDatabase db, final String numeric, - final String apnName) { - final String selection; - final String[] selectionArgs; - if (TextUtils.isEmpty(apnName)) { - selection = SELECTION_NUMERIC; - selectionArgs = new String[] { numeric }; - } else { - selection = SELECTION_NUMERIC + " AND " + SELECTION_APN; - selectionArgs = new String[] { numeric, apnName }; - } - Cursor cursor = null; - try { - cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs, - null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/); - } catch (final SQLiteException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Local APN table does not exist. Try rebuilding.", e); - ApnDatabase.forceBuildAndLoadApnTables(); - cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs, - null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/); - } - if (cursor == null || cursor.getCount() < 1) { - if (cursor != null) { - cursor.close(); - } - LogUtil.w(LogUtil.BUGLE_TAG, "Query local APNs with apn " + apnName - + " returned empty"); - return null; - } - return cursor; - } - - private static String trimWithNullCheck(final String value) { - return value != null ? value.trim() : null; - } - - /** - * Trim leading zeros from IPv4 address strings - * Our base libraries will interpret that as octel.. - * Must leave non v4 addresses and host names alone. - * For example, 192.168.000.010 -> 192.168.0.10 - * - * @param addr a string representing an ip addr - * @return a string propertly trimmed - */ - private static String trimV4AddrZeros(final String addr) { - if (addr == null) { - return null; - } - final String[] octets = addr.split("\\."); - if (octets.length != 4) { - return addr; - } - final StringBuilder builder = new StringBuilder(16); - String result = null; - for (int i = 0; i < 4; i++) { - try { - if (octets[i].length() > 3) { - return addr; - } - builder.append(Integer.parseInt(octets[i])); - } catch (final NumberFormatException e) { - return addr; - } - if (i < 3) { - builder.append('.'); - } - } - result = builder.toString(); - return result; - } - - /** - * Check if the APN contains the APN type we want - * - * @param types The string encodes a list of supported types - * @param requestType The type we want - * @return true if the input types string contains the requestType - */ - public static boolean isValidApnType(final String types, final String requestType) { - // If APN type is unspecified, assume APN_TYPE_ALL. - if (TextUtils.isEmpty(types)) { - return true; - } - for (final String t : types.split(",")) { - if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) { - return true; - } - } - return false; - } - - /** - * Get the ID of first APN to try - */ - public static String getFirstTryApn(final SQLiteDatabase database, final String mccMnc) { - String key = null; - Cursor cursor = null; - try { - cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/); - if (cursor.moveToFirst()) { - key = cursor.getString(ApnDatabase.COLUMN_ID); - } - } catch (final Exception e) { - // Nothing to do - } finally { - if (cursor != null) { - cursor.close(); - } - } - return key; - } -} diff --git a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java b/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java deleted file mode 100644 index ac6c7e4..0000000 --- a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.os.Bundle; -import android.support.v7.mms.CarrierConfigValuesLoader; -import android.util.SparseArray; - -import com.android.messaging.R; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -/** - * Carrier configuration loader - * - * Loader tries to load from resources. If there is MMS API available, also - * load from system. - */ -public class BugleCarrierConfigValuesLoader implements CarrierConfigValuesLoader { - /* - * Key types - */ - public static final String KEY_TYPE_INT = "int"; - public static final String KEY_TYPE_BOOL = "bool"; - public static final String KEY_TYPE_STRING = "string"; - - private final Context mContext; - - // Cached values for subIds - private final SparseArray mValuesCache; - - public BugleCarrierConfigValuesLoader(final Context context) { - mContext = context; - mValuesCache = new SparseArray<>(); - } - - @Override - public Bundle get(int subId) { - subId = PhoneUtils.getDefault().getEffectiveSubId(subId); - Bundle values; - String loadSource = null; - synchronized (this) { - values = mValuesCache.get(subId); - if (values == null) { - values = new Bundle(); - mValuesCache.put(subId, values); - loadSource = loadLocked(subId, values); - } - } - if (loadSource != null) { - LogUtil.i(LogUtil.BUGLE_TAG, "Carrier configs loaded: " + values - + " from " + loadSource + " for subId=" + subId); - } - return values; - } - - /** - * Clear the cache for reloading - */ - public void reset() { - synchronized (this) { - mValuesCache.clear(); - } - } - - /** - * Loading carrier config values - * - * @param subId which SIM to load for - * @param values the result to add to - * @return the source of the config, could be "resources" or "resources+system" - */ - private String loadLocked(final int subId, final Bundle values) { - // Load from resources in earlier platform - loadFromResources(subId, values); - if (OsUtil.isAtLeastL()) { - // Load from system to override if system API exists - loadFromSystem(subId, values); - return "resources+system"; - } - return "resources"; - } - - /** - * Load from system, using MMS API - * - * @param subId which SIM to load for - * @param values the result to add to - */ - private static void loadFromSystem(final int subId, final Bundle values) { - try { - final Bundle systemValues = - PhoneUtils.get(subId).getSmsManager().getCarrierConfigValues(); - if (systemValues != null) { - values.putAll(systemValues); - } - } catch (final Exception e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Calling system getCarrierConfigValues exception", e); - } - } - - /** - * Load from SIM-dependent resources - * - * @param subId which SIM to load for - * @param values the result to add to - */ - private void loadFromResources(final int subId, final Bundle values) { - // Get a subscription-dependent context for loading the mms_config.xml - final Context subContext = getSubDepContext(mContext, subId); - // Load and parse the XML - XmlResourceParser parser = null; - try { - parser = subContext.getResources().getXml(R.xml.mms_config); - final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser); - processor.setMmsConfigHandler(new ApnsXmlProcessor.MmsConfigHandler() { - @Override - public void process(final String mccMnc, final String key, final String value, - final String type) { - update(values, type, key, value); - } - }); - processor.process(); - } catch (final Resources.NotFoundException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Can not find mms_config.xml"); - } finally { - if (parser != null) { - parser.close(); - } - } - } - - /** - * Get a subscription's Context so we can load resources from it - * - * @param context the sub-independent Context - * @param subId the SIM's subId - * @return the sub-dependent Context - */ - private static Context getSubDepContext(final Context context, final int subId) { - if (!OsUtil.isAtLeastL_MR1()) { - return context; - } - final int[] mccMnc = PhoneUtils.get(subId).getMccMnc(); - final int mcc = mccMnc[0]; - final int mnc = mccMnc[1]; - final Configuration subConfig = new Configuration(); - if (mcc == 0 && mnc == 0) { - Configuration config = context.getResources().getConfiguration(); - subConfig.mcc = config.mcc; - subConfig.mnc = config.mnc; - } else { - subConfig.mcc = mcc; - subConfig.mnc = mnc; - } - return context.createConfigurationContext(subConfig); - } - - /** - * Add or update a carrier config key/value pair to the Bundle - * - * @param values the result Bundle to add to - * @param type the value type - * @param key the key - * @param value the value - */ - public static void update(final Bundle values, final String type, final String key, - final String value) { - try { - if (KEY_TYPE_INT.equals(type)) { - values.putInt(key, Integer.parseInt(value)); - } else if (KEY_TYPE_BOOL.equals(type)) { - values.putBoolean(key, Boolean.parseBoolean(value)); - } else if (KEY_TYPE_STRING.equals(type)){ - values.putString(key, value); - } - } catch (final NumberFormatException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Add carrier values: " - + "invalid " + key + "," + value + "," + type); - } - } -} diff --git a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java b/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java deleted file mode 100644 index a8dc5c4..0000000 --- a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.Context; -import android.support.v7.mms.UserAgentInfoLoader; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.VersionUtil; - -/** - * User agent and UA profile URL loader - */ -public class BugleUserAgentInfoLoader implements UserAgentInfoLoader { - private static final String DEFAULT_USER_AGENT_PREFIX = "Bugle/"; - - private Context mContext; - private boolean mLoaded; - - private String mUserAgent; - private String mUAProfUrl; - - public BugleUserAgentInfoLoader(final Context context) { - mContext = context; - } - - @Override - public String getUserAgent() { - load(); - return mUserAgent; - } - - @Override - public String getUAProfUrl() { - load(); - return mUAProfUrl; - } - - private void load() { - if (mLoaded) { - return; - } - boolean didLoad = false; - synchronized (this) { - if (!mLoaded) { - loadLocked(); - mLoaded = true; - didLoad = true; - } - } - if (didLoad) { - LogUtil.i(LogUtil.BUGLE_TAG, "Loaded user agent info: " - + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl); - } - } - - private void loadLocked() { - if (OsUtil.isAtLeastKLP()) { - // load the MMS User agent and UaProfUrl from TelephonyManager APIs - final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE); - mUserAgent = telephonyManager.getMmsUserAgent(); - mUAProfUrl = telephonyManager.getMmsUAProfUrl(); - } - // if user agent string isn't set, use the format "Bugle/". - if (TextUtils.isEmpty(mUserAgent)) { - final String simpleVersionName = VersionUtil.getInstance(mContext).getSimpleName(); - mUserAgent = DEFAULT_USER_AGENT_PREFIX + simpleVersionName; - } - // if the UAProfUrl isn't set, get it from Gservices - if (TextUtils.isEmpty(mUAProfUrl)) { - mUAProfUrl = BugleGservices.get().getString( - BugleGservicesKeys.MMS_UA_PROFILE_URL, - BugleGservicesKeys.MMS_UA_PROFILE_URL_DEFAULT); - } - } -} diff --git a/src/com/android/messaging/sms/DatabaseMessages.java b/src/com/android/messaging/sms/DatabaseMessages.java deleted file mode 100644 index 0f662e5..0000000 --- a/src/com/android/messaging/sms/DatabaseMessages.java +++ /dev/null @@ -1,1006 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Sms; -import android.text.TextUtils; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.media.VideoThumbnailRequest; -import com.android.messaging.mmslib.pdu.CharacterSets; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaMetadataRetrieverWrapper; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.collect.Lists; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - -/** - * Class contains various SMS/MMS database entities from telephony provider - */ -public class DatabaseMessages { - private static final String TAG = LogUtil.BUGLE_TAG; - - public abstract static class DatabaseMessage { - public abstract int getProtocol(); - public abstract String getUri(); - public abstract long getTimestampInMillis(); - - @Override - public boolean equals(final Object other) { - if (other == null || !(other instanceof DatabaseMessage)) { - return false; - } - final DatabaseMessage otherDbMsg = (DatabaseMessage) other; - // No need to check timestamp since we only need this when we compare - // messages at the same timestamp - return TextUtils.equals(getUri(), otherDbMsg.getUri()); - } - - @Override - public int hashCode() { - // No need to check timestamp since we only need this when we compare - // messages at the same timestamp - return getUri().hashCode(); - } - } - - /** - * SMS message - */ - public static class SmsMessage extends DatabaseMessage implements Parcelable { - private static int sIota = 0; - public static final int INDEX_ID = sIota++; - public static final int INDEX_TYPE = sIota++; - public static final int INDEX_ADDRESS = sIota++; - public static final int INDEX_BODY = sIota++; - public static final int INDEX_DATE = sIota++; - public static final int INDEX_THREAD_ID = sIota++; - public static final int INDEX_STATUS = sIota++; - public static final int INDEX_READ = sIota++; - public static final int INDEX_SEEN = sIota++; - public static final int INDEX_DATE_SENT = sIota++; - public static final int INDEX_SUB_ID = sIota++; - - private static String[] sProjection; - - public static String[] getProjection() { - if (sProjection == null) { - String[] projection = new String[] { - Sms._ID, - Sms.TYPE, - Sms.ADDRESS, - Sms.BODY, - Sms.DATE, - Sms.THREAD_ID, - Sms.STATUS, - Sms.READ, - Sms.SEEN, - Sms.DATE_SENT, - Sms.SUBSCRIPTION_ID, - }; - if (!MmsUtils.hasSmsDateSentColumn()) { - projection[INDEX_DATE_SENT] = Sms.DATE; - } - if (!OsUtil.isAtLeastL_MR1()) { - Assert.equals(INDEX_SUB_ID, projection.length - 1); - String[] withoutSubId = new String[projection.length - 1]; - System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length); - projection = withoutSubId; - } - - sProjection = projection; - } - - return sProjection; - } - - public String mUri; - public String mAddress; - public String mBody; - private long mRowId; - public long mTimestampInMillis; - public long mTimestampSentInMillis; - public int mType; - public long mThreadId; - public int mStatus; - public boolean mRead; - public boolean mSeen; - public int mSubId; - - private SmsMessage() { - } - - /** - * Load from a cursor of a query that returns the SMS to import - * - * @param cursor - */ - private void load(final Cursor cursor) { - mRowId = cursor.getLong(INDEX_ID); - mAddress = cursor.getString(INDEX_ADDRESS); - mBody = cursor.getString(INDEX_BODY); - mTimestampInMillis = cursor.getLong(INDEX_DATE); - // Before ICS, there is no "date_sent" so use copy of "date" value - mTimestampSentInMillis = cursor.getLong(INDEX_DATE_SENT); - mType = cursor.getInt(INDEX_TYPE); - mThreadId = cursor.getLong(INDEX_THREAD_ID); - mStatus = cursor.getInt(INDEX_STATUS); - mRead = cursor.getInt(INDEX_READ) == 0 ? false : true; - mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true; - mUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mRowId).toString(); - mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID); - } - - /** - * Get a new SmsMessage by loading from the cursor of a query - * that returns the SMS to import - * - * @param cursor - * @return - */ - public static SmsMessage get(final Cursor cursor) { - final SmsMessage msg = new SmsMessage(); - msg.load(cursor); - return msg; - } - - @Override - public String getUri() { - return mUri; - } - - public int getSubId() { - return mSubId; - } - - @Override - public int getProtocol() { - return MessageData.PROTOCOL_SMS; - } - - @Override - public long getTimestampInMillis() { - return mTimestampInMillis; - } - - @Override - public int describeContents() { - return 0; - } - - private SmsMessage(final Parcel in) { - mUri = in.readString(); - mRowId = in.readLong(); - mTimestampInMillis = in.readLong(); - mTimestampSentInMillis = in.readLong(); - mType = in.readInt(); - mThreadId = in.readLong(); - mStatus = in.readInt(); - mRead = in.readInt() != 0; - mSeen = in.readInt() != 0; - mSubId = in.readInt(); - - // SMS specific - mAddress = in.readString(); - mBody = in.readString(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SmsMessage createFromParcel(final Parcel in) { - return new SmsMessage(in); - } - - @Override - public SmsMessage[] newArray(final int size) { - return new SmsMessage[size]; - } - }; - - @Override - public void writeToParcel(final Parcel out, final int flags) { - out.writeString(mUri); - out.writeLong(mRowId); - out.writeLong(mTimestampInMillis); - out.writeLong(mTimestampSentInMillis); - out.writeInt(mType); - out.writeLong(mThreadId); - out.writeInt(mStatus); - out.writeInt(mRead ? 1 : 0); - out.writeInt(mSeen ? 1 : 0); - out.writeInt(mSubId); - - // SMS specific - out.writeString(mAddress); - out.writeString(mBody); - } - } - - /** - * MMS message - */ - public static class MmsMessage extends DatabaseMessage implements Parcelable { - private static int sIota = 0; - public static final int INDEX_ID = sIota++; - public static final int INDEX_MESSAGE_BOX = sIota++; - public static final int INDEX_SUBJECT = sIota++; - public static final int INDEX_SUBJECT_CHARSET = sIota++; - public static final int INDEX_MESSAGE_SIZE = sIota++; - public static final int INDEX_DATE = sIota++; - public static final int INDEX_DATE_SENT = sIota++; - public static final int INDEX_THREAD_ID = sIota++; - public static final int INDEX_PRIORITY = sIota++; - public static final int INDEX_STATUS = sIota++; - public static final int INDEX_READ = sIota++; - public static final int INDEX_SEEN = sIota++; - public static final int INDEX_CONTENT_LOCATION = sIota++; - public static final int INDEX_TRANSACTION_ID = sIota++; - public static final int INDEX_MESSAGE_TYPE = sIota++; - public static final int INDEX_EXPIRY = sIota++; - public static final int INDEX_RESPONSE_STATUS = sIota++; - public static final int INDEX_RETRIEVE_STATUS = sIota++; - public static final int INDEX_SUB_ID = sIota++; - - private static String[] sProjection; - - public static String[] getProjection() { - if (sProjection == null) { - String[] projection = new String[] { - Mms._ID, - Mms.MESSAGE_BOX, - Mms.SUBJECT, - Mms.SUBJECT_CHARSET, - Mms.MESSAGE_SIZE, - Mms.DATE, - Mms.DATE_SENT, - Mms.THREAD_ID, - Mms.PRIORITY, - Mms.STATUS, - Mms.READ, - Mms.SEEN, - Mms.CONTENT_LOCATION, - Mms.TRANSACTION_ID, - Mms.MESSAGE_TYPE, - Mms.EXPIRY, - Mms.RESPONSE_STATUS, - Mms.RETRIEVE_STATUS, - Mms.SUBSCRIPTION_ID, - }; - - if (!OsUtil.isAtLeastL_MR1()) { - Assert.equals(INDEX_SUB_ID, projection.length - 1); - String[] withoutSubId = new String[projection.length - 1]; - System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length); - projection = withoutSubId; - } - - sProjection = projection; - } - - return sProjection; - } - - public String mUri; - private long mRowId; - public int mType; - public String mSubject; - public int mSubjectCharset; - private long mSize; - public long mTimestampInMillis; - public long mSentTimestampInMillis; - public long mThreadId; - public int mPriority; - public int mStatus; - public boolean mRead; - public boolean mSeen; - public String mContentLocation; - public String mTransactionId; - public int mMmsMessageType; - public long mExpiryInMillis; - public int mSubId; - public String mSender; - public int mResponseStatus; - public int mRetrieveStatus; - - public List mParts = Lists.newArrayList(); - private boolean mPartsProcessed = false; - - private MmsMessage() { - } - - /** - * Load from a cursor of a query that returns the MMS to import - * - * @param cursor - */ - public void load(final Cursor cursor) { - mRowId = cursor.getLong(INDEX_ID); - mType = cursor.getInt(INDEX_MESSAGE_BOX); - mSubject = cursor.getString(INDEX_SUBJECT); - mSubjectCharset = cursor.getInt(INDEX_SUBJECT_CHARSET); - if (!TextUtils.isEmpty(mSubject)) { - // PduPersister stores the subject using ISO_8859_1 - // Let's load it using that encoding and convert it back to its original - // See PduPersister.persist and PduPersister.toIsoString - // (Refer to bug b/11162476) - mSubject = getDecodedString( - getStringBytes(mSubject, CharacterSets.ISO_8859_1), mSubjectCharset); - } - mSize = cursor.getLong(INDEX_MESSAGE_SIZE); - // MMS db times are in seconds - mTimestampInMillis = cursor.getLong(INDEX_DATE) * 1000; - mSentTimestampInMillis = cursor.getLong(INDEX_DATE_SENT) * 1000; - mThreadId = cursor.getLong(INDEX_THREAD_ID); - mPriority = cursor.getInt(INDEX_PRIORITY); - mStatus = cursor.getInt(INDEX_STATUS); - mRead = cursor.getInt(INDEX_READ) == 0 ? false : true; - mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true; - mContentLocation = cursor.getString(INDEX_CONTENT_LOCATION); - mTransactionId = cursor.getString(INDEX_TRANSACTION_ID); - mMmsMessageType = cursor.getInt(INDEX_MESSAGE_TYPE); - mExpiryInMillis = cursor.getLong(INDEX_EXPIRY) * 1000; - mResponseStatus = cursor.getInt(INDEX_RESPONSE_STATUS); - mRetrieveStatus = cursor.getInt(INDEX_RETRIEVE_STATUS); - // Clear all parts in case we reuse this object - mParts.clear(); - mPartsProcessed = false; - mUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mRowId).toString(); - mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID); - } - - /** - * Get a new MmsMessage by loading from the cursor of a query - * that returns the MMS to import - * - * @param cursor - * @return - */ - public static MmsMessage get(final Cursor cursor) { - final MmsMessage msg = new MmsMessage(); - msg.load(cursor); - return msg; - } - /** - * Add a loaded MMS part - * - * @param part - */ - public void addPart(final MmsPart part) { - mParts.add(part); - } - - public List getParts() { - return mParts; - } - - public long getSize() { - if (!mPartsProcessed) { - processParts(); - } - return mSize; - } - - /** - * Process loaded MMS parts to obtain the combined text, the combined attachment url, - * the combined content type and the combined size. - */ - private void processParts() { - if (mPartsProcessed) { - return; - } - mPartsProcessed = true; - // Remember the width and height of the first media part - // These are needed when building attachment list - long sizeOfParts = 0L; - for (final MmsPart part : mParts) { - sizeOfParts += part.mSize; - } - if (mSize <= 0) { - mSize = mSubject != null ? mSubject.getBytes().length : 0L; - mSize += sizeOfParts; - } - } - - @Override - public String getUri() { - return mUri; - } - - public long getId() { - return mRowId; - } - - public int getSubId() { - return mSubId; - } - - @Override - public int getProtocol() { - return MessageData.PROTOCOL_MMS; - } - - @Override - public long getTimestampInMillis() { - return mTimestampInMillis; - } - - @Override - public int describeContents() { - return 0; - } - - public void setSender(final String sender) { - mSender = sender; - } - - private MmsMessage(final Parcel in) { - mUri = in.readString(); - mRowId = in.readLong(); - mTimestampInMillis = in.readLong(); - mSentTimestampInMillis = in.readLong(); - mType = in.readInt(); - mThreadId = in.readLong(); - mStatus = in.readInt(); - mRead = in.readInt() != 0; - mSeen = in.readInt() != 0; - mSubId = in.readInt(); - - // MMS specific - mSubject = in.readString(); - mContentLocation = in.readString(); - mTransactionId = in.readString(); - mSender = in.readString(); - - mSize = in.readLong(); - mExpiryInMillis = in.readLong(); - - mSubjectCharset = in.readInt(); - mPriority = in.readInt(); - mMmsMessageType = in.readInt(); - mResponseStatus = in.readInt(); - mRetrieveStatus = in.readInt(); - - final int nParts = in.readInt(); - mParts = new ArrayList(); - mPartsProcessed = false; - for (int i = 0; i < nParts; i++) { - mParts.add((MmsPart) in.readParcelable(getClass().getClassLoader())); - } - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MmsMessage createFromParcel(final Parcel in) { - return new MmsMessage(in); - } - - @Override - public MmsMessage[] newArray(final int size) { - return new MmsMessage[size]; - } - }; - - @Override - public void writeToParcel(final Parcel out, final int flags) { - out.writeString(mUri); - out.writeLong(mRowId); - out.writeLong(mTimestampInMillis); - out.writeLong(mSentTimestampInMillis); - out.writeInt(mType); - out.writeLong(mThreadId); - out.writeInt(mStatus); - out.writeInt(mRead ? 1 : 0); - out.writeInt(mSeen ? 1 : 0); - out.writeInt(mSubId); - - out.writeString(mSubject); - out.writeString(mContentLocation); - out.writeString(mTransactionId); - out.writeString(mSender); - - out.writeLong(mSize); - out.writeLong(mExpiryInMillis); - - out.writeInt(mSubjectCharset); - out.writeInt(mPriority); - out.writeInt(mMmsMessageType); - out.writeInt(mResponseStatus); - out.writeInt(mRetrieveStatus); - - out.writeInt(mParts.size()); - for (final MmsPart part : mParts) { - out.writeParcelable(part, 0); - } - } - } - - /** - * Part of an MMS message - */ - public static class MmsPart implements Parcelable { - public static final String[] PROJECTION = new String[] { - Mms.Part._ID, - Mms.Part.MSG_ID, - Mms.Part.CHARSET, - Mms.Part.CONTENT_TYPE, - Mms.Part.TEXT, - }; - private static int sIota = 0; - public static final int INDEX_ID = sIota++; - public static final int INDEX_MSG_ID = sIota++; - public static final int INDEX_CHARSET = sIota++; - public static final int INDEX_CONTENT_TYPE = sIota++; - public static final int INDEX_TEXT = sIota++; - - public String mUri; - public long mRowId; - public long mMessageId; - public String mContentType; - public String mText; - public int mCharset; - private int mWidth; - private int mHeight; - public long mSize; - - private MmsPart() { - } - - /** - * Load from a cursor of a query that returns the MMS part to import - * - * @param cursor - */ - public void load(final Cursor cursor, final boolean loadMedia) { - mRowId = cursor.getLong(INDEX_ID); - mMessageId = cursor.getLong(INDEX_MSG_ID); - mContentType = cursor.getString(INDEX_CONTENT_TYPE); - mText = cursor.getString(INDEX_TEXT); - mCharset = cursor.getInt(INDEX_CHARSET); - mWidth = 0; - mHeight = 0; - mSize = 0; - if (isMedia()) { - // For importing we don't load media since performance is critical - // For loading when we receive mms, we do load media to get enough - // information of the media file - if (loadMedia) { - if (ContentType.isImageType(mContentType)) { - loadImage(); - } else if (ContentType.isVideoType(mContentType)) { - loadVideo(); - } // No need to load audio for parsing - mSize = MmsUtils.getMediaFileSize(getDataUri()); - } - } else { - // Load text if not media type - loadText(); - } - mUri = Uri.withAppendedPath(Mms.CONTENT_URI, cursor.getString(INDEX_ID)).toString(); - } - - /** - * Get content type from file extension - */ - private static String extractContentType(final Context context, final Uri uri) { - final String path = uri.getPath(); - final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String extension = MimeTypeMap.getFileExtensionFromUrl(path); - if (TextUtils.isEmpty(extension)) { - // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle - // urlEncoded strings. Let's try one last time at finding the extension. - final int dotPos = path.lastIndexOf('.'); - if (0 <= dotPos) { - extension = path.substring(dotPos + 1); - } - } - return mimeTypeMap.getMimeTypeFromExtension(extension); - } - - /** - * Get text of a text part - */ - private void loadText() { - byte[] data = null; - if (isEmbeddedTextType()) { - // Embedded text, get from the "text" column - if (!TextUtils.isEmpty(mText)) { - data = getStringBytes(mText, mCharset); - } - } else { - // Not embedded, load from disk - final ContentResolver resolver = - Factory.get().getApplicationContext().getContentResolver(); - final Uri uri = getDataUri(); - InputStream is = null; - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - is = resolver.openInputStream(uri); - final byte[] buffer = new byte[256]; - int len = is.read(buffer); - while (len >= 0) { - baos.write(buffer, 0, len); - len = is.read(buffer); - } - } catch (final IOException e) { - LogUtil.e(TAG, - "DatabaseMessages.MmsPart: loading text from file failed: " + e, e); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - LogUtil.e(TAG, "DatabaseMessages.MmsPart: close file failed: " + e, e); - } - } - } - data = baos.toByteArray(); - } - if (data != null && data.length > 0) { - mSize = data.length; - mText = getDecodedString(data, mCharset); - } - } - - /** - * Load image file of an image part and parse the dimensions and type - */ - private void loadImage() { - final Context context = Factory.get().getApplicationContext(); - final ContentResolver resolver = context.getContentResolver(); - final Uri uri = getDataUri(); - // We have to get the width and height of the image -- they're needed when adding - // an attachment in bugle. - InputStream is = null; - try { - is = resolver.openInputStream(uri); - final BitmapFactory.Options opt = new BitmapFactory.Options(); - opt.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, opt); - mContentType = opt.outMimeType; - mWidth = opt.outWidth; - mHeight = opt.outHeight; - if (TextUtils.isEmpty(mContentType)) { - // BitmapFactory couldn't figure out the image type. That's got to be a bad - // sign, but see if we can figure it out from the file extension. - mContentType = extractContentType(context, uri); - } - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "DatabaseMessages.MmsPart.loadImage: file not found", e); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - Log.e(TAG, "IOException caught while closing stream", e); - } - } - } - } - - /** - * Load video file of a video part and parse the dimensions and type - */ - private void loadVideo() { - // This is a coarse check, and should not be applied to outgoing messages. However, - // currently, this does not cause any problems. - if (!VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()) { - return; - } - final Uri uri = getDataUri(); - final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper(); - try { - retriever.setDataSource(uri); - // FLAG: This inadvertently fixes a problem with phone receiving audio - // messages on some carrier. We should handle this in a less accidental way so that - // we don't break it again. (The carrier changes the content type in the wrapper - // in-transit from audio/mp4 to video/3gpp without changing the data) - // Also note: There is a bug in some OEM device where mmr returns - // video/ffmpeg for image files. That shouldn't happen here but be aware. - mContentType = - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE); - final Bitmap bitmap = retriever.getFrameAtTime(-1); - if (bitmap != null) { - mWidth = bitmap.getWidth(); - mHeight = bitmap.getHeight(); - } else { - // Get here if it's not actually video (see above) - LogUtil.i(LogUtil.BUGLE_TAG, "loadVideo: Got null bitmap from " + uri); - } - } catch (IOException e) { - LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting metadata from " + uri, e); - } finally { - retriever.release(); - } - } - - /** - * Get media file size - */ - private long getMediaFileSize() { - final Context context = Factory.get().getApplicationContext(); - final Uri uri = getDataUri(); - AssetFileDescriptor fd = null; - try { - fd = context.getContentResolver().openAssetFileDescriptor(uri, "r"); - if (fd != null) { - return fd.getParcelFileDescriptor().getStatSize(); - } - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e); - } finally { - if (fd != null) { - try { - fd.close(); - } catch (final IOException e) { - LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e); - } - } - } - return 0L; - } - - /** - * @return If the type is a text type that stores text embedded (i.e. in db table) - */ - private boolean isEmbeddedTextType() { - return ContentType.TEXT_PLAIN.equals(mContentType) - || ContentType.APP_SMIL.equals(mContentType) - || ContentType.TEXT_HTML.equals(mContentType); - } - - /** - * Get an instance of the MMS part from the part table cursor - * - * @param cursor - * @param loadMedia Whether to load the media file of the part - * @return - */ - public static MmsPart get(final Cursor cursor, final boolean loadMedia) { - final MmsPart part = new MmsPart(); - part.load(cursor, loadMedia); - return part; - } - - public boolean isText() { - return ContentType.TEXT_PLAIN.equals(mContentType) - || ContentType.TEXT_HTML.equals(mContentType) - || ContentType.APP_WAP_XHTML.equals(mContentType); - } - - public boolean isMedia() { - return ContentType.isImageType(mContentType) - || ContentType.isVideoType(mContentType) - || ContentType.isAudioType(mContentType) - || ContentType.isVCardType(mContentType); - } - - public boolean isImage() { - return ContentType.isImageType(mContentType); - } - - public Uri getDataUri() { - return Uri.parse("content://mms/part/" + mRowId); - } - - @Override - public int describeContents() { - return 0; - } - - private MmsPart(final Parcel in) { - mUri = in.readString(); - mRowId = in.readLong(); - mMessageId = in.readLong(); - mContentType = in.readString(); - mText = in.readString(); - mCharset = in.readInt(); - mWidth = in.readInt(); - mHeight = in.readInt(); - mSize = in.readLong(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public MmsPart createFromParcel(final Parcel in) { - return new MmsPart(in); - } - - @Override - public MmsPart[] newArray(final int size) { - return new MmsPart[size]; - } - }; - - @Override - public void writeToParcel(final Parcel out, final int flags) { - out.writeString(mUri); - out.writeLong(mRowId); - out.writeLong(mMessageId); - out.writeString(mContentType); - out.writeString(mText); - out.writeInt(mCharset); - out.writeInt(mWidth); - out.writeInt(mHeight); - out.writeLong(mSize); - } - } - - /** - * This class provides the same DatabaseMessage interface over a local SMS db message - */ - public static class LocalDatabaseMessage extends DatabaseMessage implements Parcelable { - private final int mProtocol; - private final String mUri; - private final long mTimestamp; - private final long mLocalId; - private final String mConversationId; - - public LocalDatabaseMessage(final long localId, final int protocol, final String uri, - final long timestamp, final String conversationId) { - mLocalId = localId; - mProtocol = protocol; - mUri = uri; - mTimestamp = timestamp; - mConversationId = conversationId; - } - - @Override - public int getProtocol() { - return mProtocol; - } - - @Override - public long getTimestampInMillis() { - return mTimestamp; - } - - @Override - public String getUri() { - return mUri; - } - - public long getLocalId() { - return mLocalId; - } - - public String getConversationId() { - return mConversationId; - } - - @Override - public int describeContents() { - return 0; - } - - private LocalDatabaseMessage(final Parcel in) { - mUri = in.readString(); - mConversationId = in.readString(); - mLocalId = in.readLong(); - mTimestamp = in.readLong(); - mProtocol = in.readInt(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public LocalDatabaseMessage createFromParcel(final Parcel in) { - return new LocalDatabaseMessage(in); - } - - @Override - public LocalDatabaseMessage[] newArray(final int size) { - return new LocalDatabaseMessage[size]; - } - }; - - @Override - public void writeToParcel(final Parcel out, final int flags) { - out.writeString(mUri); - out.writeString(mConversationId); - out.writeLong(mLocalId); - out.writeLong(mTimestamp); - out.writeInt(mProtocol); - } - } - - /** - * Address for MMS message - */ - public static class MmsAddr { - public static final String[] PROJECTION = new String[] { - Mms.Addr.ADDRESS, - Mms.Addr.CHARSET, - }; - private static int sIota = 0; - public static final int INDEX_ADDRESS = sIota++; - public static final int INDEX_CHARSET = sIota++; - - public static String get(final Cursor cursor) { - final int charset = cursor.getInt(INDEX_CHARSET); - // PduPersister stores the addresses using ISO_8859_1 - // Let's load it using that encoding and convert it back to its original - // See PduPersister.persistAddress - return getDecodedString( - getStringBytes(cursor.getString(INDEX_ADDRESS), CharacterSets.ISO_8859_1), - charset); - } - } - - /** - * Decoded string by character set - */ - public static String getDecodedString(final byte[] data, final int charset) { - if (CharacterSets.ANY_CHARSET == charset) { - return new String(data); // system default encoding. - } else { - try { - final String name = CharacterSets.getMimeName(charset); - return new String(data, name); - } catch (final UnsupportedEncodingException e) { - try { - return new String(data, CharacterSets.MIMENAME_ISO_8859_1); - } catch (final UnsupportedEncodingException exception) { - return new String(data); // system default encoding. - } - } - } - } - - /** - * Unpack a given String into a byte[]. - */ - public static byte[] getStringBytes(final String data, final int charset) { - if (CharacterSets.ANY_CHARSET == charset) { - return data.getBytes(); - } else { - try { - final String name = CharacterSets.getMimeName(charset); - return data.getBytes(name); - } catch (final UnsupportedEncodingException e) { - return data.getBytes(); - } - } - } -} diff --git a/src/com/android/messaging/sms/MmsConfig.java b/src/com/android/messaging/sms/MmsConfig.java deleted file mode 100755 index f13d785..0000000 --- a/src/com/android/messaging/sms/MmsConfig.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.os.Bundle; -import android.support.v7.mms.CarrierConfigValuesLoader; -import android.telephony.SubscriptionInfo; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; -import com.google.common.collect.Maps; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * MMS configuration. - * - * This is now a wrapper around the BugleCarrierConfigValuesLoader, which does - * the actual loading and stores the values in a Bundle. This class provides getter - * methods for values used in the app, which is easier to use than the raw loader - * class. - */ -public class MmsConfig { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final int DEFAULT_MAX_TEXT_LENGTH = 2000; - - /* - * Key types - */ - public static final String KEY_TYPE_INT = "int"; - public static final String KEY_TYPE_BOOL = "bool"; - public static final String KEY_TYPE_STRING = "string"; - - private static final Map sKeyTypeMap = Maps.newHashMap(); - static { - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_MMS, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS, - KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, - KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES, - KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_READ_REPORTS, KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_DELIVERY_REPORTS, - KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER, - KEY_TYPE_BOOL); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD, - KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH, KEY_TYPE_INT); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME, KEY_TYPE_STRING); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS, KEY_TYPE_STRING); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER, KEY_TYPE_STRING); - sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX, KEY_TYPE_STRING); - } - - // A map that stores all MmsConfigs, one per active subscription. For pre-LMSim, this will - // contain just one entry with the default self sub id; for LMSim and above, this will contain - // all active sub ids but the default subscription id - the default subscription id will be - // resolved to an active sub id during runtime. - private static final Map sSubIdToMmsConfigMap = Maps.newHashMap(); - // The fallback values - private static final MmsConfig sFallback = - new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, new Bundle()); - - // Per-subscription configuration values. - private final Bundle mValues; - private final int mSubId; - - /** - * Retrieves the MmsConfig instance associated with the given {@code subId} - */ - public static MmsConfig get(final int subId) { - final int realSubId = PhoneUtils.getDefault().getEffectiveSubId(subId); - synchronized (sSubIdToMmsConfigMap) { - final MmsConfig mmsConfig = sSubIdToMmsConfigMap.get(realSubId); - if (mmsConfig == null) { - // The subId is no longer valid. Fall back to the default config. - LogUtil.e(LogUtil.BUGLE_TAG, "Get mms config failed: invalid subId. subId=" + subId - + ", real subId=" + realSubId - + ", map=" + sSubIdToMmsConfigMap.keySet()); - return sFallback; - } - return mmsConfig; - } - } - - private MmsConfig(final int subId, final Bundle values) { - mSubId = subId; - mValues = values; - } - - /** - * Same as load() but doing it using an async thread from SafeAsyncTask thread pool. - */ - public static void loadAsync() { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - load(); - } - }); - } - - /** - * Reload the device and per-subscription settings. - */ - public static synchronized void load() { - final BugleCarrierConfigValuesLoader loader = Factory.get().getCarrierConfigValuesLoader(); - // Rebuild the entire MmsConfig map. - sSubIdToMmsConfigMap.clear(); - loader.reset(); - if (OsUtil.isAtLeastL_MR1()) { - final List subInfoRecords = - PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList(); - if (subInfoRecords == null) { - LogUtil.w(TAG, "Loading mms config failed: no active SIM"); - return; - } - for (SubscriptionInfo subInfoRecord : subInfoRecords) { - final int subId = subInfoRecord.getSubscriptionId(); - final Bundle values = loader.get(subId); - addMmsConfig(new MmsConfig(subId, values)); - } - } else { - final Bundle values = loader.get(ParticipantData.DEFAULT_SELF_SUB_ID); - addMmsConfig(new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, values)); - } - } - - private static void addMmsConfig(MmsConfig mmsConfig) { - Assert.isTrue(OsUtil.isAtLeastL_MR1() != - (mmsConfig.mSubId == ParticipantData.DEFAULT_SELF_SUB_ID)); - sSubIdToMmsConfigMap.put(mmsConfig.mSubId, mmsConfig); - } - - public int getSmsToMmsTextThreshold() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, - CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT); - } - - public int getSmsToMmsTextLengthThreshold() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD, - CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT); - } - - public int getMaxMessageSize() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, - CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT); - } - - /** - * Return the largest MaxMessageSize for any subid - */ - public static int getMaxMaxMessageSize() { - int maxMax = 0; - for (MmsConfig config : sSubIdToMmsConfigMap.values()) { - maxMax = Math.max(maxMax, config.getMaxMessageSize()); - } - return maxMax > 0 ? maxMax : sFallback.getMaxMessageSize(); - } - - public boolean getTransIdEnabled() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID, - CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID_DEFAULT); - } - - public String getEmailGateway() { - return mValues.getString(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER, - CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT); - } - - public int getMaxImageHeight() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT, - CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT_DEFAULT); - } - - public int getMaxImageWidth() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH, - CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH_DEFAULT); - } - - public int getRecipientLimit() { - final int limit = mValues.getInt(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT, - CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT_DEFAULT); - return limit < 0 ? Integer.MAX_VALUE : limit; - } - - public int getMaxTextLimit() { - final int max = mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE, - CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT); - return max > -1 ? max : DEFAULT_MAX_TEXT_LENGTH; - } - - public boolean getMultipartSmsEnabled() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS, - CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS_DEFAULT); - } - - public boolean getSendMultipartSmsAsSeparateMessages() { - return mValues.getBoolean( - CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES, - CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT); - } - - public boolean getSMSDeliveryReportsEnabled() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS, - CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT); - } - - public boolean getNotifyWapMMSC() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC, - CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT); - } - - public boolean isAliasEnabled() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED, - CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED_DEFAULT); - } - - public int getAliasMinChars() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS, - CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS_DEFAULT); - } - - public int getAliasMaxChars() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS, - CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS_DEFAULT); - } - - public boolean getAllowAttachAudio() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO, - CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT); - } - - public int getMaxSubjectLength() { - return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH, - CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH_DEFAULT); - } - - public boolean getGroupMmsEnabled() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS, - CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS_DEFAULT); - } - - public boolean getSupportMmsContentDisposition() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, - CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT); - } - - public boolean getShowCellBroadcast() { - return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS, - CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT); - } - - public Object getValue(final String key) { - return mValues.get(key); - } - - public Set keySet() { - return mValues.keySet(); - } - - public static String getKeyType(final String key) { - return sKeyTypeMap.get(key); - } - - public void update(final String type, final String key, final String value) { - BugleCarrierConfigValuesLoader.update(mValues, type, key, value); - } -} diff --git a/src/com/android/messaging/sms/MmsFailureException.java b/src/com/android/messaging/sms/MmsFailureException.java deleted file mode 100644 index dd702ee..0000000 --- a/src/com/android/messaging/sms/MmsFailureException.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.Assert; - -/** - * Exception for MMS failures - */ -public class MmsFailureException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * Hint of how we should retry in case of failure. Take values defined in MmsUtils. - */ - public final int retryHint; - - /** - * If set, provides a more detailed reason for the failure. - */ - public final int rawStatus; - - private void checkRetryHint() { - Assert.isTrue(retryHint == MmsUtils.MMS_REQUEST_AUTO_RETRY - || retryHint == MmsUtils.MMS_REQUEST_MANUAL_RETRY - || retryHint == MmsUtils.MMS_REQUEST_NO_RETRY); - } - /** - * Creates a new MmsFailureException. - * - * @param retryHint Hint for how to retry - */ - public MmsFailureException(final int retryHint) { - super(); - this.retryHint = retryHint; - checkRetryHint(); - this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - } - - public MmsFailureException(final int retryHint, final int rawStatus) { - super(); - this.retryHint = retryHint; - checkRetryHint(); - this.rawStatus = rawStatus; - } - - /** - * Creates a new MmsFailureException with the specified detail message. - * - * @param retryHint Hint for how to retry - * @param message the detail message. - */ - public MmsFailureException(final int retryHint, String message) { - super(message); - this.retryHint = retryHint; - checkRetryHint(); - this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - } - - /** - * Creates a new MmsFailureException with the specified cause. - * - * @param retryHint Hint for how to retry - * @param cause the cause. - */ - public MmsFailureException(final int retryHint, Throwable cause) { - super(cause); - this.retryHint = retryHint; - checkRetryHint(); - this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - } - - /** - * Creates a new MmsFailureException - * with the specified detail message and cause. - * - * @param retryHint Hint for how to retry - * @param message the detail message. - * @param cause the cause. - */ - public MmsFailureException(final int retryHint, String message, Throwable cause) { - super(message, cause); - this.retryHint = retryHint; - checkRetryHint(); - this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - } -} diff --git a/src/com/android/messaging/sms/MmsSender.java b/src/com/android/messaging/sms/MmsSender.java deleted file mode 100644 index 6dfa81a..0000000 --- a/src/com/android/messaging/sms/MmsSender.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.mms.MmsManager; -import android.telephony.SmsManager; - -import com.android.messaging.datamodel.MmsFileProvider; -import com.android.messaging.datamodel.action.SendMessageAction; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.mmslib.InvalidHeaderValueException; -import com.android.messaging.mmslib.pdu.AcknowledgeInd; -import com.android.messaging.mmslib.pdu.EncodedStringValue; -import com.android.messaging.mmslib.pdu.GenericPdu; -import com.android.messaging.mmslib.pdu.NotifyRespInd; -import com.android.messaging.mmslib.pdu.PduComposer; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.mmslib.pdu.PduParser; -import com.android.messaging.mmslib.pdu.RetrieveConf; -import com.android.messaging.mmslib.pdu.SendConf; -import com.android.messaging.mmslib.pdu.SendReq; -import com.android.messaging.receiver.SendStatusReceiver; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * Class that sends chat message via MMS. - * - * The interface emulates a blocking send similar to making an HTTP request. - */ -public class MmsSender { - private static final String TAG = LogUtil.BUGLE_TAG; - - /** - * Send an MMS message. - * - * @param context Context - * @param messageUri The unique URI of the message for identifying it during sending - * @param sendReq The SendReq PDU of the message - * @throws MmsFailureException - */ - public static void sendMms(final Context context, final int subId, final Uri messageUri, - final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException { - sendMms(context, - subId, - messageUri, - null /* locationUrl */, - sendReq, - true /* responseImportant */, - sentIntentExras); - } - - /** - * Send NotifyRespInd (response to mms auto download). - * - * @param context Context - * @param subId subscription to use to send the response - * @param transactionId The transaction id of the MMS message - * @param contentLocation The url of the MMS message - * @param status The status to send with the NotifyRespInd - * @throws MmsFailureException - * @throws InvalidHeaderValueException - */ - public static void sendNotifyResponseForMmsDownload(final Context context, final int subId, - final byte[] transactionId, final String contentLocation, final int status) - throws MmsFailureException, InvalidHeaderValueException { - // Create the M-NotifyResp.ind - final NotifyRespInd notifyRespInd = new NotifyRespInd( - PduHeaders.CURRENT_MMS_VERSION, transactionId, status); - final Uri messageUri = Uri.parse(contentLocation); - // Pack M-NotifyResp.ind and send it - sendMms(context, - subId, - messageUri, - MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null, - notifyRespInd, - false /* responseImportant */, - null /* sentIntentExtras */); - } - - /** - * Send AcknowledgeInd (response to mms manual download). Ignore failures. - * - * @param context Context - * @param subId The SIM's subId we are currently using - * @param transactionId The transaction id of the MMS message - * @param contentLocation The url of the MMS message - * @throws MmsFailureException - * @throws InvalidHeaderValueException - */ - public static void sendAcknowledgeForMmsDownload(final Context context, final int subId, - final byte[] transactionId, final String contentLocation) - throws MmsFailureException, InvalidHeaderValueException { - final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/); - // Create the M-Acknowledge.ind - final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION, - transactionId); - acknowledgeInd.setFrom(new EncodedStringValue(selfNumber)); - final Uri messageUri = Uri.parse(contentLocation); - // Sending - sendMms(context, - subId, - messageUri, - MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null, - acknowledgeInd, - false /*responseImportant*/, - null /* sentIntentExtras */); - } - - /** - * Send a generic PDU. - * - * @param context Context - * @param messageUri The unique URI of the message for identifying it during sending - * @param locationUrl The optional URL to send to - * @param pdu The PDU to send - * @param responseImportant If the sending response is important. Responses to the - * Sending of AcknowledgeInd and NotifyRespInd are not important. - * @throws MmsFailureException - */ - private static void sendMms(final Context context, final int subId, final Uri messageUri, - final String locationUrl, final GenericPdu pdu, final boolean responseImportant, - final Bundle sentIntentExtras) throws MmsFailureException { - // Write PDU to temporary file to send to platform - final Uri contentUri = writePduToTempFile(context, pdu, subId); - - // Construct PendingIntent that will notify us when message sending is complete - final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION, - messageUri, - context, - SendStatusReceiver.class); - sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri); - sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant); - if (sentIntentExtras != null) { - sentIntent.putExtras(sentIntentExtras); - } - final PendingIntent sentPendingIntent = PendingIntent.getBroadcast( - context, - 0 /*request code*/, - sentIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - // Send the message - MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl, - sentPendingIntent); - } - - private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId) - throws MmsFailureException { - final Uri contentUri = MmsFileProvider.buildRawMmsUri(); - final File tempFile = MmsFileProvider.getFile(contentUri); - FileOutputStream writer = null; - try { - // Ensure rawmms directory exists - tempFile.getParentFile().mkdirs(); - writer = new FileOutputStream(tempFile); - final byte[] pduBytes = new PduComposer(context, pdu).make(); - if (pduBytes == null) { - throw new MmsFailureException( - MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU"); - } - if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) { - throw new MmsFailureException( - MmsUtils.MMS_REQUEST_NO_RETRY, - MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG); - } - writer.write(pduBytes); - } catch (final IOException e) { - if (tempFile != null) { - tempFile.delete(); - } - LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e); - throw new MmsFailureException( - MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file"); - } catch (final OutOfMemoryError e) { - if (tempFile != null) { - tempFile.delete(); - } - LogUtil.e(TAG, "Out of memory in composing PDU", e); - throw new MmsFailureException( - MmsUtils.MMS_REQUEST_MANUAL_RETRY, - MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (final IOException e) { - // no action we can take here - } - } - } - return contentUri; - } - - public static SendConf parseSendConf(byte[] response, int subId) { - if (response != null) { - final GenericPdu respPdu = new PduParser( - response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse(); - if (respPdu != null) { - if (respPdu instanceof SendConf) { - return (SendConf) respPdu; - } else { - LogUtil.e(TAG, "MmsSender: send response not SendConf"); - } - } else { - // Invalid PDU - LogUtil.e(TAG, "MmsSender: send invalid response"); - } - } - // Empty or invalid response - return null; - } - - /** - * Download an MMS message. - * - * @param context Context - * @param contentLocation The url of the MMS message - * @throws MmsFailureException - * @throws InvalidHeaderValueException - */ - public static void downloadMms(final Context context, final int subId, - final String contentLocation, Bundle extras) throws MmsFailureException, - InvalidHeaderValueException { - final Uri requestUri = Uri.parse(contentLocation); - final Uri contentUri = MmsFileProvider.buildRawMmsUri(); - - final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION, - requestUri, - context, - SendStatusReceiver.class); - downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri); - if (extras != null) { - downloadedIntent.putExtras(extras); - } - final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast( - context, - 0 /*request code*/, - downloadedIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri, - downloadedPendingIntent); - } - - public static RetrieveConf parseRetrieveConf(byte[] data, int subId) { - if (data != null) { - final GenericPdu pdu = new PduParser( - data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse(); - if (pdu != null) { - if (pdu instanceof RetrieveConf) { - return (RetrieveConf) pdu; - } else { - LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: " - + pdu.getClass().getName()); - } - } else { - LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)"); - } - } - LogUtil.e(TAG, "MmsSender: downloaded pdu is empty"); - return null; - } - - // Process different result code from platform MMS service - public static int getErrorResultStatus(int resultCode, int httpStatusCode) { - Assert.isFalse(resultCode == Activity.RESULT_OK); - switch (resultCode) { - case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS: - case SmsManager.MMS_ERROR_IO_ERROR: - return MmsUtils.MMS_REQUEST_AUTO_RETRY; - case SmsManager.MMS_ERROR_INVALID_APN: - case SmsManager.MMS_ERROR_CONFIGURATION_ERROR: - case SmsManager.MMS_ERROR_NO_DATA_NETWORK: - case SmsManager.MMS_ERROR_UNSPECIFIED: - return MmsUtils.MMS_REQUEST_MANUAL_RETRY; - case SmsManager.MMS_ERROR_HTTP_FAILURE: - if (httpStatusCode == 404) { - return MmsUtils.MMS_REQUEST_NO_RETRY; - } else { - return MmsUtils.MMS_REQUEST_AUTO_RETRY; - } - default: - return MmsUtils.MMS_REQUEST_MANUAL_RETRY; - } - } -} diff --git a/src/com/android/messaging/sms/MmsSmsUtils.java b/src/com/android/messaging/sms/MmsSmsUtils.java deleted file mode 100644 index 1a0ef99..0000000 --- a/src/com/android/messaging/sms/MmsSmsUtils.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.text.TextUtils; -import android.util.Patterns; - -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.util.LogUtil; - -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utility functions for the Messaging Service - */ -public class MmsSmsUtils { - private MmsSmsUtils() { - // Forbidden being instantiated. - } - - // An alias (or commonly called "nickname") is: - // Nickname must begin with a letter. - // Only letters a-z, numbers 0-9, or . are allowed in Nickname field. - public static boolean isAlias(final String string, final int subId) { - if (!MmsConfig.get(subId).isAliasEnabled()) { - return false; - } - - final int len = string == null ? 0 : string.length(); - - if (len < MmsConfig.get(subId).getAliasMinChars() || - len > MmsConfig.get(subId).getAliasMaxChars()) { - return false; - } - - if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter - return false; - } - for (int i = 1; i < len; i++) { - final char c = string.charAt(i); - if (!(Character.isLetterOrDigit(c) || c == '.')) { - return false; - } - } - - return true; - } - - /** - * mailbox = name-addr - * name-addr = [display-name] angle-addr - * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] - */ - public static final Pattern NAME_ADDR_EMAIL_PATTERN = - Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*"); - - public static String extractAddrSpec(final String address) { - final Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address); - - if (match.matches()) { - return match.group(2); - } - return address; - } - - /** - * Returns true if the address is an email address - * - * @param address the input address to be tested - * @return true if address is an email address - */ - public static boolean isEmailAddress(final String address) { - if (TextUtils.isEmpty(address)) { - return false; - } - - final String s = extractAddrSpec(address); - final Matcher match = Patterns.EMAIL_ADDRESS.matcher(s); - return match.matches(); - } - - /** - * Returns true if the number is a Phone number - * - * @param number the input number to be tested - * @return true if number is a Phone number - */ - public static boolean isPhoneNumber(final String number) { - if (TextUtils.isEmpty(number)) { - return false; - } - - final Matcher match = Patterns.PHONE.matcher(number); - return match.matches(); - } - - /** - * Check if MMS is required when sending to email address - * - * @param destinationHasEmailAddress destination includes an email address - * @return true if MMS is required. - */ - public static boolean getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress, - final int subId) { - if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway())) { - return false; - } else { - return destinationHasEmailAddress; - } - } - - /** - * Helper functions for the "threads" table used by MMS and SMS. - */ - public static final class Threads implements android.provider.Telephony.ThreadsColumns { - private static final String[] ID_PROJECTION = { BaseColumns._ID }; - private static final Uri THREAD_ID_CONTENT_URI = Uri.parse( - "content://mms-sms/threadID"); - public static final Uri CONTENT_URI = Uri.withAppendedPath( - android.provider.Telephony.MmsSms.CONTENT_URI, "conversations"); - - // No one should construct an instance of this class. - private Threads() { - } - - /** - * This is a single-recipient version of - * getOrCreateThreadId. It's convenient for use with SMS - * messages. - */ - public static long getOrCreateThreadId(final Context context, final String recipient) { - final Set recipients = new HashSet(); - - recipients.add(recipient); - return getOrCreateThreadId(context, recipients); - } - - /** - * Given the recipients list and subject of an unsaved message, - * return its thread ID. If the message starts a new thread, - * allocate a new thread ID. Otherwise, use the appropriate - * existing thread ID. - * - * Find the thread ID of the same set of recipients (in - * any order, without any additions). If one - * is found, return it. Otherwise, return a unique thread ID. - */ - public static long getOrCreateThreadId( - final Context context, final Set recipients) { - final Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon(); - - for (String recipient : recipients) { - if (isEmailAddress(recipient)) { - recipient = extractAddrSpec(recipient); - } - - uriBuilder.appendQueryParameter("recipient", recipient); - } - - final Uri uri = uriBuilder.build(); - //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri); - - final Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(), - uri, ID_PROJECTION, null, null, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - return cursor.getLong(0); - } else { - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, - "getOrCreateThreadId returned no rows!"); - } - } finally { - cursor.close(); - } - } - - LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "getOrCreateThreadId failed with " - + LogUtil.sanitizePII(recipients.toString())); - throw new IllegalArgumentException("Unable to find or allocate a thread ID."); - } - } -} diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java deleted file mode 100644 index 913e9a6..0000000 --- a/src/com/android/messaging/sms/MmsUtils.java +++ /dev/null @@ -1,2747 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetFileDescriptor; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.media.MediaMetadataRetriever; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.provider.Telephony; -import android.provider.Telephony.Mms; -import android.provider.Telephony.Sms; -import android.provider.Telephony.Threads; -import android.telephony.SmsManager; -import android.telephony.SmsMessage; -import android.text.TextUtils; -import android.text.util.Rfc822Token; -import android.text.util.Rfc822Tokenizer; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.action.DownloadMmsAction; -import com.android.messaging.datamodel.action.SendMessageAction; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.InvalidHeaderValueException; -import com.android.messaging.mmslib.MmsException; -import com.android.messaging.mmslib.SqliteWrapper; -import com.android.messaging.mmslib.pdu.CharacterSets; -import com.android.messaging.mmslib.pdu.EncodedStringValue; -import com.android.messaging.mmslib.pdu.GenericPdu; -import com.android.messaging.mmslib.pdu.NotificationInd; -import com.android.messaging.mmslib.pdu.PduBody; -import com.android.messaging.mmslib.pdu.PduComposer; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.mmslib.pdu.PduParser; -import com.android.messaging.mmslib.pdu.PduPart; -import com.android.messaging.mmslib.pdu.PduPersister; -import com.android.messaging.mmslib.pdu.RetrieveConf; -import com.android.messaging.mmslib.pdu.SendConf; -import com.android.messaging.mmslib.pdu.SendReq; -import com.android.messaging.sms.SmsSender.SendResult; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.EmailAddress; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.ImageUtils.ImageResizer; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaMetadataRetrieverWrapper; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.google.common.base.Joiner; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; - -/** - * Utils for sending sms/mms messages. - */ -public class MmsUtils { - private static final String TAG = LogUtil.BUGLE_TAG; - - public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false; - public static final boolean DEFAULT_READ_REPORT_MODE = false; - public static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 7 * 24 * 60 * 60; - public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; - - public static final int MAX_SMS_RETRY = 3; - - /** - * MMS request succeeded - */ - public static final int MMS_REQUEST_SUCCEEDED = 0; - /** - * MMS request failed with a transient error and can be retried automatically - */ - public static final int MMS_REQUEST_AUTO_RETRY = 1; - /** - * MMS request failed with an error and can be retried manually - */ - public static final int MMS_REQUEST_MANUAL_RETRY = 2; - /** - * MMS request failed with a specific error and should not be retried - */ - public static final int MMS_REQUEST_NO_RETRY = 3; - - public static final String getRequestStatusDescription(final int status) { - switch (status) { - case MMS_REQUEST_SUCCEEDED: - return "SUCCEEDED"; - case MMS_REQUEST_AUTO_RETRY: - return "AUTO_RETRY"; - case MMS_REQUEST_MANUAL_RETRY: - return "MANUAL_RETRY"; - case MMS_REQUEST_NO_RETRY: - return "NO_RETRY"; - default: - return String.valueOf(status) + " (check MmsUtils)"; - } - } - - public static final int PDU_HEADER_VALUE_UNDEFINED = 0; - - private static final int DEFAULT_DURATION = 5000; //ms - - // amount of space to leave in a MMS for text and overhead. - private static final int MMS_MAX_SIZE_SLOP = 1024; - public static final long INVALID_TIMESTAMP = 0L; - private static String[] sNoSubjectStrings; - - public static class MmsInfo { - public Uri mUri; - public int mMessageSize; - public PduBody mPduBody; - } - - // Sync all remote messages apart from drafts - private static final String REMOTE_SMS_SELECTION = String.format( - Locale.US, - "(%s IN (%d, %d, %d, %d, %d))", - Sms.TYPE, - Sms.MESSAGE_TYPE_INBOX, - Sms.MESSAGE_TYPE_OUTBOX, - Sms.MESSAGE_TYPE_QUEUED, - Sms.MESSAGE_TYPE_FAILED, - Sms.MESSAGE_TYPE_SENT); - - private static final String REMOTE_MMS_SELECTION = String.format( - Locale.US, - "((%s IN (%d, %d, %d, %d)) AND (%s IN (%d, %d, %d)))", - Mms.MESSAGE_BOX, - Mms.MESSAGE_BOX_INBOX, - Mms.MESSAGE_BOX_OUTBOX, - Mms.MESSAGE_BOX_SENT, - Mms.MESSAGE_BOX_FAILED, - Mms.MESSAGE_TYPE, - PduHeaders.MESSAGE_TYPE_SEND_REQ, - PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND, - PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); - - /** - * Type selection for importing sms messages. - * - * @return The SQL selection for importing sms messages - */ - public static String getSmsTypeSelectionSql() { - return REMOTE_SMS_SELECTION; - } - - /** - * Type selection for importing mms messages. - * - * @return The SQL selection for importing mms messages. This selects the message type, - * not including the selection on timestamp. - */ - public static String getMmsTypeSelectionSql() { - return REMOTE_MMS_SELECTION; - } - - // SMIL spec: http://www.w3.org/TR/SMIL3 - - private static final String sSmilImagePart = - "" + - "" + - ""; - - private static final String sSmilVideoPart = - "" + - ""; - - private static final String sSmilAudioPart = - "" + - ""; - - private static final String sSmilTextPart = - "" + - "" + - ""; - - private static final String sSmilPart = - "" + - "" + - ""; - - private static final String sSmilTextOnly = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "%s" + // constructed body goes here - "" + - ""; - - private static final String sSmilVisualAttachmentsOnly = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "%s" + // constructed body goes here - "" + - ""; - - private static final String sSmilVisualAttachmentsWithText = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "%s" + // constructed body goes here - "" + - ""; - - private static final String sSmilNonVisualAttachmentsOnly = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "%s" + // constructed body goes here - "" + - ""; - - private static final String sSmilNonVisualAttachmentsWithText = sSmilTextOnly; - - public static final String MMS_DUMP_PREFIX = "mmsdump-"; - public static final String SMS_DUMP_PREFIX = "smsdump-"; - - public static final int MIN_VIDEO_BYTES_PER_SECOND = 4 * 1024; - public static final int MIN_IMAGE_BYTE_SIZE = 16 * 1024; - public static final int MAX_VIDEO_ATTACHMENT_COUNT = 1; - - public static MmsInfo makePduBody(final Context context, final MessageData message, - final int subId) { - final PduBody pb = new PduBody(); - - // Compute data size requirements for this message: count up images and total size of - // non-image attachments. - int totalLength = 0; - int countImage = 0; - for (final MessagePartData part : message.getParts()) { - if (part.isAttachment()) { - final String contentType = part.getContentType(); - if (ContentType.isImageType(contentType)) { - countImage++; - } else if (ContentType.isVCardType(contentType)) { - totalLength += getDataLength(context, part.getContentUri()); - } else { - totalLength += getMediaFileSize(part.getContentUri()); - } - } - } - final long minSize = countImage * MIN_IMAGE_BYTE_SIZE; - final int byteBudget = MmsConfig.get(subId).getMaxMessageSize() - totalLength - - MMS_MAX_SIZE_SLOP; - final double budgetFactor = - minSize > 0 ? Math.max(1.0, byteBudget / ((double) minSize)) : 1; - final int bytesPerImage = (int) (budgetFactor * MIN_IMAGE_BYTE_SIZE); - final int widthLimit = MmsConfig.get(subId).getMaxImageWidth(); - final int heightLimit = MmsConfig.get(subId).getMaxImageHeight(); - - // Actually add the attachments, shrinking images appropriately. - int index = 0; - totalLength = 0; - boolean hasVisualAttachment = false; - boolean hasNonVisualAttachment = false; - boolean hasText = false; - final StringBuilder smilBody = new StringBuilder(); - for (final MessagePartData part : message.getParts()) { - String srcName; - if (part.isAttachment()) { - String contentType = part.getContentType(); - if (ContentType.isImageType(contentType)) { - // There's a good chance that if we selected the image from our media picker the - // content type is image/*. Fix the content type here for gifs so that we only - // need to open the input stream once. All other gif vs static image checks will - // only have to do a string comparison which is much cheaper. - final boolean isGif = ImageUtils.isGif(contentType, part.getContentUri()); - contentType = isGif ? ContentType.IMAGE_GIF : contentType; - srcName = String.format(isGif ? "image%06d.gif" : "image%06d.jpg", index); - smilBody.append(String.format(sSmilImagePart, srcName)); - totalLength += addPicturePart(context, pb, index, part, - widthLimit, heightLimit, bytesPerImage, srcName, contentType); - hasVisualAttachment = true; - } else if (ContentType.isVideoType(contentType)) { - srcName = String.format("video%06d.mp4", index); - final int length = addVideoPart(context, pb, part, srcName); - totalLength += length; - smilBody.append(String.format(sSmilVideoPart, srcName, - getMediaDurationMs(context, part, DEFAULT_DURATION))); - hasVisualAttachment = true; - } else if (ContentType.isVCardType(contentType)) { - srcName = String.format("contact%06d.vcf", index); - totalLength += addVCardPart(context, pb, part, srcName); - smilBody.append(String.format(sSmilPart, srcName)); - hasNonVisualAttachment = true; - } else if (ContentType.isAudioType(contentType)) { - srcName = String.format("recording%06d.amr", index); - totalLength += addOtherPart(context, pb, part, srcName); - final int duration = getMediaDurationMs(context, part, -1); - Assert.isTrue(duration != -1); - smilBody.append(String.format(sSmilAudioPart, srcName, duration)); - hasNonVisualAttachment = true; - } else { - srcName = String.format("other%06d.dat", index); - totalLength += addOtherPart(context, pb, part, srcName); - smilBody.append(String.format(sSmilPart, srcName)); - } - index++; - } - if (!TextUtils.isEmpty(part.getText())) { - hasText = true; - } - } - - if (hasText) { - final String srcName = String.format("text.%06d.txt", index); - final String text = message.getMessageText(); - totalLength += addTextPart(context, pb, text, srcName); - - // Append appropriate SMIL to the body. - smilBody.append(String.format(sSmilTextPart, srcName)); - } - - final String smilTemplate = getSmilTemplate(hasVisualAttachment, - hasNonVisualAttachment, hasText); - addSmilPart(pb, smilTemplate, smilBody.toString()); - - final MmsInfo mmsInfo = new MmsInfo(); - mmsInfo.mPduBody = pb; - mmsInfo.mMessageSize = totalLength; - - return mmsInfo; - } - - private static int getMediaDurationMs(final Context context, final MessagePartData part, - final int defaultDurationMs) { - Assert.notNull(context); - Assert.notNull(part); - Assert.isTrue(ContentType.isAudioType(part.getContentType()) || - ContentType.isVideoType(part.getContentType())); - - final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper(); - try { - retriever.setDataSource(part.getContentUri()); - return retriever.extractInteger( - MediaMetadataRetriever.METADATA_KEY_DURATION, defaultDurationMs); - } catch (final IOException e) { - LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting duration from " + part.getContentUri(), e); - return defaultDurationMs; - } finally { - retriever.release(); - } - } - - private static void setPartContentLocationAndId(final PduPart part, final String srcName) { - // Set Content-Location. - part.setContentLocation(srcName.getBytes()); - - // Set Content-Id. - final int index = srcName.lastIndexOf("."); - final String contentId = (index == -1) ? srcName : srcName.substring(0, index); - part.setContentId(contentId.getBytes()); - } - - private static int addTextPart(final Context context, final PduBody pb, - final String text, final String srcName) { - final PduPart part = new PduPart(); - - // Set Charset if it's a text media. - part.setCharset(CharacterSets.UTF_8); - - // Set Content-Type. - part.setContentType(ContentType.TEXT_PLAIN.getBytes()); - - // Set Content-Location. - setPartContentLocationAndId(part, srcName); - - part.setData(text.getBytes()); - - pb.addPart(part); - - return part.getData().length; - } - - private static int addPicturePart(final Context context, final PduBody pb, final int index, - final MessagePartData messagePart, int widthLimit, int heightLimit, - final int maxPartSize, final String srcName, final String contentType) { - final Uri imageUri = messagePart.getContentUri(); - final int width = messagePart.getWidth(); - final int height = messagePart.getHeight(); - - // Swap the width and height limits to match the orientation of the image so we scale the - // picture as little as possible. - if ((height > width) != (heightLimit > widthLimit)) { - final int temp = widthLimit; - widthLimit = heightLimit; - heightLimit = temp; - } - - final int orientation = ImageUtils.getOrientation(context, imageUri); - int imageSize = getDataLength(context, imageUri); - if (imageSize <= 0) { - LogUtil.e(TAG, "Can't get image", new Exception()); - return 0; - } - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addPicturePart size: " + imageSize + " width: " - + width + " widthLimit: " + widthLimit - + " height: " + height - + " heightLimit: " + heightLimit); - } - - PduPart part; - // Check if we're already within the limits - in which case we don't need to resize. - // The size can be zero here, even when the media has content. See the comment in - // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the - // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly - // set the size. - if (imageSize <= maxPartSize && - width <= widthLimit && - height <= heightLimit && - (orientation == android.media.ExifInterface.ORIENTATION_UNDEFINED || - orientation == android.media.ExifInterface.ORIENTATION_NORMAL)) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addPicturePart - already sized"); - } - part = new PduPart(); - part.setDataUri(imageUri); - part.setContentType(contentType.getBytes()); - } else { - part = getResizedImageAsPart(widthLimit, heightLimit, maxPartSize, - width, height, orientation, imageUri, context, contentType); - if (part == null) { - final OutOfMemoryError e = new OutOfMemoryError(); - LogUtil.e(TAG, "Can't resize image: not enough memory?", e); - throw e; - } - imageSize = part.getData().length; - } - - setPartContentLocationAndId(part, srcName); - - pb.addPart(index, part); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addPicturePart size: " + imageSize); - } - - return imageSize; - } - - private static void addPartForUri(final Context context, final PduBody pb, - final String srcName, final Uri uri, final String contentType) { - final PduPart part = new PduPart(); - part.setDataUri(uri); - part.setContentType(contentType.getBytes()); - - setPartContentLocationAndId(part, srcName); - - pb.addPart(part); - } - - private static int addVCardPart(final Context context, final PduBody pb, - final MessagePartData messagePart, final String srcName) { - final Uri vcardUri = messagePart.getContentUri(); - final String contentType = messagePart.getContentType(); - final int vcardSize = getDataLength(context, vcardUri); - if (vcardSize <= 0) { - LogUtil.e(TAG, "Can't get vcard", new Exception()); - return 0; - } - - addPartForUri(context, pb, srcName, vcardUri, contentType); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addVCardPart size: " + vcardSize); - } - - return vcardSize; - } - - /** - * Add video part recompressing video if necessary. If recompression fails, part is not - * added. - */ - private static int addVideoPart(final Context context, final PduBody pb, - final MessagePartData messagePart, final String srcName) { - final Uri attachmentUri = messagePart.getContentUri(); - String contentType = messagePart.getContentType(); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString()); - } - - if (TextUtils.isEmpty(contentType)) { - contentType = ContentType.VIDEO_3G2; - } - - addPartForUri(context, pb, srcName, attachmentUri, contentType); - return (int) getMediaFileSize(attachmentUri); - } - - private static int addOtherPart(final Context context, final PduBody pb, - final MessagePartData messagePart, final String srcName) { - final Uri attachmentUri = messagePart.getContentUri(); - final String contentType = messagePart.getContentType(); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString()); - } - - final int dataSize = (int) getMediaFileSize(attachmentUri); - - addPartForUri(context, pb, srcName, attachmentUri, contentType); - - return dataSize; - } - - private static void addSmilPart(final PduBody pb, final String smilTemplate, - final String smilBody) { - final PduPart smilPart = new PduPart(); - smilPart.setContentId("smil".getBytes()); - smilPart.setContentLocation("smil.xml".getBytes()); - smilPart.setContentType(ContentType.APP_SMIL.getBytes()); - final String smil = String.format(smilTemplate, smilBody); - smilPart.setData(smil.getBytes()); - pb.addPart(0, smilPart); - } - - private static String getSmilTemplate(final boolean hasVisualAttachments, - final boolean hasNonVisualAttachments, final boolean hasText) { - if (hasVisualAttachments) { - return hasText ? sSmilVisualAttachmentsWithText : sSmilVisualAttachmentsOnly; - } - if (hasNonVisualAttachments) { - return hasText ? sSmilNonVisualAttachmentsWithText : sSmilNonVisualAttachmentsOnly; - } - return sSmilTextOnly; - } - - private static int getDataLength(final Context context, final Uri uri) { - InputStream is = null; - try { - is = context.getContentResolver().openInputStream(uri); - try { - return is == null ? 0 : is.available(); - } catch (final IOException e) { - LogUtil.e(TAG, "getDataLength couldn't stream: " + uri, e); - } - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "getDataLength couldn't open: " + uri, e); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException e) { - LogUtil.e(TAG, "getDataLength couldn't close: " + uri, e); - } - } - } - return 0; - } - - /** - * Returns {@code true} if group mms is turned on, - * {@code false} otherwise. - * - * For the group mms feature to be enabled, the following must be true: - * 1. the feature is enabled in mms_config.xml (currently on by default) - * 2. the feature is enabled in the SMS settings page - * - * @return true if group mms is supported - */ - public static boolean groupMmsEnabled(final int subId) { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId); - final String groupMmsKey = resources.getString(R.string.group_mms_pref_key); - final boolean groupMmsEnabledDefault = resources.getBoolean(R.bool.group_mms_pref_default); - final boolean groupMmsPrefOn = prefs.getBoolean(groupMmsKey, groupMmsEnabledDefault); - return MmsConfig.get(subId).getGroupMmsEnabled() && groupMmsPrefOn; - } - - /** - * Get a version of this image resized to fit the given dimension and byte-size limits. Note - * that the content type of the resulting PduPart may not be the same as the content type of - * this UriImage; always call {@link PduPart#getContentType()} to get the new content type. - * - * @param widthLimit The width limit, in pixels - * @param heightLimit The height limit, in pixels - * @param byteLimit The binary size limit, in bytes - * @param width The image width, in pixels - * @param height The image height, in pixels - * @param orientation Orientation constant from ExifInterface for rotating or flipping the - * image - * @param imageUri Uri to the image data - * @param context Needed to open the image - * @return A new PduPart containing the resized image data - */ - private static PduPart getResizedImageAsPart(final int widthLimit, - final int heightLimit, final int byteLimit, final int width, final int height, - final int orientation, final Uri imageUri, final Context context, final String contentType) { - final PduPart part = new PduPart(); - - final byte[] data = ImageResizer.getResizedImageData(width, height, orientation, - widthLimit, heightLimit, byteLimit, imageUri, context, contentType); - if (data == null) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Resize image failed."); - } - return null; - } - - part.setData(data); - // Any static images will be compressed into a jpeg - final String contentTypeOfResizedImage = ImageUtils.isGif(contentType, imageUri) - ? ContentType.IMAGE_GIF : ContentType.IMAGE_JPEG; - part.setContentType(contentTypeOfResizedImage.getBytes()); - - return part; - } - - /** - * Get media file size - */ - public static long getMediaFileSize(final Uri uri) { - final Context context = Factory.get().getApplicationContext(); - AssetFileDescriptor fd = null; - try { - fd = context.getContentResolver().openAssetFileDescriptor(uri, "r"); - if (fd != null) { - return fd.getParcelFileDescriptor().getStatSize(); - } - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "MmsUtils.getMediaFileSize: cound not find media file: " + e, e); - } finally { - if (fd != null) { - try { - fd.close(); - } catch (final IOException e) { - LogUtil.e(TAG, "MmsUtils.getMediaFileSize: failed to close " + e, e); - } - } - } - return 0L; - } - - // Code for extracting the actual phone numbers for the participants in a conversation, - // given a thread id. - - private static final Uri ALL_THREADS_URI = - Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); - - private static final String[] RECIPIENTS_PROJECTION = { - Threads._ID, - Threads.RECIPIENT_IDS - }; - - private static final int RECIPIENT_IDS = 1; - - public static List getRecipientsByThread(final long threadId) { - final String spaceSepIds = getRawRecipientIdsForThread(threadId); - if (!TextUtils.isEmpty(spaceSepIds)) { - final Context context = Factory.get().getApplicationContext(); - return getAddresses(context, spaceSepIds); - } - return null; - } - - // NOTE: There are phones on which you can't get the recipients from the thread id for SMS - // until you have a message in the conversation! - public static String getRawRecipientIdsForThread(final long threadId) { - if (threadId <= 0) { - return null; - } - final Context context = Factory.get().getApplicationContext(); - final ContentResolver cr = context.getContentResolver(); - final Cursor thread = cr.query( - ALL_THREADS_URI, - RECIPIENTS_PROJECTION, "_id=?", new String[] { String.valueOf(threadId) }, null); - if (thread != null) { - try { - if (thread.moveToFirst()) { - // recipientIds will be a space-separated list of ids into the - // canonical addresses table. - return thread.getString(RECIPIENT_IDS); - } - } finally { - thread.close(); - } - } - return null; - } - - private static final Uri SINGLE_CANONICAL_ADDRESS_URI = - Uri.parse("content://mms-sms/canonical-address"); - - private static List getAddresses(final Context context, final String spaceSepIds) { - final List numbers = new ArrayList(); - final String[] ids = spaceSepIds.split(" "); - for (final String id : ids) { - long longId; - - try { - longId = Long.parseLong(id); - if (longId < 0) { - LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id " + longId); - continue; - } - } catch (final NumberFormatException ex) { - LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id. " + ex, ex); - // skip this id - continue; - } - - // TODO: build a single query where we get all the addresses at once. - Cursor c = null; - try { - c = context.getContentResolver().query( - ContentUris.withAppendedId(SINGLE_CANONICAL_ADDRESS_URI, longId), - null, null, null, null); - } catch (final Exception e) { - LogUtil.e(TAG, "MmsUtils.getAddresses: query failed for id " + longId, e); - } - if (c != null) { - try { - if (c.moveToFirst()) { - final String number = c.getString(0); - if (!TextUtils.isEmpty(number)) { - numbers.add(number); - } else { - LogUtil.w(TAG, "Canonical MMS/SMS address is empty for id: " + longId); - } - } - } finally { - c.close(); - } - } - } - if (numbers.isEmpty()) { - LogUtil.w(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]"); - } - return numbers; - } - - // Get telephony SMS thread ID - public static long getOrCreateSmsThreadId(final Context context, final String dest) { - // use destinations to determine threadId - final Set recipients = new HashSet(); - recipients.add(dest); - try { - return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e); - return -1; - } - } - - // Get telephony SMS thread ID - public static long getOrCreateThreadId(final Context context, final List dests) { - if (dests == null || dests.size() == 0) { - return -1; - } - // use destinations to determine threadId - final Set recipients = new HashSet(dests); - try { - return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e); - return -1; - } - } - - /** - * Add an SMS to the given URI with thread_id specified. - * - * @param resolver the content resolver to use - * @param uri the URI to add the message to - * @param subId subId for the receiving sim - * @param address the address of the sender - * @param body the body of the message - * @param subject the psuedo-subject of the message - * @param date the timestamp for the message - * @param read true if the message has been read, false if not - * @param threadId the thread_id of the message - * @return the URI for the new message - */ - private static Uri addMessageToUri(final ContentResolver resolver, - final Uri uri, final int subId, final String address, final String body, - final String subject, final Long date, final boolean read, final boolean seen, - final int status, final int type, final long threadId) { - final ContentValues values = new ContentValues(7); - - values.put(Telephony.Sms.ADDRESS, address); - if (date != null) { - values.put(Telephony.Sms.DATE, date); - } - values.put(Telephony.Sms.READ, read ? 1 : 0); - values.put(Telephony.Sms.SEEN, seen ? 1 : 0); - values.put(Telephony.Sms.SUBJECT, subject); - values.put(Telephony.Sms.BODY, body); - if (OsUtil.isAtLeastL_MR1()) { - values.put(Telephony.Sms.SUBSCRIPTION_ID, subId); - } - if (status != Telephony.Sms.STATUS_NONE) { - values.put(Telephony.Sms.STATUS, status); - } - if (type != Telephony.Sms.MESSAGE_TYPE_ALL) { - values.put(Telephony.Sms.TYPE, type); - } - if (threadId != -1L) { - values.put(Telephony.Sms.THREAD_ID, threadId); - } - return resolver.insert(uri, values); - } - - // Insert an SMS message to telephony - public static Uri insertSmsMessage(final Context context, final Uri uri, final int subId, - final String dest, final String text, final long timestamp, final int status, - final int type, final long threadId) { - Uri response = null; - try { - response = addMessageToUri(context.getContentResolver(), uri, subId, dest, - text, null /* subject */, timestamp, true /* read */, - true /* seen */, status, type, threadId); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Mmsutils: Inserted SMS message into telephony (type = " + type + ")" - + ", uri: " + response); - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e); - } - return response; - } - - // Update SMS message type in telephony; returns true if it succeeded. - public static boolean updateSmsMessageSendingStatus(final Context context, final Uri uri, - final int type, final long date) { - try { - final ContentResolver resolver = context.getContentResolver(); - final ContentValues values = new ContentValues(2); - - values.put(Telephony.Sms.TYPE, type); - values.put(Telephony.Sms.DATE, date); - final int cnt = resolver.update(uri, values, null, null); - if (cnt == 1) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Mmsutils: Updated sending SMS " + uri + "; type = " + type - + ", date = " + date + " (millis since epoch)"); - } - return true; - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e); - } - return false; - } - - // Persist a sent MMS message in telephony - private static Uri insertSendReq(final Context context, final GenericPdu pdu, final int subId, - final String subPhoneNumber) { - final PduPersister persister = PduPersister.getPduPersister(context); - Uri uri = null; - try { - // Persist the PDU - uri = persister.persist( - pdu, - Mms.Sent.CONTENT_URI, - subId, - subPhoneNumber, - null/*preOpenedFiles*/); - // Update mms table to reflect sent messages are always seen and read - final ContentValues values = new ContentValues(1); - values.put(Mms.READ, 1); - values.put(Mms.SEEN, 1); - SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null); - } catch (final MmsException e) { - LogUtil.e(TAG, "MmsUtils: persist mms sent message failure " + e, e); - } - return uri; - } - - // Persist a received MMS message in telephony - public static Uri insertReceivedMmsMessage(final Context context, - final RetrieveConf retrieveConf, final int subId, final String subPhoneNumber, - final long receivedTimestampInSeconds, final String contentLocation) { - final PduPersister persister = PduPersister.getPduPersister(context); - Uri uri = null; - try { - uri = persister.persist( - retrieveConf, - Mms.Inbox.CONTENT_URI, - subId, - subPhoneNumber, - null/*preOpenedFiles*/); - - final ContentValues values = new ContentValues(2); - // Update mms table with local time instead of PDU time - values.put(Mms.DATE, receivedTimestampInSeconds); - // Also update the content location field from NotificationInd so that - // wap push dedup would work even after the wap push is deleted - values.put(Mms.CONTENT_LOCATION, contentLocation); - SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "MmsUtils: Inserted MMS message into telephony, uri: " + uri); - } - } catch (final MmsException e) { - LogUtil.e(TAG, "MmsUtils: persist mms received message failure " + e, e); - // Just returns empty uri to RetrieveMmsRequest, which triggers a permanent failure - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsUtils: update mms received message failure " + e, e); - // Time update failure is ignored. - } - return uri; - } - - // Update MMS message type in telephony; returns true if it succeeded. - public static boolean updateMmsMessageSendingStatus(final Context context, final Uri uri, - final int box, final long timestampInMillis) { - try { - final ContentResolver resolver = context.getContentResolver(); - final ContentValues values = new ContentValues(); - - final long timestampInSeconds = timestampInMillis / 1000L; - values.put(Telephony.Mms.MESSAGE_BOX, box); - values.put(Telephony.Mms.DATE, timestampInSeconds); - final int cnt = resolver.update(uri, values, null, null); - if (cnt == 1) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Mmsutils: Updated sending MMS " + uri + "; box = " + box - + ", date = " + timestampInSeconds + " (secs since epoch)"); - } - return true; - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e); - } - return false; - } - - /** - * Parse values from a received sms message - * - * @param context - * @param msgs The received sms message content - * @param error The received sms error - * @return Parsed values from the message - */ - public static ContentValues parseReceivedSmsMessage( - final Context context, final SmsMessage[] msgs, final int error) { - final SmsMessage sms = msgs[0]; - final ContentValues values = new ContentValues(); - - values.put(Sms.ADDRESS, sms.getDisplayOriginatingAddress()); - values.put(Sms.BODY, buildMessageBodyFromPdus(msgs)); - if (MmsUtils.hasSmsDateSentColumn()) { - // TODO:: The boxing here seems unnecessary. - values.put(Sms.DATE_SENT, Long.valueOf(sms.getTimestampMillis())); - } - values.put(Sms.PROTOCOL, sms.getProtocolIdentifier()); - if (sms.getPseudoSubject().length() > 0) { - values.put(Sms.SUBJECT, sms.getPseudoSubject()); - } - values.put(Sms.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0); - values.put(Sms.SERVICE_CENTER, sms.getServiceCenterAddress()); - // Error code - values.put(Sms.ERROR_CODE, error); - - return values; - } - - // Some providers send formfeeds in their messages. Convert those formfeeds to newlines. - private static String replaceFormFeeds(final String s) { - return s == null ? "" : s.replace('\f', '\n'); - } - - // Parse the message body from message PDUs - private static String buildMessageBodyFromPdus(final SmsMessage[] msgs) { - if (msgs.length == 1) { - // There is only one part, so grab the body directly. - return replaceFormFeeds(msgs[0].getDisplayMessageBody()); - } else { - // Build up the body from the parts. - final StringBuilder body = new StringBuilder(); - for (final SmsMessage msg : msgs) { - try { - // getDisplayMessageBody() can NPE if mWrappedMessage inside is null. - body.append(msg.getDisplayMessageBody()); - } catch (final NullPointerException e) { - // Nothing to do - } - } - return replaceFormFeeds(body.toString()); - } - } - - // Parse the message date - public static Long getMessageDate(final SmsMessage sms, long now) { - // Use now for the timestamp to avoid confusion with clock - // drift between the handset and the SMSC. - // Check to make sure the system is giving us a non-bogus time. - final Calendar buildDate = new GregorianCalendar(2011, 8, 18); // 18 Sep 2011 - final Calendar nowDate = new GregorianCalendar(); - nowDate.setTimeInMillis(now); - if (nowDate.before(buildDate)) { - // It looks like our system clock isn't set yet because the current time right now - // is before an arbitrary time we made this build. Instead of inserting a bogus - // receive time in this case, use the timestamp of when the message was sent. - now = sms.getTimestampMillis(); - } - return now; - } - - /** - * cleanseMmsSubject will take a subject that's says, "", and return - * a null string. Otherwise it will return the original subject string. - * @param resources So the function can grab string resources - * @param subject the raw subject - * @return - */ - public static String cleanseMmsSubject(final Resources resources, final String subject) { - if (TextUtils.isEmpty(subject)) { - return null; - } - if (sNoSubjectStrings == null) { - sNoSubjectStrings = - resources.getStringArray(R.array.empty_subject_strings); - } - for (final String noSubjectString : sNoSubjectStrings) { - if (subject.equalsIgnoreCase(noSubjectString)) { - return null; - } - } - return subject; - } - - // return a semicolon separated list of phone numbers from a smsto: uri. - public static String getSmsRecipients(final Uri uri) { - String recipients = uri.getSchemeSpecificPart(); - final int pos = recipients.indexOf('?'); - if (pos != -1) { - recipients = recipients.substring(0, pos); - } - recipients = replaceUnicodeDigits(recipients).replace(',', ';'); - return recipients; - } - - // This function was lifted from Telephony.PhoneNumberUtils because it was @hide - /** - * Replace arabic/unicode digits with decimal digits. - * @param number - * the number to be normalized. - * @return the replaced number. - */ - private static String replaceUnicodeDigits(final String number) { - final StringBuilder normalizedDigits = new StringBuilder(number.length()); - for (final char c : number.toCharArray()) { - final int digit = Character.digit(c, 10); - if (digit != -1) { - normalizedDigits.append(digit); - } else { - normalizedDigits.append(c); - } - } - return normalizedDigits.toString(); - } - - /** - * @return Whether the data roaming is enabled - */ - private static boolean isDataRoamingEnabled() { - boolean dataRoamingEnabled = false; - final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver(); - if (OsUtil.isAtLeastJB_MR1()) { - dataRoamingEnabled = (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0); - } else { - dataRoamingEnabled = (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0); - } - return dataRoamingEnabled; - } - - /** - * @return Whether to auto retrieve MMS - */ - public static boolean allowMmsAutoRetrieve(final int subId) { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId); - final boolean autoRetrieve = prefs.getBoolean( - resources.getString(R.string.auto_retrieve_mms_pref_key), - resources.getBoolean(R.bool.auto_retrieve_mms_pref_default)); - if (autoRetrieve) { - final boolean autoRetrieveInRoaming = prefs.getBoolean( - resources.getString(R.string.auto_retrieve_mms_when_roaming_pref_key), - resources.getBoolean(R.bool.auto_retrieve_mms_when_roaming_pref_default)); - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - if ((autoRetrieveInRoaming && phoneUtils.isDataRoamingEnabled()) - || !phoneUtils.isRoaming()) { - return true; - } - } - return false; - } - - /** - * Parse the message row id from a message Uri. - * - * @param messageUri The input Uri - * @return The message row id if valid, otherwise -1 - */ - public static long parseRowIdFromMessageUri(final Uri messageUri) { - try { - if (messageUri != null) { - return ContentUris.parseId(messageUri); - } - } catch (final UnsupportedOperationException e) { - // Nothing to do - } catch (final NumberFormatException e) { - // Nothing to do - } - return -1; - } - - public static SmsMessage getSmsMessageFromDeliveryReport(final Intent intent) { - final byte[] pdu = intent.getByteArrayExtra("pdu"); - return SmsMessage.createFromPdu(pdu); - } - - /** - * Update the status and date_sent column of sms message in telephony provider - * - * @param smsMessageUri - * @param status - * @param timeSentInMillis - */ - public static void updateSmsStatusAndDateSent(final Uri smsMessageUri, final int status, - final long timeSentInMillis) { - if (smsMessageUri == null) { - return; - } - final ContentValues values = new ContentValues(); - values.put(Sms.STATUS, status); - if (MmsUtils.hasSmsDateSentColumn()) { - values.put(Sms.DATE_SENT, timeSentInMillis); - } - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - resolver.update(smsMessageUri, values, null/*where*/, null/*selectionArgs*/); - } - - /** - * Get the SQL selection statement for matching messages with media. - * - * Example for MMS part table: - * "((ct LIKE 'image/%') - * OR (ct LIKE 'video/%') - * OR (ct LIKE 'audio/%') - * OR (ct='application/ogg')) - * - * @param contentTypeColumn The content-type column name - * @return The SQL selection statement for matching media types: image, video, audio - */ - public static String getMediaTypeSelectionSql(final String contentTypeColumn) { - return String.format( - Locale.US, - "((%s LIKE '%s') OR (%s LIKE '%s') OR (%s LIKE '%s') OR (%s='%s'))", - contentTypeColumn, - "image/%", - contentTypeColumn, - "video/%", - contentTypeColumn, - "audio/%", - contentTypeColumn, - ContentType.AUDIO_OGG); - } - - // Max number of operands per SQL query for deleting SMS messages - public static final int MAX_IDS_PER_QUERY = 128; - - /** - * Delete MMS messages with media parts. - * - * Because the telephony provider constraints, we can't use JOIN and delete messages in one - * shot. We have to do a query first and then batch delete the messages based on IDs. - * - * @return The count of messages deleted. - */ - public static int deleteMediaMessages() { - // Do a query first - // - // The WHERE clause has two parts: - // The first part is to select the exact same types of MMS messages as when we import them - // (so that we don't delete messages that are not in local database) - // The second part is to select MMS with media parts, including image, video and audio - final String selection = String.format( - Locale.US, - "%s AND (%s IN (SELECT %s FROM part WHERE %s))", - getMmsTypeSelectionSql(), - Mms._ID, - Mms.Part.MSG_ID, - getMediaTypeSelectionSql(Mms.Part.CONTENT_TYPE)); - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - final Cursor cursor = resolver.query(Mms.CONTENT_URI, - new String[]{ Mms._ID }, - selection, - null/*selectionArgs*/, - null/*sortOrder*/); - int deleted = 0; - if (cursor != null) { - final long[] messageIds = new long[cursor.getCount()]; - try { - int i = 0; - while (cursor.moveToNext()) { - messageIds[i++] = cursor.getLong(0); - } - } finally { - cursor.close(); - } - final int totalIds = messageIds.length; - if (totalIds > 0) { - // Batch delete the messages using IDs - // We don't want to send all IDs at once since there is a limit on SQL statement - for (int start = 0; start < totalIds; start += MAX_IDS_PER_QUERY) { - final int end = Math.min(start + MAX_IDS_PER_QUERY, totalIds); // excluding - final int count = end - start; - final String batchSelection = String.format( - Locale.US, - "%s IN %s", - Mms._ID, - getSqlInOperand(count)); - final String[] batchSelectionArgs = - getSqlInOperandArgs(messageIds, start, count); - final int deletedForBatch = resolver.delete( - Mms.CONTENT_URI, - batchSelection, - batchSelectionArgs); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "deleteMediaMessages: deleting IDs = " - + Joiner.on(',').skipNulls().join(batchSelectionArgs) - + ", deleted = " + deletedForBatch); - } - deleted += deletedForBatch; - } - } - } - return deleted; - } - - /** - * Get the (?,?,...) thing for the SQL IN operator by a count - * - * @param count - * @return - */ - public static String getSqlInOperand(final int count) { - if (count <= 0) { - return null; - } - final StringBuilder sb = new StringBuilder(); - sb.append("(?"); - for (int i = 0; i < count - 1; i++) { - sb.append(",?"); - } - sb.append(")"); - return sb.toString(); - } - - /** - * Get the args for SQL IN operator from a long ID array - * - * @param ids The original long id array - * @param start Start of the ids to fill the args - * @param count Number of ids to pack - * @return The long array with the id args - */ - private static String[] getSqlInOperandArgs( - final long[] ids, final int start, final int count) { - if (count <= 0) { - return null; - } - final String[] args = new String[count]; - for (int i = 0; i < count; i++) { - args[i] = Long.toString(ids[start + i]); - } - return args; - } - - /** - * Delete SMS and MMS messages that are earlier than a specific timestamp - * - * @param cutOffTimestampInMillis The cut-off timestamp - * @return Total number of messages deleted. - */ - public static int deleteMessagesOlderThan(final long cutOffTimestampInMillis) { - int deleted = 0; - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - // Delete old SMS - final String smsSelection = String.format( - Locale.US, - "%s AND (%s<=%d)", - getSmsTypeSelectionSql(), - Sms.DATE, - cutOffTimestampInMillis); - deleted += resolver.delete(Sms.CONTENT_URI, smsSelection, null/*selectionArgs*/); - // Delete old MMS - final String mmsSelection = String.format( - Locale.US, - "%s AND (%s<=%d)", - getMmsTypeSelectionSql(), - Mms.DATE, - cutOffTimestampInMillis / 1000L); - deleted += resolver.delete(Mms.CONTENT_URI, mmsSelection, null/*selectionArgs*/); - return deleted; - } - - /** - * Update the read status of SMS/MMS messages by thread and timestamp - * - * @param threadId The thread of sms/mms to change - * @param timestampInMillis Change the status before this timestamp - */ - public static void updateSmsReadStatus(final long threadId, final long timestampInMillis) { - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - final ContentValues values = new ContentValues(); - values.put("read", 1); - values.put("seen", 1); /* If you read it you saw it */ - final String smsSelection = String.format( - Locale.US, - "%s=%d AND %s<=%d AND %s=0", - Sms.THREAD_ID, - threadId, - Sms.DATE, - timestampInMillis, - Sms.READ); - resolver.update( - Sms.CONTENT_URI, - values, - smsSelection, - null/*selectionArgs*/); - final String mmsSelection = String.format( - Locale.US, - "%s=%d AND %s<=%d AND %s=0", - Mms.THREAD_ID, - threadId, - Mms.DATE, - timestampInMillis / 1000L, - Mms.READ); - resolver.update( - Mms.CONTENT_URI, - values, - mmsSelection, - null/*selectionArgs*/); - } - - /** - * Update the read status of a single MMS message by its URI - * - * @param mmsUri - * @param read - */ - public static void updateReadStatusForMmsMessage(final Uri mmsUri, final boolean read) { - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - final ContentValues values = new ContentValues(); - values.put(Mms.READ, read ? 1 : 0); - resolver.update(mmsUri, values, null/*where*/, null/*selectionArgs*/); - } - - public static class AttachmentInfo { - public String mUrl; - public String mContentType; - public int mWidth; - public int mHeight; - } - - /** - * Convert byte array to Java String using a charset name - * - * @param bytes - * @param charsetName - * @return - */ - public static String bytesToString(final byte[] bytes, final String charsetName) { - if (bytes == null) { - return null; - } - try { - return new String(bytes, charsetName); - } catch (final UnsupportedEncodingException e) { - LogUtil.e(TAG, "MmsUtils.bytesToString: " + e, e); - return new String(bytes); - } - } - - /** - * Convert a Java String to byte array using a charset name - * - * @param string - * @param charsetName - * @return - */ - public static byte[] stringToBytes(final String string, final String charsetName) { - if (string == null) { - return null; - } - try { - return string.getBytes(charsetName); - } catch (final UnsupportedEncodingException e) { - LogUtil.e(TAG, "MmsUtils.stringToBytes: " + e, e); - return string.getBytes(); - } - } - - private static final String[] TEST_DATE_SENT_PROJECTION = new String[] { Sms.DATE_SENT }; - private static Boolean sHasSmsDateSentColumn = null; - /** - * Check if date_sent column exists on ICS and above devices. We need to do a test - * query to figure that out since on some ICS+ devices, somehow the date_sent column does - * not exist. http://b/17629135 tracks the associated compliance test. - * - * @return Whether "date_sent" column exists in sms table - */ - public static boolean hasSmsDateSentColumn() { - if (sHasSmsDateSentColumn == null) { - Cursor cursor = null; - try { - final Context context = Factory.get().getApplicationContext(); - final ContentResolver resolver = context.getContentResolver(); - cursor = SqliteWrapper.query( - context, - resolver, - Sms.CONTENT_URI, - TEST_DATE_SENT_PROJECTION, - null/*selection*/, - null/*selectionArgs*/, - Sms.DATE_SENT + " ASC LIMIT 1"); - sHasSmsDateSentColumn = true; - } catch (final SQLiteException e) { - LogUtil.w(TAG, "date_sent in sms table does not exist", e); - sHasSmsDateSentColumn = false; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - return sHasSmsDateSentColumn; - } - - private static final String[] TEST_CARRIERS_PROJECTION = - new String[] { Telephony.Carriers.MMSC }; - private static Boolean sUseSystemApn = null; - /** - * Check if we can access the APN data in the Telephony provider. Access was restricted in - * JB MR1 (and some JB MR2) devices. If we can't access the APN, we have to fall back and use - * a private table in our own app. - * - * @return Whether we can access the system APN table - */ - public static boolean useSystemApnTable() { - if (sUseSystemApn == null) { - Cursor cursor = null; - try { - final Context context = Factory.get().getApplicationContext(); - final ContentResolver resolver = context.getContentResolver(); - cursor = SqliteWrapper.query( - context, - resolver, - Telephony.Carriers.CONTENT_URI, - TEST_CARRIERS_PROJECTION, - null/*selection*/, - null/*selectionArgs*/, - null); - sUseSystemApn = true; - } catch (final SecurityException e) { - LogUtil.w(TAG, "Can't access system APN, using internal table", e); - sUseSystemApn = false; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - return sUseSystemApn; - } - - // For the internal debugger only - public static void setUseSystemApnTable(final boolean turnOn) { - if (!turnOn) { - // We're not turning on to the system table. Instead, we're using our internal table. - final int osVersion = OsUtil.getApiVersion(); - if (osVersion != android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - // We're turning on local APNs on a device where we wouldn't normally have the - // local APN table. Build it here. - - final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase(); - - // Do we already have the table? - Cursor cursor = null; - try { - cursor = database.query(ApnDatabase.APN_TABLE, - ApnDatabase.APN_PROJECTION, - null, null, null, null, null, null); - } catch (final Exception e) { - // Apparently there's no table, create it now. - ApnDatabase.forceBuildAndLoadApnTables(); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - } - sUseSystemApn = turnOn; - } - - /** - * Checks if we should dump sms, based on both the setting and the global debug - * flag - * - * @return if dump sms is enabled - */ - public static boolean isDumpSmsEnabled() { - if (!DebugUtils.isDebugEnabled()) { - return false; - } - return getDumpSmsOrMmsPref(R.string.dump_sms_pref_key, R.bool.dump_sms_pref_default); - } - - /** - * Checks if we should dump mms, based on both the setting and the global debug - * flag - * - * @return if dump mms is enabled - */ - public static boolean isDumpMmsEnabled() { - if (!DebugUtils.isDebugEnabled()) { - return false; - } - return getDumpSmsOrMmsPref(R.string.dump_mms_pref_key, R.bool.dump_mms_pref_default); - } - - /** - * Load the value of dump sms or mms setting preference - */ - private static boolean getDumpSmsOrMmsPref(final int prefKeyRes, final int defaultKeyRes) { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - final String key = resources.getString(prefKeyRes); - final boolean defaultValue = resources.getBoolean(defaultKeyRes); - return prefs.getBoolean(key, defaultValue); - } - - public static final Uri MMS_PART_CONTENT_URI = Uri.parse("content://mms/part"); - - /** - * Load MMS from telephony - * - * @param mmsUri The MMS pdu Uri - * @return A memory copy of the MMS pdu including parts (but not addresses) - */ - public static DatabaseMessages.MmsMessage loadMms(final Uri mmsUri) { - final Context context = Factory.get().getApplicationContext(); - final ContentResolver resolver = context.getContentResolver(); - DatabaseMessages.MmsMessage mms = null; - Cursor cursor = null; - // Load pdu first - try { - cursor = SqliteWrapper.query(context, resolver, - mmsUri, - DatabaseMessages.MmsMessage.getProjection(), - null/*selection*/, null/*selectionArgs*/, null/*sortOrder*/); - if (cursor != null && cursor.moveToFirst()) { - mms = DatabaseMessages.MmsMessage.get(cursor); - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsLoader: query pdu failure: " + e, e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - if (mms == null) { - return null; - } - // Load parts except SMIL - // TODO: we may need to load SMIL part in the future. - final long rowId = MmsUtils.parseRowIdFromMessageUri(mmsUri); - final String selection = String.format( - Locale.US, - "%s != '%s' AND %s = ?", - Mms.Part.CONTENT_TYPE, - ContentType.APP_SMIL, - Mms.Part.MSG_ID); - cursor = null; - try { - cursor = SqliteWrapper.query(context, resolver, - MMS_PART_CONTENT_URI, - DatabaseMessages.MmsPart.PROJECTION, - selection, - new String[] { Long.toString(rowId) }, - null/*sortOrder*/); - if (cursor != null) { - while (cursor.moveToNext()) { - mms.addPart(DatabaseMessages.MmsPart.get(cursor, true/*loadMedia*/)); - } - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "MmsLoader: query parts failure: " + e, e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return mms; - } - - /** - * Get the sender of an MMS message - * - * @param recipients The recipient list of the message - * @param mmsUri The pdu uri of the MMS - * @return The sender phone number of the MMS - */ - public static String getMmsSender(final List recipients, final String mmsUri) { - final Context context = Factory.get().getApplicationContext(); - // We try to avoid the database query. - // If this is a 1v1 conv., then the other party is the sender - if (recipients != null && recipients.size() == 1) { - return recipients.get(0); - } - // Otherwise, we have to query the MMS addr table for sender address - // This should only be done for a received group mms message - final Cursor cursor = SqliteWrapper.query( - context, - context.getContentResolver(), - Uri.withAppendedPath(Uri.parse(mmsUri), "addr"), - new String[] { Mms.Addr.ADDRESS, Mms.Addr.CHARSET }, - Mms.Addr.TYPE + "=" + PduHeaders.FROM, - null/*selectionArgs*/, - null/*sortOrder*/); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - return DatabaseMessages.MmsAddr.get(cursor); - } - } finally { - cursor.close(); - } - } - return null; - } - - public static int bugleStatusForMms(final boolean isOutgoing, final boolean isNotification, - final int messageBox) { - int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN; - // For a message we sync either - if (isOutgoing) { - if (messageBox == Mms.MESSAGE_BOX_OUTBOX || messageBox == Mms.MESSAGE_BOX_FAILED) { - // Not sent counts as failed and available for manual resend - bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED; - } else { - // Otherwise outgoing message is complete - bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE; - } - } else if (isNotification) { - // Incoming MMS notifications we sync count as failed and available for manual download - bugleStatus = MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD; - } else { - // Other incoming MMS messages are complete - bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE; - } - return bugleStatus; - } - - public static MessageData createMmsMessage(final DatabaseMessages.MmsMessage mms, - final String conversationId, final String participantId, final String selfId, - final int bugleStatus) { - Assert.notNull(mms); - final boolean isNotification = (mms.mMmsMessageType == - PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); - final int rawMmsStatus = (bugleStatus < MessageData.BUGLE_STATUS_FIRST_INCOMING - ? mms.mRetrieveStatus : mms.mResponseStatus); - - final MessageData message = MessageData.createMmsMessage(mms.getUri(), - participantId, selfId, conversationId, isNotification, bugleStatus, - mms.mContentLocation, mms.mTransactionId, mms.mPriority, mms.mSubject, - mms.mSeen, mms.mRead, mms.getSize(), rawMmsStatus, - mms.mExpiryInMillis, mms.mSentTimestampInMillis, mms.mTimestampInMillis); - - for (final DatabaseMessages.MmsPart part : mms.mParts) { - final MessagePartData messagePart = MmsUtils.createMmsMessagePart(part); - // Import media and text parts (skip SMIL and others) - if (messagePart != null) { - message.addPart(messagePart); - } - } - - if (!message.getParts().iterator().hasNext()) { - message.addPart(MessagePartData.createEmptyMessagePart()); - } - - return message; - } - - public static MessagePartData createMmsMessagePart(final DatabaseMessages.MmsPart part) { - MessagePartData messagePart = null; - if (part.isText()) { - final int mmsTextLengthLimit = - BugleGservices.get().getInt(BugleGservicesKeys.MMS_TEXT_LIMIT, - BugleGservicesKeys.MMS_TEXT_LIMIT_DEFAULT); - String text = part.mText; - if (text != null && text.length() > mmsTextLengthLimit) { - // Limit the text to a reasonable value. We ran into a situation where a vcard - // with a photo was sent as plain text. The massive amount of text caused the - // app to hang, ANR, and eventually crash in native text code. - text = text.substring(0, mmsTextLengthLimit); - } - messagePart = MessagePartData.createTextMessagePart(text); - } else if (part.isMedia()) { - messagePart = MessagePartData.createMediaMessagePart(part.mContentType, - part.getDataUri(), MessagePartData.UNSPECIFIED_SIZE, - MessagePartData.UNSPECIFIED_SIZE); - } - return messagePart; - } - - public static class StatusPlusUri { - // The request status to be as the result of the operation - // e.g. MMS_REQUEST_MANUAL_RETRY - public final int status; - // The raw telephony status - public final int rawStatus; - // The raw telephony URI - public final Uri uri; - // The operation result code from system api invocation (sent by system) - // or mapped from internal exception (sent by app) - public final int resultCode; - - public StatusPlusUri(final int status, final int rawStatus, final Uri uri) { - this.status = status; - this.rawStatus = rawStatus; - this.uri = uri; - resultCode = MessageData.UNKNOWN_RESULT_CODE; - } - - public StatusPlusUri(final int status, final int rawStatus, final Uri uri, - final int resultCode) { - this.status = status; - this.rawStatus = rawStatus; - this.uri = uri; - this.resultCode = resultCode; - } - } - - public static class SendReqResp { - public SendReq mSendReq; - public SendConf mSendConf; - - public SendReqResp(final SendReq sendReq, final SendConf sendConf) { - mSendReq = sendReq; - mSendConf = sendConf; - } - } - - /** - * Returned when sending/downloading MMS via platform APIs. In that case, we have to wait to - * receive the pending intent to determine status. - */ - public static final StatusPlusUri STATUS_PENDING = new StatusPlusUri(-1, -1, null); - - public static StatusPlusUri downloadMmsMessage(final Context context, final Uri notificationUri, - final int subId, final String subPhoneNumber, final String transactionId, - final String contentLocation, final boolean autoDownload, - final long receivedTimestampInSeconds, Bundle extras) { - if (TextUtils.isEmpty(contentLocation)) { - LogUtil.e(TAG, "MmsUtils: Download from empty content location URL"); - return new StatusPlusUri( - MMS_REQUEST_NO_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, null); - } - if (!isMmsDataAvailable(subId)) { - LogUtil.e(TAG, - "MmsUtils: failed to download message, no data available"); - return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY, - MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, - null, - SmsManager.MMS_ERROR_NO_DATA_NETWORK); - } - int status = MMS_REQUEST_MANUAL_RETRY; - try { - RetrieveConf retrieveConf = null; - if (DebugUtils.isDebugEnabled() && - MediaScratchFileProvider - .isMediaScratchSpaceUri(Uri.parse(contentLocation))) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "MmsUtils: Reading MMS from dump file: " + contentLocation); - } - final String fileName = Uri.parse(contentLocation).getPathSegments().get(1); - final byte[] data = DebugUtils.receiveFromDumpFile(fileName); - retrieveConf = receiveFromDumpFile(data); - } else { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "MmsUtils: Downloading MMS via MMS lib API; notification " - + "message: " + notificationUri); - } - if (OsUtil.isAtLeastL_MR1()) { - if (subId < 0) { - LogUtil.e(TAG, "MmsUtils: Incoming MMS came from unknown SIM"); - throw new MmsFailureException(MMS_REQUEST_NO_RETRY, - "Message from unknown SIM"); - } - } else { - Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID); - } - if (extras == null) { - extras = new Bundle(); - } - extras.putParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI, notificationUri); - extras.putInt(DownloadMmsAction.EXTRA_SUB_ID, subId); - extras.putString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER, subPhoneNumber); - extras.putString(DownloadMmsAction.EXTRA_TRANSACTION_ID, transactionId); - extras.putString(DownloadMmsAction.EXTRA_CONTENT_LOCATION, contentLocation); - extras.putBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD, autoDownload); - extras.putLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP, - receivedTimestampInSeconds); - - MmsSender.downloadMms(context, subId, contentLocation, extras); - return STATUS_PENDING; // Download happens asynchronously; no status to return - } - return insertDownloadedMessageAndSendResponse(context, notificationUri, subId, - subPhoneNumber, transactionId, contentLocation, autoDownload, - receivedTimestampInSeconds, retrieveConf); - - } catch (final MmsFailureException e) { - LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e); - status = e.retryHint; - } catch (final InvalidHeaderValueException e) { - LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e); - } - return new StatusPlusUri(status, PDU_HEADER_VALUE_UNDEFINED, null); - } - - public static StatusPlusUri insertDownloadedMessageAndSendResponse(final Context context, - final Uri notificationUri, final int subId, final String subPhoneNumber, - final String transactionId, final String contentLocation, - final boolean autoDownload, final long receivedTimestampInSeconds, - final RetrieveConf retrieveConf) { - final byte[] transactionIdBytes = stringToBytes(transactionId, "UTF-8"); - Uri messageUri = null; - int status = MMS_REQUEST_MANUAL_RETRY; - int retrieveStatus = PDU_HEADER_VALUE_UNDEFINED; - - retrieveStatus = retrieveConf.getRetrieveStatus(); - if (retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK) { - status = MMS_REQUEST_SUCCEEDED; - } else if (retrieveStatus >= PduHeaders.RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE && - retrieveStatus < PduHeaders.RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE) { - status = MMS_REQUEST_AUTO_RETRY; - } else { - // else not meant to retry download - status = MMS_REQUEST_NO_RETRY; - LogUtil.e(TAG, "MmsUtils: failed to retrieve message; retrieveStatus: " - + retrieveStatus); - } - final ContentValues values = new ContentValues(1); - values.put(Mms.RETRIEVE_STATUS, retrieveConf.getRetrieveStatus()); - SqliteWrapper.update(context, context.getContentResolver(), - notificationUri, values, null, null); - - if (status == MMS_REQUEST_SUCCEEDED) { - // Send response of the notification - if (autoDownload) { - sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes, - contentLocation, PduHeaders.STATUS_RETRIEVED); - } else { - sendAcknowledgeForMmsDownload(context, subId, transactionIdBytes, contentLocation); - } - - // Insert downloaded message into telephony - final Uri inboxUri = MmsUtils.insertReceivedMmsMessage(context, retrieveConf, subId, - subPhoneNumber, receivedTimestampInSeconds, contentLocation); - messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, ContentUris.parseId(inboxUri)); - } else if (status == MMS_REQUEST_AUTO_RETRY) { - // For a retry do nothing - } else if (status == MMS_REQUEST_MANUAL_RETRY && autoDownload) { - // Failure from autodownload - just treat like manual download - sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes, - contentLocation, PduHeaders.STATUS_DEFERRED); - } - return new StatusPlusUri(status, retrieveStatus, messageUri); - } - - /** - * Send response for MMS download - catches and ignores errors - */ - public static void sendNotifyResponseForMmsDownload(final Context context, final int subId, - final byte[] transactionId, final String contentLocation, final int status) { - try { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "MmsUtils: Sending M-NotifyResp.ind for received MMS, status: " - + String.format("0x%X", status)); - } - if (contentLocation == null) { - LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; contentLocation is null"); - return; - } - if (transactionId == null) { - LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; transaction id is null"); - return; - } - if (!isMmsDataAvailable(subId)) { - LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; no data available"); - return; - } - MmsSender.sendNotifyResponseForMmsDownload( - context, subId, transactionId, contentLocation, status); - } catch (final MmsFailureException e) { - LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e); - } catch (final InvalidHeaderValueException e) { - LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e); - } - } - - /** - * Send acknowledge for mms download - catched and ignores errors - */ - public static void sendAcknowledgeForMmsDownload(final Context context, final int subId, - final byte[] transactionId, final String contentLocation) { - try { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "MmsUtils: Sending M-Acknowledge.ind for received MMS"); - } - if (contentLocation == null) { - LogUtil.w(TAG, "MmsUtils: Can't send AckInd; contentLocation is null"); - return; - } - if (transactionId == null) { - LogUtil.w(TAG, "MmsUtils: Can't send AckInd; transaction id is null"); - return; - } - if (!isMmsDataAvailable(subId)) { - LogUtil.w(TAG, "MmsUtils: Can't send AckInd; no data available"); - return; - } - MmsSender.sendAcknowledgeForMmsDownload(context, subId, transactionId, contentLocation); - } catch (final MmsFailureException e) { - LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e); - } catch (final InvalidHeaderValueException e) { - LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e); - } - } - - /** - * Try parsing a PDU without knowing the carrier. This is useful for importing - * MMS or storing draft when carrier info is not available - * - * @param data The PDU data - * @return Parsed PDU, null if failed to parse - */ - private static GenericPdu parsePduForAnyCarrier(final byte[] data) { - GenericPdu pdu = null; - try { - pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse(); - } catch (final RuntimeException e) { - LogUtil.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU with content disposition", - e); - } - if (pdu == null) { - try { - pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse(); - } catch (final RuntimeException e) { - LogUtil.d(TAG, - "parsePduForAnyCarrier: Failed to parse PDU without content disposition", - e); - } - } - return pdu; - } - - private static RetrieveConf receiveFromDumpFile(final byte[] data) throws MmsFailureException { - final GenericPdu pdu = parsePduForAnyCarrier(data); - if (pdu == null || !(pdu instanceof RetrieveConf)) { - LogUtil.e(TAG, "receiveFromDumpFile: Parsing retrieved PDU failure"); - throw new MmsFailureException(MMS_REQUEST_MANUAL_RETRY, "Failed reading dump file"); - } - return (RetrieveConf) pdu; - } - - private static boolean isMmsDataAvailable(final int subId) { - if (OsUtil.isAtLeastL_MR1()) { - // L_MR1 above may support sending mms via wifi - return true; - } - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - return !phoneUtils.isAirplaneModeOn() && phoneUtils.isMobileDataEnabled(); - } - - private static boolean isSmsDataAvailable(final int subId) { - if (OsUtil.isAtLeastL_MR1()) { - // L_MR1 above may support sending sms via wifi - return true; - } - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - return !phoneUtils.isAirplaneModeOn(); - } - - public static boolean isMobileDataEnabled(final int subId) { - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - return phoneUtils.isMobileDataEnabled(); - } - - public static boolean isAirplaneModeOn(final int subId) { - final PhoneUtils phoneUtils = PhoneUtils.get(subId); - return phoneUtils.isAirplaneModeOn(); - } - - public static StatusPlusUri sendMmsMessage(final Context context, final int subId, - final Uri messageUri, final Bundle extras) { - int status = MMS_REQUEST_MANUAL_RETRY; - int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED; - if (!isMmsDataAvailable(subId)) { - LogUtil.w(TAG, "MmsUtils: failed to send message, no data available"); - return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY, - MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, - messageUri, - SmsManager.MMS_ERROR_NO_DATA_NETWORK); - } - final PduPersister persister = PduPersister.getPduPersister(context); - try { - final SendReq sendReq = (SendReq) persister.load(messageUri); - if (sendReq == null) { - LogUtil.w(TAG, "MmsUtils: Sending MMS was deleted; uri = " + messageUri); - return new StatusPlusUri(MMS_REQUEST_NO_RETRY, - MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, messageUri); - } - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, String.format("MmsUtils: Sending MMS, message uri: %s", messageUri)); - } - extras.putInt(SendMessageAction.KEY_SUB_ID, subId); - MmsSender.sendMms(context, subId, messageUri, sendReq, extras); - return STATUS_PENDING; - } catch (final MmsFailureException e) { - status = e.retryHint; - rawStatus = e.rawStatus; - LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e); - } catch (final InvalidHeaderValueException e) { - LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e); - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MmsUtils: invalid message to send " + e, e); - } catch (final MmsException e) { - LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e); - } - // If we get here, some exception occurred - return new StatusPlusUri(status, rawStatus, messageUri); - } - - public static StatusPlusUri updateSentMmsMessageStatus(final Context context, - final Uri messageUri, final SendConf sendConf) { - int status = MMS_REQUEST_MANUAL_RETRY; - final int respStatus = sendConf.getResponseStatus(); - - final ContentValues values = new ContentValues(2); - values.put(Mms.RESPONSE_STATUS, respStatus); - final byte[] messageId = sendConf.getMessageId(); - if (messageId != null && messageId.length > 0) { - values.put(Mms.MESSAGE_ID, PduPersister.toIsoString(messageId)); - } - SqliteWrapper.update(context, context.getContentResolver(), - messageUri, values, null, null); - if (respStatus == PduHeaders.RESPONSE_STATUS_OK) { - status = MMS_REQUEST_SUCCEEDED; - } else if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE || - respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM || - respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) { - status = MMS_REQUEST_AUTO_RETRY; - } else { - // else permanent failure - LogUtil.e(TAG, "MmsUtils: failed to send message; respStatus = " - + String.format("0x%X", respStatus)); - } - return new StatusPlusUri(status, respStatus, messageUri); - } - - public static void clearMmsStatus(final Context context, final Uri uri) { - // Messaging application can leave invalid values in STATUS field of M-Notification.ind - // messages. Take this opportunity to clear it. - // Downloading status just kept in local db and not reflected into telephony. - final ContentValues values = new ContentValues(1); - values.putNull(Mms.STATUS); - SqliteWrapper.update(context, context.getContentResolver(), - uri, values, null, null); - } - - // Selection for new dedup algorithm: - // ((m_type<>130) OR (exp>NOW)) AND (date>NOW-7d) AND (date?) OR (" + Mms.EXPIRY + ">?)) AND (" - + Mms.DATE + ">?) AND (" + Mms.DATE + " 0) { - // New dedup algorithm - selection = DUP_NOTIFICATION_QUERY_SELECTION; - final long nowSecs = System.currentTimeMillis() / 1000; - final long timeLowerBoundSecs = nowSecs - timeLimit; - // Need upper bound to protect against clock change so that a message has a time - // stamp in the future - final long timeUpperBoundSecs = nowSecs + timeLimit; - selectionArgs = new String[] { - Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), - Long.toString(nowSecs), - Long.toString(timeLowerBoundSecs), - Long.toString(timeUpperBoundSecs), - location - }; - } else { - // If time limit is 0, we revert back to old behavior in case the new - // dedup algorithm behaves badly - selection = DUP_NOTIFICATION_QUERY_SELECTION_OLD; - selectionArgs = new String[] { - Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), - location - }; - } - Cursor cursor = null; - try { - cursor = SqliteWrapper.query( - context, context.getContentResolver(), - Mms.CONTENT_URI, new String[] { Mms._ID }, - selection, selectionArgs, null); - final int dupCount = cursor.getCount(); - if (dupCount > 0) { - // We already received the same notification before. - // Don't want to return too many dups. It is only for debugging. - final int returnCount = dupCount < MAX_RETURN ? dupCount : MAX_RETURN; - final String[] dups = new String[returnCount]; - for (int i = 0; cursor.moveToNext() && i < returnCount; i++) { - dups[i] = cursor.getString(0); - } - return dups; - } - } catch (final SQLiteException e) { - LogUtil.e(TAG, "query failure: " + e, e); - } finally { - cursor.close(); - } - } - return null; - } - - /** - * Try parse the address using RFC822 format. If it fails to parse, then return the - * original address - * - * @param address The MMS ind sender address to parse - * @return The real address. If in RFC822 format, returns the correct email. - */ - private static String parsePotentialRfc822EmailAddress(final String address) { - if (address == null || !address.contains("@") || !address.contains("<")) { - return address; - } - final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address); - if (tokens != null && tokens.length > 0) { - for (final Rfc822Token token : tokens) { - if (token != null && !TextUtils.isEmpty(token.getAddress())) { - return token.getAddress(); - } - } - } - return address; - } - - public static DatabaseMessages.MmsMessage processReceivedPdu(final Context context, - final byte[] pushData, final int subId, final String subPhoneNumber) { - // Parse data - - // Insert placeholder row to telephony and local db - // Get raw PDU push-data from the message and parse it - final PduParser parser = new PduParser(pushData, - MmsConfig.get(subId).getSupportMmsContentDisposition()); - final GenericPdu pdu = parser.parse(); - - if (null == pdu) { - LogUtil.e(TAG, "Invalid PUSH data"); - return null; - } - - final PduPersister p = PduPersister.getPduPersister(context); - final int type = pdu.getMessageType(); - - Uri messageUri = null; - switch (type) { - case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: - case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: { - // TODO: Should this be commented out? -// threadId = findThreadId(context, pdu, type); -// if (threadId == -1) { -// // The associated SendReq isn't found, therefore skip -// // processing this PDU. -// break; -// } - -// Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, -// MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null); -// // Update thread ID for ReadOrigInd & DeliveryInd. -// ContentValues values = new ContentValues(1); -// values.put(Mms.THREAD_ID, threadId); -// SqliteWrapper.update(mContext, cr, uri, values, null, null); - LogUtil.w(TAG, "Received unsupported WAP Push, type=" + type); - break; - } - case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: { - final NotificationInd nInd = (NotificationInd) pdu; - - if (MmsConfig.get(subId).getTransIdEnabled()) { - final byte [] contentLocationTemp = nInd.getContentLocation(); - if ('=' == contentLocationTemp[contentLocationTemp.length - 1]) { - final byte [] transactionIdTemp = nInd.getTransactionId(); - final byte [] contentLocationWithId = - new byte [contentLocationTemp.length - + transactionIdTemp.length]; - System.arraycopy(contentLocationTemp, 0, contentLocationWithId, - 0, contentLocationTemp.length); - System.arraycopy(transactionIdTemp, 0, contentLocationWithId, - contentLocationTemp.length, transactionIdTemp.length); - nInd.setContentLocation(contentLocationWithId); - } - } - final String[] dups = getDupNotifications(context, nInd); - if (dups == null) { - // TODO: Do we handle Rfc822 Email Addresses? - //final String contentLocation = - // MmsUtils.bytesToString(nInd.getContentLocation(), "UTF-8"); - //final byte[] transactionId = nInd.getTransactionId(); - //final long messageSize = nInd.getMessageSize(); - //final long expiry = nInd.getExpiry(); - //final String transactionIdString = - // MmsUtils.bytesToString(transactionId, "UTF-8"); - - //final EncodedStringValue fromEncoded = nInd.getFrom(); - // An mms ind received from email address will have from address shown as - // "John Doe " but the actual received message will only - // have the email address. So let's try to parse the RFC822 format to get the - // real email. Otherwise we will create two conversations for the MMS - // notification and the actual MMS message if auto retrieve is disabled. - //final String from = parsePotentialRfc822EmailAddress( - // fromEncoded != null ? fromEncoded.getString() : null); - - Uri inboxUri = null; - try { - inboxUri = p.persist(pdu, Mms.Inbox.CONTENT_URI, subId, subPhoneNumber, - null); - messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, - ContentUris.parseId(inboxUri)); - } catch (final MmsException e) { - LogUtil.e(TAG, "Failed to save the data from PUSH: type=" + type, e); - } - } else { - LogUtil.w(TAG, "Received WAP Push is a dup: " + Joiner.on(',').join(dups)); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.w(TAG, "Dup WAP Push url=" + new String(nInd.getContentLocation())); - } - } - break; - } - default: - LogUtil.e(TAG, "Received unrecognized WAP Push, type=" + type); - } - - DatabaseMessages.MmsMessage mms = null; - if (messageUri != null) { - mms = MmsUtils.loadMms(messageUri); - } - return mms; - } - - public static Uri insertSendingMmsMessage(final Context context, final List recipients, - final MessageData content, final int subId, final String subPhoneNumber, - final long timestamp) { - final SendReq sendReq = createMmsSendReq( - context, subId, recipients.toArray(new String[recipients.size()]), content, - DEFAULT_DELIVERY_REPORT_MODE, - DEFAULT_READ_REPORT_MODE, - DEFAULT_EXPIRY_TIME_IN_SECONDS, - DEFAULT_PRIORITY, - timestamp); - Uri messageUri = null; - if (sendReq != null) { - final Uri outboxUri = MmsUtils.insertSendReq(context, sendReq, subId, subPhoneNumber); - if (outboxUri != null) { - messageUri = ContentUris.withAppendedId(Telephony.Mms.CONTENT_URI, - ContentUris.parseId(outboxUri)); - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "Mmsutils: Inserted sending MMS message into telephony, uri: " - + outboxUri); - } - } else { - LogUtil.e(TAG, "insertSendingMmsMessage: failed to persist message into telephony"); - } - } - return messageUri; - } - - public static MessageData readSendingMmsMessage(final Uri messageUri, - final String conversationId, final String participantId, final String selfId) { - MessageData message = null; - if (messageUri != null) { - final DatabaseMessages.MmsMessage mms = MmsUtils.loadMms(messageUri); - - // Make sure that the message has not been deleted from the Telephony DB - if (mms != null) { - // Transform the message - message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId, - MessageData.BUGLE_STATUS_OUTGOING_RESENDING); - } - } - return message; - } - - /** - * Create an MMS message with subject, text and image - * - * @return Both the M-Send.req and the M-Send.conf for processing in the caller - * @throws MmsException - */ - private static SendReq createMmsSendReq(final Context context, final int subId, - final String[] recipients, final MessageData message, - final boolean requireDeliveryReport, final boolean requireReadReport, - final long expiryTime, final int priority, final long timestampMillis) { - Assert.notNull(context); - if (recipients == null || recipients.length < 1) { - throw new IllegalArgumentException("MMS sendReq no recipient"); - } - - // Make a copy so we don't propagate changes to recipients to outside of this method - final String[] recipientsCopy = new String[recipients.length]; - // Don't send phone number as is since some received phone number is malformed - // for sending. We need to strip the separators. - for (int i = 0; i < recipients.length; i++) { - final String recipient = recipients[i]; - if (EmailAddress.isValidEmail(recipients[i])) { - // Don't do stripping for emails - recipientsCopy[i] = recipient; - } else { - recipientsCopy[i] = stripPhoneNumberSeparators(recipient); - } - } - - SendReq sendReq = null; - try { - sendReq = createSendReq(context, subId, recipientsCopy, - message, requireDeliveryReport, - requireReadReport, expiryTime, priority, timestampMillis); - } catch (final InvalidHeaderValueException e) { - LogUtil.e(TAG, "InvalidHeaderValue creating sendReq PDU"); - } catch (final OutOfMemoryError e) { - LogUtil.e(TAG, "Out of memory error creating sendReq PDU"); - } - return sendReq; - } - - /** - * Stripping out the invalid characters in a phone number before sending - * MMS. We only keep alphanumeric and '*', '#', '+'. - */ - private static String stripPhoneNumberSeparators(final String phoneNumber) { - if (phoneNumber == null) { - return null; - } - final int len = phoneNumber.length(); - final StringBuilder ret = new StringBuilder(len); - for (int i = 0; i < len; i++) { - final char c = phoneNumber.charAt(i); - if (Character.isLetterOrDigit(c) || c == '+' || c == '*' || c == '#') { - ret.append(c); - } - } - return ret.toString(); - } - - /** - * Create M-Send.req for the MMS message to be sent. - * - * @return the M-Send.req - * @throws InvalidHeaderValueException if there is any error in parsing the input - */ - static SendReq createSendReq(final Context context, final int subId, - final String[] recipients, final MessageData message, - final boolean requireDeliveryReport, - final boolean requireReadReport, final long expiryTime, final int priority, - final long timestampMillis) - throws InvalidHeaderValueException { - final SendReq req = new SendReq(); - // From, per spec - final String lineNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/); - if (!TextUtils.isEmpty(lineNumber)) { - req.setFrom(new EncodedStringValue(lineNumber)); - } - // To - final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients); - if (encodedNumbers != null) { - req.setTo(encodedNumbers); - } - // Subject - if (!TextUtils.isEmpty(message.getMmsSubject())) { - req.setSubject(new EncodedStringValue(message.getMmsSubject())); - } - // Date - req.setDate(timestampMillis / 1000L); - // Body - final MmsInfo bodyInfo = MmsUtils.makePduBody(context, message, subId); - req.setBody(bodyInfo.mPduBody); - // Message size - req.setMessageSize(bodyInfo.mMessageSize); - // Message class - req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes()); - // Expiry - req.setExpiry(expiryTime); - // Priority - req.setPriority(priority); - // Delivery report - req.setDeliveryReport(requireDeliveryReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); - // Read report - req.setReadReport(requireReadReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); - return req; - } - - public static boolean isDeliveryReportRequired(final int subId) { - if (!MmsConfig.get(subId).getSMSDeliveryReportsEnabled()) { - return false; - } - final Context context = Factory.get().getApplicationContext(); - final Resources res = context.getResources(); - final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId); - final String deliveryReportKey = res.getString(R.string.delivery_reports_pref_key); - final boolean defaultValue = res.getBoolean(R.bool.delivery_reports_pref_default); - return prefs.getBoolean(deliveryReportKey, defaultValue); - } - - public static int sendSmsMessage(final String recipient, final String messageText, - final Uri requestUri, final int subId, - final String smsServiceCenter, final boolean requireDeliveryReport) { - if (!isSmsDataAvailable(subId)) { - LogUtil.w(TAG, "MmsUtils: can't send SMS without radio"); - return MMS_REQUEST_MANUAL_RETRY; - } - final Context context = Factory.get().getApplicationContext(); - int status = MMS_REQUEST_MANUAL_RETRY; - try { - // Send a single message - final SendResult result = SmsSender.sendMessage( - context, - subId, - recipient, - messageText, - smsServiceCenter, - requireDeliveryReport, - requestUri); - if (!result.hasPending()) { - // not timed out, check failures - final int failureLevel = result.getHighestFailureLevel(); - switch (failureLevel) { - case SendResult.FAILURE_LEVEL_NONE: - status = MMS_REQUEST_SUCCEEDED; - break; - case SendResult.FAILURE_LEVEL_TEMPORARY: - status = MMS_REQUEST_AUTO_RETRY; - LogUtil.e(TAG, "MmsUtils: SMS temporary failure"); - break; - case SendResult.FAILURE_LEVEL_PERMANENT: - LogUtil.e(TAG, "MmsUtils: SMS permanent failure"); - break; - } - } else { - // Timed out - LogUtil.e(TAG, "MmsUtils: sending SMS timed out"); - } - } catch (final Exception e) { - LogUtil.e(TAG, "MmsUtils: failed to send SMS " + e, e); - } - return status; - } - - /** - * Delete SMS and MMS messages in a particular thread - * - * @return the number of messages deleted - */ - public static int deleteThread(final long threadId, final long cutOffTimestampInMillis) { - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - final Uri threadUri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId); - if (cutOffTimestampInMillis < Long.MAX_VALUE) { - return resolver.delete(threadUri, Sms.DATE + "<=?", - new String[] { Long.toString(cutOffTimestampInMillis) }); - } else { - return resolver.delete(threadUri, null /* smsSelection */, null /* selectionArgs */); - } - } - - /** - * Delete single SMS and MMS message - * - * @return number of rows deleted (should be 1 or 0) - */ - public static int deleteMessage(final Uri messageUri) { - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - return resolver.delete(messageUri, null /* selection */, null /* selectionArgs */); - } - - public static byte[] createDebugNotificationInd(final String fileName) { - byte[] pduData = null; - try { - final Context context = Factory.get().getApplicationContext(); - // Load the message file - final byte[] data = DebugUtils.receiveFromDumpFile(fileName); - final RetrieveConf retrieveConf = receiveFromDumpFile(data); - // Create the notification - final NotificationInd notification = new NotificationInd(); - final long expiry = System.currentTimeMillis() / 1000 + 600; - notification.setTransactionId(fileName.getBytes()); - notification.setMmsVersion(retrieveConf.getMmsVersion()); - notification.setFrom(retrieveConf.getFrom()); - notification.setSubject(retrieveConf.getSubject()); - notification.setExpiry(expiry); - notification.setMessageSize(data.length); - notification.setMessageClass(retrieveConf.getMessageClass()); - - final Uri.Builder builder = MediaScratchFileProvider.getUriBuilder(); - builder.appendPath(fileName); - final Uri contentLocation = builder.build(); - notification.setContentLocation(contentLocation.toString().getBytes()); - - // Serialize - pduData = new PduComposer(context, notification).make(); - if (pduData == null || pduData.length < 1) { - throw new IllegalArgumentException("Empty or zero length PDU data"); - } - } catch (final MmsFailureException e) { - // Nothing to do - } catch (final InvalidHeaderValueException e) { - // Nothing to do - } - return pduData; - } - - public static int mapRawStatusToErrorResourceId(final int bugleStatus, final int rawStatus) { - int stringResId = R.string.message_status_send_failed; - switch (rawStatus) { - case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED: - case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED: - //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID: - stringResId = R.string.mms_failure_outgoing_service; - break; - case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED: - case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED: - case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED: - stringResId = R.string.mms_failure_outgoing_address; - break; - case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT: - case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT: - stringResId = R.string.mms_failure_outgoing_corrupt; - break; - case PduHeaders.RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED: - case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED: - stringResId = R.string.mms_failure_outgoing_content; - break; - case PduHeaders.RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE: - //case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND: - //case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND: - stringResId = R.string.mms_failure_outgoing_unsupported; - break; - case MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG: - stringResId = R.string.mms_failure_outgoing_too_large; - break; - } - return stringResId; - } - - /** - * The absence of a connection type. - */ - public static final int TYPE_NONE = -1; - - public static int getConnectivityEventNetworkType(final Context context, final Intent intent) { - final ConnectivityManager connMgr = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (OsUtil.isAtLeastJB_MR1()) { - return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE); - } else { - final NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - if (info != null) { - return info.getType(); - } - } - return TYPE_NONE; - } - - /** - * Dump the raw MMS data into a file - * - * @param rawPdu The raw pdu data - * @param pdu The parsed pdu, used to construct a dump file name - */ - public static void dumpPdu(final byte[] rawPdu, final GenericPdu pdu) { - if (rawPdu == null || rawPdu.length < 1) { - return; - } - final String dumpFileName = MmsUtils.MMS_DUMP_PREFIX + getDumpFileId(pdu); - final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true); - if (dumpFile != null) { - try { - final FileOutputStream fos = new FileOutputStream(dumpFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos); - try { - bos.write(rawPdu); - bos.flush(); - } finally { - bos.close(); - } - DebugUtils.ensureReadable(dumpFile); - } catch (final IOException e) { - LogUtil.e(TAG, "dumpPdu: " + e, e); - } - } - } - - /** - * Get the dump file id based on the parsed PDU - * 1. Use message id if not empty - * 2. Use transaction id if message id is empty - * 3. If all above is empty, use random UUID - * - * @param pdu the parsed PDU - * @return the id of the dump file - */ - private static String getDumpFileId(final GenericPdu pdu) { - String fileId = null; - if (pdu != null && pdu instanceof RetrieveConf) { - final RetrieveConf retrieveConf = (RetrieveConf) pdu; - if (retrieveConf.getMessageId() != null) { - fileId = new String(retrieveConf.getMessageId()); - } else if (retrieveConf.getTransactionId() != null) { - fileId = new String(retrieveConf.getTransactionId()); - } - } - if (TextUtils.isEmpty(fileId)) { - fileId = UUID.randomUUID().toString(); - } - return fileId; - } -} diff --git a/src/com/android/messaging/sms/SmsException.java b/src/com/android/messaging/sms/SmsException.java deleted file mode 100644 index 728db8c..0000000 --- a/src/com/android/messaging/sms/SmsException.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -/** - * A generic Exception for errors in sending SMS - */ -class SmsException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * Creates a new SmsException. - */ - public SmsException() { - super(); - } - - /** - * Creates a new SmsException with the specified detail message. - * - * @param message the detail message. - */ - public SmsException(String message) { - super(message); - } - - /** - * Creates a new SmsException with the specified cause. - * - * @param cause the cause. - */ - public SmsException(Throwable cause) { - super(cause); - } - - /** - * Creates a new SmsException with the specified detail message and cause. - * - * @param message the detail message. - * @param cause the cause. - */ - public SmsException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/com/android/messaging/sms/SmsReleaseStorage.java b/src/com/android/messaging/sms/SmsReleaseStorage.java deleted file mode 100644 index 13a6284..0000000 --- a/src/com/android/messaging/sms/SmsReleaseStorage.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.content.res.Resources; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Class handling message cleanup when storage is low - */ -public class SmsReleaseStorage { - /** - * Class representing a time duration specified by Gservices - */ - public static class Duration { - // Time duration unit types - public static final int UNIT_WEEK = 'w'; - public static final int UNIT_MONTH = 'm'; - public static final int UNIT_YEAR = 'y'; - - // Number of units - public final int mCount; - // Unit type: week, month or year - public final int mUnit; - - public Duration(final int count, final int unit) { - mCount = count; - mUnit = unit; - } - } - - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final Duration DEFAULT_DURATION = new Duration(1, Duration.UNIT_MONTH); - - private static final Pattern DURATION_PATTERN = Pattern.compile("([1-9]+\\d*)(w|m|y)"); - /** - * Parse message retaining time duration specified by Gservices - * - * @return The parsed time duration from Gservices - */ - public static Duration parseMessageRetainingDuration() { - final String smsAutoDeleteMessageRetainingDuration = - BugleGservices.get().getString( - BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION, - BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT); - final Matcher matcher = DURATION_PATTERN.matcher(smsAutoDeleteMessageRetainingDuration); - try { - if (matcher.matches()) { - return new Duration( - Integer.parseInt(matcher.group(1)), - matcher.group(2).charAt(0)); - } - } catch (final NumberFormatException e) { - // Nothing to do - } - LogUtil.e(TAG, "SmsAutoDelete: invalid duration " + - smsAutoDeleteMessageRetainingDuration); - return DEFAULT_DURATION; - } - - /** - * Get string representation of the time duration - * - * @param duration - * @return - */ - public static String getMessageRetainingDurationString(final Duration duration) { - final Resources resources = Factory.get().getApplicationContext().getResources(); - switch (duration.mUnit) { - case Duration.UNIT_WEEK: - return resources.getQuantityString( - R.plurals.week_count, duration.mCount, duration.mCount); - case Duration.UNIT_MONTH: - return resources.getQuantityString( - R.plurals.month_count, duration.mCount, duration.mCount); - case Duration.UNIT_YEAR: - return resources.getQuantityString( - R.plurals.year_count, duration.mCount, duration.mCount); - } - throw new IllegalArgumentException( - "SmsAutoDelete: invalid duration unit " + duration.mUnit); - } - - // Time conversations - private static final long WEEK_IN_MILLIS = 7 * 24 * 3600 * 1000L; - private static final long MONTH_IN_MILLIS = 30 * 24 * 3600 * 1000L; - private static final long YEAR_IN_MILLIS = 365 * 24 * 3600 * 1000L; - - /** - * Convert time duration to time in milliseconds - * - * @param duration - * @return - */ - public static long durationToTimeInMillis(final Duration duration) { - switch (duration.mUnit) { - case Duration.UNIT_WEEK: - return duration.mCount * WEEK_IN_MILLIS; - case Duration.UNIT_MONTH: - return duration.mCount * MONTH_IN_MILLIS; - case Duration.UNIT_YEAR: - return duration.mCount * YEAR_IN_MILLIS; - } - return -1L; - } - - /** - * Delete message actions: - * 0: delete media messages - * 1: delete old messages - * - * @param actionIndex The index of the delete action to perform - * @param durationInMillis The time duration for retaining messages - */ - public static void deleteMessages(final int actionIndex, final long durationInMillis) { - int deleted = 0; - switch (actionIndex) { - case 0: { - // Delete media - deleted = MmsUtils.deleteMediaMessages(); - break; - } - case 1: { - // Delete old messages - final long now = System.currentTimeMillis(); - final long cutOffTimestampInMillis = now - durationInMillis; - // Delete messages from telephony provider - deleted = MmsUtils.deleteMessagesOlderThan(cutOffTimestampInMillis); - break; - } - default: { - LogUtil.e(TAG, "SmsStorageStatusManager: invalid action " + actionIndex); - break; - } - } - - if (deleted > 0) { - // Kick off a sync to update local db. - SyncManager.sync(); - } - } -} diff --git a/src/com/android/messaging/sms/SmsSender.java b/src/com/android/messaging/sms/SmsSender.java deleted file mode 100644 index 889973f..0000000 --- a/src/com/android/messaging/sms/SmsSender.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.sms; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.SystemClock; -import android.telephony.PhoneNumberUtils; -import android.telephony.SmsManager; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.receiver.SendStatusReceiver; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Class that sends chat message via SMS. - * - * The interface emulates a blocking sending similar to making an HTTP request. - * It calls the SmsManager to send a (potentially multipart) message and waits - * on the sent status on each part. The waiting has a timeout so it won't wait - * forever. Once the sent status of all parts received, the call returns. - * A successful sending requires success status for all parts. Otherwise, we - * pick the highest level of failure as the error for the whole message, which - * is used to determine if we need to retry the sending. - */ -public class SmsSender { - private static final String TAG = LogUtil.BUGLE_TAG; - - public static final String EXTRA_PART_ID = "part_id"; - - /* - * A map for pending sms messages. The key is the random request UUID. - */ - private static ConcurrentHashMap sPendingMessageMap = - new ConcurrentHashMap(); - - private static final Random RANDOM = new Random(); - - // Whether we should send multipart SMS as separate messages - private static Boolean sSendMultipartSmsAsSeparateMessages = null; - - /** - * Class that holds the sent status for all parts of a multipart message sending - */ - public static class SendResult { - // Failure levels, used by the caller of the sender. - // For temporary failures, possibly we could retry the sending - // For permanent failures, we probably won't retry - public static final int FAILURE_LEVEL_NONE = 0; - public static final int FAILURE_LEVEL_TEMPORARY = 1; - public static final int FAILURE_LEVEL_PERMANENT = 2; - - // Tracking the remaining pending parts in sending - private int mPendingParts; - // Tracking the highest level of failure among all parts - private int mHighestFailureLevel; - - public SendResult(final int numOfParts) { - Assert.isTrue(numOfParts > 0); - mPendingParts = numOfParts; - mHighestFailureLevel = FAILURE_LEVEL_NONE; - } - - // Update the sent status of one part - public void setPartResult(final int resultCode) { - mPendingParts--; - setHighestFailureLevel(resultCode); - } - - public boolean hasPending() { - return mPendingParts > 0; - } - - public int getHighestFailureLevel() { - return mHighestFailureLevel; - } - - private int getFailureLevel(final int resultCode) { - switch (resultCode) { - case Activity.RESULT_OK: - return FAILURE_LEVEL_NONE; - case SmsManager.RESULT_ERROR_NO_SERVICE: - return FAILURE_LEVEL_TEMPORARY; - case SmsManager.RESULT_ERROR_RADIO_OFF: - return FAILURE_LEVEL_PERMANENT; - case SmsManager.RESULT_ERROR_GENERIC_FAILURE: - return FAILURE_LEVEL_PERMANENT; - default: { - LogUtil.e(TAG, "SmsSender: Unexpected sent intent resultCode = " + resultCode); - return FAILURE_LEVEL_PERMANENT; - } - } - } - - private void setHighestFailureLevel(final int resultCode) { - final int level = getFailureLevel(resultCode); - if (level > mHighestFailureLevel) { - mHighestFailureLevel = level; - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("SendResult:"); - sb.append("Pending=").append(mPendingParts).append(","); - sb.append("HighestFailureLevel=").append(mHighestFailureLevel); - return sb.toString(); - } - } - - public static void setResult(final Uri requestId, final int resultCode, - final int errorCode, final int partId, int subId) { - if (resultCode != Activity.RESULT_OK) { - LogUtil.e(TAG, "SmsSender: failure in sending message part. " - + " requestId=" + requestId + " partId=" + partId - + " resultCode=" + resultCode + " errorCode=" + errorCode); - if (errorCode != SendStatusReceiver.NO_ERROR_CODE) { - final Context context = Factory.get().getApplicationContext(); - UiUtils.showToastAtBottom(getSendErrorToastMessage(context, subId, errorCode)); - } - } else { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SmsSender: received sent result. " + " requestId=" + requestId - + " partId=" + partId + " resultCode=" + resultCode); - } - } - if (requestId != null) { - final SendResult result = sPendingMessageMap.get(requestId); - if (result != null) { - synchronized (result) { - result.setPartResult(resultCode); - if (!result.hasPending()) { - result.notifyAll(); - } - } - } else { - LogUtil.e(TAG, "SmsSender: ignoring sent result. " + " requestId=" + requestId - + " partId=" + partId + " resultCode=" + resultCode); - } - } - } - - private static String getSendErrorToastMessage(final Context context, final int subId, - final int errorCode) { - final String carrierName = PhoneUtils.get(subId).getCarrierName(); - if (TextUtils.isEmpty(carrierName)) { - return context.getString(R.string.carrier_send_error_unknown_carrier, errorCode); - } else { - return context.getString(R.string.carrier_send_error, carrierName, errorCode); - } - } - - // This should be called from a RequestWriter queue thread - public static SendResult sendMessage(final Context context, final int subId, String dest, - String message, final String serviceCenter, final boolean requireDeliveryReport, - final Uri messageUri) throws SmsException { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SmsSender: sending message. " + - "dest=" + dest + " message=" + message + - " serviceCenter=" + serviceCenter + - " requireDeliveryReport=" + requireDeliveryReport + - " requestId=" + messageUri); - } - if (TextUtils.isEmpty(message)) { - throw new SmsException("SmsSender: empty text message"); - } - // Get the real dest and message for email or alias if dest is email or alias - // Or sanitize the dest if dest is a number - if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway()) && - (MmsSmsUtils.isEmailAddress(dest) || MmsSmsUtils.isAlias(dest, subId))) { - // The original destination (email address) goes with the message - message = dest + " " + message; - // the new address is the email gateway # - dest = MmsConfig.get(subId).getEmailGateway(); - } else { - // remove spaces and dashes from destination number - // (e.g. "801 555 1212" -> "8015551212") - // (e.g. "+8211-123-4567" -> "+82111234567") - dest = PhoneNumberUtils.stripSeparators(dest); - } - if (TextUtils.isEmpty(dest)) { - throw new SmsException("SmsSender: empty destination address"); - } - // Divide the input message by SMS length limit - final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager(); - final ArrayList messages = smsManager.divideMessage(message); - if (messages == null || messages.size() < 1) { - throw new SmsException("SmsSender: fails to divide message"); - } - // Prepare the send result, which collects the send status for each part - final SendResult pendingResult = new SendResult(messages.size()); - sPendingMessageMap.put(messageUri, pendingResult); - // Actually send the sms - sendInternal( - context, subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri); - // Wait for pending intent to come back - synchronized (pendingResult) { - final long smsSendTimeoutInMillis = BugleGservices.get().getLong( - BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS, - BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT); - final long beginTime = SystemClock.elapsedRealtime(); - long waitTime = smsSendTimeoutInMillis; - // We could possibly be woken up while still pending - // so make sure we wait the full timeout period unless - // we have the send results of all parts. - while (pendingResult.hasPending() && waitTime > 0) { - try { - pendingResult.wait(waitTime); - } catch (final InterruptedException e) { - LogUtil.e(TAG, "SmsSender: sending wait interrupted"); - } - waitTime = smsSendTimeoutInMillis - (SystemClock.elapsedRealtime() - beginTime); - } - } - // Either we timed out or have all the results (success or failure) - sPendingMessageMap.remove(messageUri); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "SmsSender: sending completed. " + - "dest=" + dest + " message=" + message + " result=" + pendingResult); - } - return pendingResult; - } - - // Actually sending the message using SmsManager - private static void sendInternal(final Context context, final int subId, String dest, - final ArrayList messages, final String serviceCenter, - final boolean requireDeliveryReport, final Uri messageUri) throws SmsException { - Assert.notNull(context); - final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager(); - final int messageCount = messages.size(); - final ArrayList deliveryIntents = new ArrayList(messageCount); - final ArrayList sentIntents = new ArrayList(messageCount); - for (int i = 0; i < messageCount; i++) { - // Make pending intents different for each message part - final int partId = (messageCount <= 1 ? 0 : i + 1); - if (requireDeliveryReport && (i == (messageCount - 1))) { - // TODO we only care about the delivery status of the last part - // Shall we have better tracking of delivery status of all parts? - deliveryIntents.add(PendingIntent.getBroadcast( - context, - partId, - getSendStatusIntent(context, SendStatusReceiver.MESSAGE_DELIVERED_ACTION, - messageUri, partId, subId), - 0/*flag*/)); - } else { - deliveryIntents.add(null); - } - sentIntents.add(PendingIntent.getBroadcast( - context, - partId, - getSendStatusIntent(context, SendStatusReceiver.MESSAGE_SENT_ACTION, - messageUri, partId, subId), - 0/*flag*/)); - } - if (sSendMultipartSmsAsSeparateMessages == null) { - sSendMultipartSmsAsSeparateMessages = MmsConfig.get(subId) - .getSendMultipartSmsAsSeparateMessages(); - } - try { - if (sSendMultipartSmsAsSeparateMessages) { - // If multipart sms is not supported, send them as separate messages - for (int i = 0; i < messageCount; i++) { - smsManager.sendTextMessage(dest, - serviceCenter, - messages.get(i), - sentIntents.get(i), - deliveryIntents.get(i)); - } - } else { - smsManager.sendMultipartTextMessage( - dest, serviceCenter, messages, sentIntents, deliveryIntents); - } - } catch (final Exception e) { - throw new SmsException("SmsSender: caught exception in sending " + e); - } - } - - private static Intent getSendStatusIntent(final Context context, final String action, - final Uri requestUri, final int partId, final int subId) { - // Encode requestId in intent data - final Intent intent = new Intent(action, requestUri, context, SendStatusReceiver.class); - intent.putExtra(SendStatusReceiver.EXTRA_PART_ID, partId); - intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId); - return intent; - } -} diff --git a/src/com/android/messaging/sms/SmsStorageStatusManager.java b/src/com/android/messaging/sms/SmsStorageStatusManager.java deleted file mode 100644 index ff7b79d..0000000 --- a/src/com/android/messaging/sms/SmsStorageStatusManager.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.sms; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.content.res.Resources; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.PendingIntentConstants; -import com.android.messaging.util.PhoneUtils; - -/** - * Class that handles SMS auto delete and notification when storage is low - */ -public class SmsStorageStatusManager { - /** - * Handles storage low signal for SMS - */ - public static void handleStorageLow() { - if (!PhoneUtils.getDefault().isSmsEnabled()) { - return; - } - - // TODO: Auto-delete messages, when that setting exists and is enabled - - // Notify low storage for SMS - postStorageLowNotification(); - } - - /** - * Handles storage OK signal for SMS - */ - public static void handleStorageOk() { - if (!PhoneUtils.getDefault().isSmsEnabled()) { - return; - } - cancelStorageLowNotification(); - } - - /** - * Post sms storage low notification - */ - private static void postStorageLowNotification() { - final Context context = Factory.get().getApplicationContext(); - final Resources resources = context.getResources(); - final PendingIntent pendingIntent = UIIntents.get() - .getPendingIntentForLowStorageNotifications(context); - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - builder.setContentTitle(resources.getString(R.string.sms_storage_low_title)) - .setTicker(resources.getString(R.string.sms_storage_low_notification_ticker)) - .setSmallIcon(R.drawable.ic_failed_light) - .setPriority(Notification.PRIORITY_DEFAULT) - .setOngoing(true) // Can't be swiped off - .setAutoCancel(false) // Don't auto cancel - .setContentIntent(pendingIntent); - - final NotificationCompat.BigTextStyle bigTextStyle = - new NotificationCompat.BigTextStyle(builder); - bigTextStyle.bigText(resources.getString(R.string.sms_storage_low_text)); - final Notification notification = bigTextStyle.build(); - - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - - notificationManager.notify(getNotificationTag(), - PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID, notification); - } - - /** - * Cancel the notification - */ - public static void cancelStorageLowNotification() { - final NotificationManagerCompat notificationManager = - NotificationManagerCompat.from(Factory.get().getApplicationContext()); - notificationManager.cancel(getNotificationTag(), - PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID); - } - - private static String getNotificationTag() { - return Factory.get().getApplicationContext().getPackageName() + ":smsstoragelow"; - } -} diff --git a/src/com/android/messaging/sms/SystemProperties.java b/src/com/android/messaging/sms/SystemProperties.java deleted file mode 100644 index 669e448..0000000 --- a/src/com/android/messaging/sms/SystemProperties.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.sms; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Hacky way to call the hidden SystemProperties class API - */ -class SystemProperties { - private static Method sSystemPropertiesGetMethod = null; - - public static String get(final String name) { - if (sSystemPropertiesGetMethod == null) { - try { - final Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); - if (systemPropertiesClass != null) { - sSystemPropertiesGetMethod = - systemPropertiesClass.getMethod("get", String.class); - } - } catch (final ClassNotFoundException e) { - // Nothing to do - } catch (final NoSuchMethodException e) { - // Nothing to do - } - } - if (sSystemPropertiesGetMethod != null) { - try { - return (String) sSystemPropertiesGetMethod.invoke(null, name); - } catch (final IllegalArgumentException e) { - // Nothing to do - } catch (final IllegalAccessException e) { - // Nothing to do - } catch (final InvocationTargetException e) { - // Nothing to do - } - } - return null; - } -} diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java deleted file mode 100644 index 9aaf0b1..0000000 --- a/src/com/android/messaging/ui/AsyncImageView.java +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Path; -import android.graphics.RectF; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.support.annotation.Nullable; -import android.support.rastermill.FrameSequenceDrawable; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.media.BindableMediaRequest; -import com.android.messaging.datamodel.media.GifImageResource; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.ThreadUtil; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -import java.util.HashSet; - -/** - * An ImageView used to asynchronously request an image from MediaResourceManager and render it. - */ -public class AsyncImageView extends ImageView implements MediaResourceLoadListener { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - // 100ms delay before disposing the image in case the AsyncImageView is re-added to the UI - private static final int DISPOSE_IMAGE_DELAY = 100; - - // AsyncImageView has a 1-1 binding relationship with an ImageRequest instance that requests - // the image from the MediaResourceManager. Since the request is done asynchronously, we - // want to make sure the image view is always bound to the latest image request that it - // issues, so that when the image is loaded, the ImageRequest (which extends BindableData) - // will be able to figure out whether the binding is still valid and whether the loaded image - // should be delivered to the AsyncImageView via onMediaResourceLoaded() callback. - @VisibleForTesting - public final Binding> mImageRequestBinding; - - /** True if we want the image to fade in when it loads */ - private boolean mFadeIn; - - /** True if we want the image to reveal (scale) when it loads. When set to true, this - * will take precedence over {@link #mFadeIn} */ - private final boolean mReveal; - - // The corner radius for drawing rounded corners around bitmap. The default value is zero - // (no rounded corners) - private final int mCornerRadius; - private final Path mRoundedCornerClipPath; - private int mClipPathWidth; - private int mClipPathHeight; - - // A placeholder drawable that takes the spot of the image when it's loading. The default - // setting is null (no placeholder). - private final Drawable mPlaceholderDrawable; - protected ImageResource mImageResource; - private final Runnable mDisposeRunnable = new Runnable() { - @Override - public void run() { - if (mImageRequestBinding.isBound()) { - mDetachedRequestDescriptor = (ImageRequestDescriptor) - mImageRequestBinding.getData().getDescriptor(); - } - unbindView(); - releaseImageResource(); - } - }; - - private AsyncImageViewDelayLoader mDelayLoader; - private ImageRequestDescriptor mDetachedRequestDescriptor; - - public AsyncImageView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mImageRequestBinding = BindingBase.createBinding(this); - final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView, - 0, 0); - mFadeIn = attr.getBoolean(R.styleable.AsyncImageView_fadeIn, true); - mReveal = attr.getBoolean(R.styleable.AsyncImageView_reveal, false); - mPlaceholderDrawable = attr.getDrawable(R.styleable.AsyncImageView_placeholderDrawable); - mCornerRadius = attr.getDimensionPixelSize(R.styleable.AsyncImageView_cornerRadius, 0); - mRoundedCornerClipPath = new Path(); - - attr.recycle(); - } - - /** - * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor - * @param descriptor the request descriptor, or null if no image should be displayed - */ - public void setImageResourceId(@Nullable final ImageRequestDescriptor descriptor) { - final String requestKey = (descriptor == null) ? null : descriptor.getKey(); - if (mImageRequestBinding.isBound()) { - if (TextUtils.equals(mImageRequestBinding.getData().getKey(), requestKey)) { - // Don't re-request the bitmap if the new request is for the same resource. - return; - } - unbindView(); - } - setImage(null); - resetTransientViewStates(); - if (!TextUtils.isEmpty(requestKey)) { - maybeSetupPlaceholderDrawable(descriptor); - final BindableMediaRequest imageRequest = - descriptor.buildAsyncMediaRequest(getContext(), this); - requestImage(imageRequest); - } - } - - /** - * Sets a delay loader that centrally manages image request delay loading logic. - */ - public void setDelayLoader(final AsyncImageViewDelayLoader delayLoader) { - Assert.isTrue(mDelayLoader == null); - mDelayLoader = delayLoader; - } - - /** - * Called by the delay loader when we can resume image loading. - */ - public void resumeLoading() { - Assert.notNull(mDelayLoader); - Assert.isTrue(mImageRequestBinding.isBound()); - MediaResourceManager.get().requestMediaResourceAsync(mImageRequestBinding.getData()); - } - - /** - * Setup the placeholder drawable if: - * 1. There's an image to be loaded AND - * 2. We are given a placeholder drawable AND - * 3. The descriptor provided us with source width and height. - */ - private void maybeSetupPlaceholderDrawable(final ImageRequestDescriptor descriptor) { - if (!TextUtils.isEmpty(descriptor.getKey()) && mPlaceholderDrawable != null) { - if (descriptor.sourceWidth != ImageRequest.UNSPECIFIED_SIZE && - descriptor.sourceHeight != ImageRequest.UNSPECIFIED_SIZE) { - // Set a transparent inset drawable to the foreground so it will mimick the final - // size of the image, and use the background to show the actual placeholder - // drawable. - setImageDrawable(PlaceholderInsetDrawable.fromDrawable( - new ColorDrawable(Color.TRANSPARENT), - descriptor.sourceWidth, descriptor.sourceHeight)); - } - setBackground(mPlaceholderDrawable); - } - } - - protected void setImage(final ImageResource resource) { - setImage(resource, false /* isCached */); - } - - protected void setImage(final ImageResource resource, final boolean isCached) { - // Switch reference to the new ImageResource. Make sure we release the current - // resource and addRef() on the new resource so that the underlying bitmaps don't - // get leaked or get recycled by the bitmap cache. - releaseImageResource(); - // Ensure that any pending dispose runnables get removed. - ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable); - // The drawable may require work to get if its a static object so try to only make this call - // once. - final Drawable drawable = (resource != null) ? resource.getDrawable(getResources()) : null; - if (drawable != null) { - mImageResource = resource; - mImageResource.addRef(); - setImageDrawable(drawable); - if (drawable instanceof FrameSequenceDrawable) { - ((FrameSequenceDrawable) drawable).start(); - } - - if (getVisibility() == VISIBLE) { - if (mReveal) { - setVisibility(INVISIBLE); - UiUtils.revealOrHideViewWithAnimation(this, VISIBLE, null); - } else if (mFadeIn && !isCached) { - // Hide initially to avoid flash. - setAlpha(0F); - animate().alpha(1F).start(); - } - } - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - if (mImageResource instanceof GifImageResource) { - LogUtil.v(TAG, "setImage size unknown -- it's a GIF"); - } else { - LogUtil.v(TAG, "setImage size: " + mImageResource.getMediaSize() + - " width: " + mImageResource.getBitmap().getWidth() + - " heigh: " + mImageResource.getBitmap().getHeight()); - } - } - } - invalidate(); - } - - private void requestImage(final BindableMediaRequest request) { - mImageRequestBinding.bind(request); - if (mDelayLoader == null || !mDelayLoader.isDelayLoadingImage()) { - MediaResourceManager.get().requestMediaResourceAsync(request); - } else { - mDelayLoader.registerView(this); - } - } - - @Override - public void onMediaResourceLoaded(final MediaRequest request, - final ImageResource resource, final boolean isCached) { - if (mImageResource != resource) { - setImage(resource, isCached); - } - } - - @Override - public void onMediaResourceLoadError( - final MediaRequest request, final Exception exception) { - // Media load failed, unbind and reset bitmap to default. - unbindView(); - setImage(null); - } - - private void releaseImageResource() { - final Drawable drawable = getDrawable(); - if (drawable instanceof FrameSequenceDrawable) { - ((FrameSequenceDrawable) drawable).stop(); - ((FrameSequenceDrawable) drawable).destroy(); - } - if (mImageResource != null) { - mImageResource.release(); - mImageResource = null; - } - setImageDrawable(null); - setBackground(null); - } - - /** - * Resets transient view states (eg. alpha, animations) before rebinding/reusing the view. - */ - private void resetTransientViewStates() { - clearAnimation(); - setAlpha(1F); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - // If it was recently removed, then cancel disposing, we're still using it. - ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable); - - // When the image view gets detached and immediately re-attached, any fade-in animation - // will be terminated, leaving the view in a semi-transparent state. Make sure we restore - // alpha when the view is re-attached. - if (mFadeIn) { - setAlpha(1F); - } - - // Check whether we are in a simple reuse scenario: detached from window, and reattached - // later without rebinding. This may be done by containers such as the RecyclerView to - // reuse the views. In this case, we would like to rebind the original image request. - if (!mImageRequestBinding.isBound() && mDetachedRequestDescriptor != null) { - setImageResourceId(mDetachedRequestDescriptor); - } - mDetachedRequestDescriptor = null; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - // Dispose the bitmap, but if an AysncImageView is removed from the window, then quickly - // re-added, we shouldn't dispose, so wait a short time before disposing - ThreadUtil.getMainThreadHandler().postDelayed(mDisposeRunnable, DISPOSE_IMAGE_DELAY); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // The base implementation does not honor the minimum sizes. We try to to honor it here. - - final int measuredWidth = getMeasuredWidth(); - final int measuredHeight = getMeasuredHeight(); - if (measuredWidth >= getMinimumWidth() || measuredHeight >= getMinimumHeight()) { - // We are ok if either of the minimum sizes is honored. Note that satisfying both the - // sizes may not be possible, depending on the aspect ratio of the image and whether - // a maximum size has been specified. This implementation only tries to handle the case - // where both the minimum sizes are not being satisfied. - return; - } - - if (!getAdjustViewBounds()) { - // The base implementation is reasonable in this case. If the view bounds cannot be - // changed, it is not possible to satisfy the minimum sizes anyway. - return; - } - - final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { - // The base implementation is reasonable in this case. - return; - } - - int width = measuredWidth; - int height = measuredHeight; - // Get the minimum sizes that will honor other constraints as well. - final int minimumWidth = resolveSize( - getMinimumWidth(), getMaxWidth(), widthMeasureSpec); - final int minimumHeight = resolveSize( - getMinimumHeight(), getMaxHeight(), heightMeasureSpec); - final float aspectRatio = measuredWidth / (float) measuredHeight; - if (aspectRatio == 0) { - // If the image is (close to) infinitely high, there is not much we can do. - return; - } - - if (width < minimumWidth) { - height = resolveSize((int) (minimumWidth / aspectRatio), - getMaxHeight(), heightMeasureSpec); - width = (int) (height * aspectRatio); - } - - if (height < minimumHeight) { - width = resolveSize((int) (minimumHeight * aspectRatio), - getMaxWidth(), widthMeasureSpec); - height = (int) (width / aspectRatio); - } - - setMeasuredDimension(width, height); - } - - private static int resolveSize(int desiredSize, int maxSize, int measureSpec) { - final int specMode = MeasureSpec.getMode(measureSpec); - final int specSize = MeasureSpec.getSize(measureSpec); - switch(specMode) { - case MeasureSpec.UNSPECIFIED: - return Math.min(desiredSize, maxSize); - - case MeasureSpec.AT_MOST: - return Math.min(Math.min(desiredSize, specSize), maxSize); - - default: - Assert.fail("Unreachable"); - return specSize; - } - } - - @Override - protected void onDraw(final Canvas canvas) { - if (mCornerRadius > 0) { - final int currentWidth = this.getWidth(); - final int currentHeight = this.getHeight(); - if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) { - final RectF rect = new RectF(0, 0, currentWidth, currentHeight); - mRoundedCornerClipPath.reset(); - mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius, - Path.Direction.CW); - mClipPathWidth = currentWidth; - mClipPathHeight = currentHeight; - } - - final int saveCount = canvas.getSaveCount(); - canvas.save(); - canvas.clipPath(mRoundedCornerClipPath); - super.onDraw(canvas); - canvas.restoreToCount(saveCount); - } else { - super.onDraw(canvas); - } - } - - private void unbindView() { - if (mImageRequestBinding.isBound()) { - mImageRequestBinding.unbind(); - if (mDelayLoader != null) { - mDelayLoader.unregisterView(this); - } - } - } - - /** - * As a performance optimization, the consumer of the AsyncImageView may opt to delay loading - * the image when it's busy doing other things (such as when a list view is scrolling). In - * order to do this, the consumer can create a new AsyncImageViewDelayLoader instance to be - * shared among all relevant AsyncImageViews (through setDelayLoader() method), and call - * onStartDelayLoading() and onStopDelayLoading() to start and stop delay loading, respectively. - */ - public static class AsyncImageViewDelayLoader { - private boolean mShouldDelayLoad; - private final HashSet mAttachedViews; - - public AsyncImageViewDelayLoader() { - mAttachedViews = new HashSet(); - } - - private void registerView(final AsyncImageView view) { - mAttachedViews.add(view); - } - - private void unregisterView(final AsyncImageView view) { - mAttachedViews.remove(view); - } - - public boolean isDelayLoadingImage() { - return mShouldDelayLoad; - } - - /** - * Called by the consumer of this view to delay loading images - */ - public void onDelayLoading() { - // Don't need to explicitly tell the AsyncImageView to stop loading since - // ImageRequests are not cancellable. - mShouldDelayLoad = true; - } - - /** - * Called by the consumer of this view to resume loading images - */ - public void onResumeLoading() { - if (mShouldDelayLoad) { - mShouldDelayLoad = false; - - // Notify all attached views to resume loading. - for (final AsyncImageView view : mAttachedViews) { - view.resumeLoading(); - } - mAttachedViews.clear(); - } - } - } -} diff --git a/src/com/android/messaging/ui/AttachmentPreview.java b/src/com/android/messaging/ui/AttachmentPreview.java deleted file mode 100644 index 7eea14b..0000000 --- a/src/com/android/messaging/ui/AttachmentPreview.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ScrollView; - -import com.android.messaging.R; -import com.android.messaging.annotation.VisibleForAnimation; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.MediaPickerMessagePartData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener; -import com.android.messaging.ui.animation.PopupTransitionAnimation; -import com.android.messaging.ui.conversation.ComposeMessageView; -import com.android.messaging.ui.conversation.ConversationFragment; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ThreadUtil; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; -import java.util.List; - -public class AttachmentPreview extends ScrollView implements OnAttachmentClickListener { - private FrameLayout mAttachmentView; - private ComposeMessageView mComposeMessageView; - private ImageButton mCloseButton; - private int mAnimatedHeight = -1; - private Animator mCloseGapAnimator; - private boolean mPendingFirstUpdate; - private Handler mHandler; - private Runnable mHideRunnable; - private boolean mPendingHideCanceled; - - private static final int CLOSE_BUTTON_REVEAL_STAGGER_MILLIS = 300; - - public AttachmentPreview(final Context context, final AttributeSet attrs) { - super(context, attrs); - mHandler = new Handler(Looper.getMainLooper()); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mCloseButton = (ImageButton) findViewById(R.id.close_button); - mCloseButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - mComposeMessageView.clearAttachments(); - } - }); - - mAttachmentView = (FrameLayout) findViewById(R.id.attachment_view); - - // The attachment preview is a scroll view so that it can show the bottom portion of the - // attachment whenever the space is tight (e.g. when in landscape mode). Per design - // request we'd like to make the attachment view always scrolled to the bottom. - addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, - final int bottom, final int oldLeft, final int oldTop, final int oldRight, - final int oldBottom) { - post(new Runnable() { - @Override - public void run() { - final int childCount = getChildCount(); - if (childCount > 0) { - final View lastChild = getChildAt(childCount - 1); - scrollTo(getScrollX(), lastChild.getBottom() - getHeight()); - } - } - }); - } - }); - mPendingFirstUpdate = true; - } - - public void setComposeMessageView(final ComposeMessageView composeMessageView) { - mComposeMessageView = composeMessageView; - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mAnimatedHeight >= 0) { - setMeasuredDimension(getMeasuredWidth(), mAnimatedHeight); - } - } - - private void cancelPendingHide() { - mPendingHideCanceled = true; - } - - public void hideAttachmentPreview() { - if (getVisibility() != GONE) { - UiUtils.revealOrHideViewWithAnimation(mCloseButton, GONE, - null /* onFinishRunnable */); - startCloseGapAnimationOnAttachmentClear(); - - if (mAttachmentView.getChildCount() > 0) { - mPendingHideCanceled = false; - final View viewToHide = mAttachmentView.getChildCount() > 1 ? - mAttachmentView : mAttachmentView.getChildAt(0); - UiUtils.revealOrHideViewWithAnimation(viewToHide, INVISIBLE, - new Runnable() { - @Override - public void run() { - // Only hide if we are didn't get overruled by showing - if (!mPendingHideCanceled) { - mAttachmentView.removeAllViews(); - setVisibility(GONE); - } - } - }); - } else { - mAttachmentView.removeAllViews(); - setVisibility(GONE); - } - } - } - - // returns true if we have attachments - public boolean onAttachmentsChanged(final DraftMessageData draftMessageData) { - final boolean isFirstUpdate = mPendingFirstUpdate; - final List attachments = draftMessageData.getReadOnlyAttachments(); - final List pendingAttachments = - draftMessageData.getReadOnlyPendingAttachments(); - - // Any change in attachments would invalidate the animated height animation. - cancelCloseGapAnimation(); - mPendingFirstUpdate = false; - - final int combinedAttachmentCount = attachments.size() + pendingAttachments.size(); - mCloseButton.setContentDescription(getResources() - .getQuantityString(R.plurals.attachment_preview_close_content_description, - combinedAttachmentCount)); - if (combinedAttachmentCount == 0) { - mHideRunnable = new Runnable() { - @Override - public void run() { - mHideRunnable = null; - // Only start the hiding if there are still no attachments - if (attachments.size() + pendingAttachments.size() == 0) { - hideAttachmentPreview(); - } - } - }; - if (draftMessageData.isSending()) { - // Wait to hide until the message is ready to start animating - // We'll execute immediately when the animation triggers - mHandler.postDelayed(mHideRunnable, - ConversationFragment.MESSAGE_ANIMATION_MAX_WAIT); - } else { - // Run immediately when clearing attachments - mHideRunnable.run(); - } - return false; - } - - cancelPendingHide(); // We're showing - if (getVisibility() != VISIBLE) { - setVisibility(VISIBLE); - mAttachmentView.setVisibility(VISIBLE); - - // Don't animate in the close button if this is the first update after view creation. - // This is the initial draft load from database for pre-existing drafts. - if (!isFirstUpdate) { - // Reveal the close button after the view animates in. - mCloseButton.setVisibility(INVISIBLE); - ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() { - @Override - public void run() { - UiUtils.revealOrHideViewWithAnimation(mCloseButton, VISIBLE, - null /* onFinishRunnable */); - } - }, UiUtils.MEDIAPICKER_TRANSITION_DURATION + CLOSE_BUTTON_REVEAL_STAGGER_MILLIS); - } - } - - // Merge the pending attachment list with real attachment. Design would prefer these be - // in LIFO order user can see added images past the 5th one but we also want them to be in - // order and we want it to be WYSIWYG. - final List combinedAttachments = new ArrayList<>(); - combinedAttachments.addAll(attachments); - combinedAttachments.addAll(pendingAttachments); - - final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - if (combinedAttachmentCount > 1) { - MultiAttachmentLayout multiAttachmentLayout = null; - Rect transitionRect = null; - if (mAttachmentView.getChildCount() > 0) { - final View firstChild = mAttachmentView.getChildAt(0); - if (firstChild instanceof MultiAttachmentLayout) { - Assert.equals(1, mAttachmentView.getChildCount()); - multiAttachmentLayout = (MultiAttachmentLayout) firstChild; - multiAttachmentLayout.bindAttachments(combinedAttachments, - null /* transitionRect */, combinedAttachmentCount); - } else { - transitionRect = new Rect(firstChild.getLeft(), firstChild.getTop(), - firstChild.getRight(), firstChild.getBottom()); - } - } - if (multiAttachmentLayout == null) { - multiAttachmentLayout = AttachmentPreviewFactory.createMultiplePreview( - getContext(), this); - multiAttachmentLayout.bindAttachments(combinedAttachments, transitionRect, - combinedAttachmentCount); - mAttachmentView.removeAllViews(); - mAttachmentView.addView(multiAttachmentLayout); - } - } else { - final MessagePartData attachment = combinedAttachments.get(0); - boolean shouldAnimate = true; - if (mAttachmentView.getChildCount() > 0) { - // If we are going from N->1 attachments, try to use the current bounds - // bounds as the starting rect. - shouldAnimate = false; - final View firstChild = mAttachmentView.getChildAt(0); - if (firstChild instanceof MultiAttachmentLayout && - attachment instanceof MediaPickerMessagePartData) { - final View leftoverView = ((MultiAttachmentLayout) firstChild) - .findViewForAttachment(attachment); - if (leftoverView != null) { - final Rect currentRect = UiUtils.getMeasuredBoundsOnScreen(leftoverView); - if (!currentRect.isEmpty() && - attachment instanceof MediaPickerMessagePartData) { - ((MediaPickerMessagePartData) attachment).setStartRect(currentRect); - shouldAnimate = true; - } - } - } - } - mAttachmentView.removeAllViews(); - final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview( - layoutInflater, attachment, mAttachmentView, - AttachmentPreviewFactory.TYPE_SINGLE, true /* startImageRequest */, this); - if (attachmentView != null) { - mAttachmentView.addView(attachmentView); - if (shouldAnimate) { - tryAnimateViewIn(attachment, attachmentView); - } - } - } - return true; - } - - public void onMessageAnimationStart() { - if (mHideRunnable == null) { - return; - } - - // Run the hide animation at the same time as the message animation - mHandler.removeCallbacks(mHideRunnable); - setVisibility(View.INVISIBLE); - mHideRunnable.run(); - } - - static void tryAnimateViewIn(final MessagePartData attachmentData, final View view) { - if (attachmentData instanceof MediaPickerMessagePartData) { - final Rect startRect = ((MediaPickerMessagePartData) attachmentData).getStartRect(); - new PopupTransitionAnimation(startRect, view).startAfterLayoutComplete(); - } - } - - @VisibleForAnimation - public void setAnimatedHeight(final int animatedHeight) { - if (mAnimatedHeight != animatedHeight) { - mAnimatedHeight = animatedHeight; - requestLayout(); - } - } - - /** - * Kicks off an animation to animate the layout change for closing the gap between the - * message list and the compose message box when the attachments are cleared. - */ - private void startCloseGapAnimationOnAttachmentClear() { - // Cancel existing animation. - cancelCloseGapAnimation(); - mCloseGapAnimator = ObjectAnimator.ofInt(this, "animatedHeight", getHeight(), 0); - mCloseGapAnimator.start(); - } - - private void cancelCloseGapAnimation() { - if (mCloseGapAnimator != null) { - mCloseGapAnimator.cancel(); - mCloseGapAnimator = null; - } - mAnimatedHeight = -1; - } - - @Override - public boolean onAttachmentClick(final MessagePartData attachment, - final Rect viewBoundsOnScreen, final boolean longPress) { - if (longPress) { - mComposeMessageView.onAttachmentPreviewLongClicked(); - return true; - } - - if (!(attachment instanceof PendingAttachmentData) && attachment.isImage()) { - mComposeMessageView.displayPhoto(attachment.getContentUri(), viewBoundsOnScreen); - return true; - } - return false; - } -} diff --git a/src/com/android/messaging/ui/AttachmentPreviewFactory.java b/src/com/android/messaging/ui/AttachmentPreviewFactory.java deleted file mode 100644 index ed5d4d7..0000000 --- a/src/com/android/messaging/ui/AttachmentPreviewFactory.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.datamodel.data.VCardContactItemData; -import com.android.messaging.datamodel.media.FileImageRequestDescriptor; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener; -import com.android.messaging.ui.PersonItemView.PersonItemViewListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; - -/** - * A view factory that creates previews for single/multiple attachments. - */ -public class AttachmentPreviewFactory { - /** Standalone attachment preview */ - public static final int TYPE_SINGLE = 1; - - /** Attachment preview displayed in a multi-attachment layout */ - public static final int TYPE_MULTIPLE = 2; - - /** Attachment preview displayed in the attachment chooser grid view */ - public static final int TYPE_CHOOSER_GRID = 3; - - public static View createAttachmentPreview(final LayoutInflater layoutInflater, - final MessagePartData attachmentData, final ViewGroup parent, - final int viewType, final boolean startImageRequest, - @Nullable final OnAttachmentClickListener clickListener) { - final String contentType = attachmentData.getContentType(); - View attachmentView = null; - if (attachmentData instanceof PendingAttachmentData) { - attachmentView = createPendingAttachmentPreview(layoutInflater, parent, - (PendingAttachmentData) attachmentData); - } else if (ContentType.isImageType(contentType)) { - attachmentView = createImagePreview(layoutInflater, attachmentData, parent, viewType, - startImageRequest); - } else if (ContentType.isAudioType(contentType)) { - attachmentView = createAudioPreview(layoutInflater, attachmentData, parent, viewType); - } else if (ContentType.isVideoType(contentType)) { - attachmentView = createVideoPreview(layoutInflater, attachmentData, parent, viewType); - } else if (ContentType.isVCardType(contentType)) { - attachmentView = createVCardPreview(layoutInflater, attachmentData, parent, viewType); - } else { - Assert.fail("unsupported attachment type: " + contentType); - return null; - } - - // Some views have a caption, set the text/visibility if one exists - final TextView captionView = (TextView) attachmentView.findViewById(R.id.caption); - if (captionView != null) { - final String caption = attachmentData.getText(); - captionView.setVisibility(TextUtils.isEmpty(caption) ? View.GONE : View.VISIBLE); - captionView.setText(caption); - } - - if (attachmentView != null && clickListener != null) { - attachmentView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view); - clickListener.onAttachmentClick(attachmentData, bounds, - false /* longPress */); - } - }); - attachmentView.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(final View view) { - final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view); - return clickListener.onAttachmentClick(attachmentData, bounds, - true /* longPress */); - } - }); - } - return attachmentView; - } - - public static MultiAttachmentLayout createMultiplePreview(final Context context, - final OnAttachmentClickListener listener) { - final MultiAttachmentLayout multiAttachmentLayout = - new MultiAttachmentLayout(context, null); - final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - multiAttachmentLayout.setLayoutParams(layoutParams); - multiAttachmentLayout.setOnAttachmentClickListener(listener); - return multiAttachmentLayout; - } - - public static ImageRequestDescriptor getImageRequestDescriptorForAttachment( - final MessagePartData attachmentData, final int desiredWidth, final int desiredHeight) { - final Uri uri = attachmentData.getContentUri(); - final String contentType = attachmentData.getContentType(); - if (ContentType.isImageType(contentType)) { - final String filePath = UriUtil.getFilePathFromUri(uri); - if (filePath != null) { - return new FileImageRequestDescriptor(filePath, desiredWidth, desiredHeight, - attachmentData.getWidth(), attachmentData.getHeight(), - false /* canUseThumbnail */, true /* allowCompression */, - false /* isStatic */); - } else { - return new UriImageRequestDescriptor(uri, desiredWidth, desiredHeight, - attachmentData.getWidth(), attachmentData.getHeight(), - true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - } - } - return null; - } - - private static View createImagePreview(final LayoutInflater layoutInflater, - final MessagePartData attachmentData, final ViewGroup parent, - final int viewType, final boolean startImageRequest) { - int layoutId = R.layout.attachment_single_image; - switch (viewType) { - case AttachmentPreviewFactory.TYPE_SINGLE: - layoutId = R.layout.attachment_single_image; - break; - case AttachmentPreviewFactory.TYPE_MULTIPLE: - layoutId = R.layout.attachment_multiple_image; - break; - case AttachmentPreviewFactory.TYPE_CHOOSER_GRID: - layoutId = R.layout.attachment_chooser_image; - break; - default: - Assert.fail("unsupported attachment view type!"); - break; - } - final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */); - final AsyncImageView imageView = (AsyncImageView) view.findViewById( - R.id.attachment_image_view); - int maxWidth = imageView.getMaxWidth(); - int maxHeight = imageView.getMaxHeight(); - if (viewType == TYPE_CHOOSER_GRID) { - final Resources resources = layoutInflater.getContext().getResources(); - maxWidth = maxHeight = resources.getDimensionPixelSize( - R.dimen.attachment_grid_image_cell_size); - } - if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) { - maxWidth = ImageRequest.UNSPECIFIED_SIZE; - } - if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) { - maxHeight = ImageRequest.UNSPECIFIED_SIZE; - } - if (startImageRequest) { - imageView.setImageResourceId(getImageRequestDescriptorForAttachment(attachmentData, - maxWidth, maxHeight)); - } - imageView.setContentDescription( - parent.getResources().getString(R.string.message_image_content_description)); - return view; - } - - private static View createPendingAttachmentPreview(final LayoutInflater layoutInflater, - final ViewGroup parent, final PendingAttachmentData attachmentData) { - final View pendingItemView = layoutInflater.inflate(R.layout.attachment_pending_item, - parent, false); - final ImageView imageView = (ImageView) - pendingItemView.findViewById(R.id.pending_item_view); - final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams(); - final int defaultSize = layoutInflater.getContext().getResources().getDimensionPixelSize( - R.dimen.pending_attachment_size); - layoutParams.width = attachmentData.getWidth() == MessagePartData.UNSPECIFIED_SIZE ? - defaultSize : attachmentData.getWidth(); - layoutParams.height = attachmentData.getHeight() == MessagePartData.UNSPECIFIED_SIZE ? - defaultSize : attachmentData.getHeight(); - return pendingItemView; - } - - private static View createVCardPreview(final LayoutInflater layoutInflater, - final MessagePartData attachmentData, final ViewGroup parent, - final int viewType) { - int layoutId = R.layout.attachment_single_vcard; - switch (viewType) { - case AttachmentPreviewFactory.TYPE_SINGLE: - layoutId = R.layout.attachment_single_vcard; - break; - case AttachmentPreviewFactory.TYPE_MULTIPLE: - layoutId = R.layout.attachment_multiple_vcard; - break; - case AttachmentPreviewFactory.TYPE_CHOOSER_GRID: - layoutId = R.layout.attachment_chooser_vcard; - break; - default: - Assert.fail("unsupported attachment view type!"); - break; - } - final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */); - final PersonItemView vcardPreview = (PersonItemView) view.findViewById( - R.id.vcard_attachment_view); - vcardPreview.setAvatarOnly(viewType != AttachmentPreviewFactory.TYPE_SINGLE); - vcardPreview.bind(DataModel.get().createVCardContactItemData(layoutInflater.getContext(), - attachmentData)); - vcardPreview.setListener(new PersonItemViewListener() { - @Override - public void onPersonClicked(final PersonItemData data) { - Assert.isTrue(data instanceof VCardContactItemData); - final VCardContactItemData vCardData = (VCardContactItemData) data; - if (vCardData.hasValidVCard()) { - final Uri vCardUri = vCardData.getVCardUri(); - UIIntents.get().launchVCardDetailActivity(vcardPreview.getContext(), vCardUri); - } - } - - @Override - public boolean onPersonLongClicked(final PersonItemData data) { - return false; - } - }); - return view; - } - - private static View createAudioPreview(final LayoutInflater layoutInflater, - final MessagePartData attachmentData, final ViewGroup parent, - final int viewType) { - int layoutId = R.layout.attachment_single_audio; - switch (viewType) { - case AttachmentPreviewFactory.TYPE_SINGLE: - layoutId = R.layout.attachment_single_audio; - break; - case AttachmentPreviewFactory.TYPE_MULTIPLE: - layoutId = R.layout.attachment_multiple_audio; - break; - case AttachmentPreviewFactory.TYPE_CHOOSER_GRID: - layoutId = R.layout.attachment_chooser_audio; - break; - default: - Assert.fail("unsupported attachment view type!"); - break; - } - final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */); - final AudioAttachmentView audioView = (AudioAttachmentView) - view.findViewById(R.id.audio_attachment_view); - audioView.bindMessagePartData(attachmentData, false /* incoming */); - return view; - } - - private static View createVideoPreview(final LayoutInflater layoutInflater, - final MessagePartData attachmentData, final ViewGroup parent, - final int viewType) { - int layoutId = R.layout.attachment_single_video; - switch (viewType) { - case AttachmentPreviewFactory.TYPE_SINGLE: - layoutId = R.layout.attachment_single_video; - break; - case AttachmentPreviewFactory.TYPE_MULTIPLE: - layoutId = R.layout.attachment_multiple_video; - break; - case AttachmentPreviewFactory.TYPE_CHOOSER_GRID: - layoutId = R.layout.attachment_chooser_video; - break; - default: - Assert.fail("unsupported attachment view type!"); - break; - } - final VideoThumbnailView videoThumbnail = (VideoThumbnailView) layoutInflater.inflate( - layoutId, parent, false /* attachToRoot */); - videoThumbnail.setSource(attachmentData, false /* incomingMessage */); - return videoThumbnail; - } -} diff --git a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java b/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java deleted file mode 100644 index 724c4fe..0000000 --- a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.ViewSwitcher; - -import com.android.messaging.R; - -/** - * Shows a tinted play pause button. - */ -public class AudioAttachmentPlayPauseButton extends ViewSwitcher { - private ImageView mPlayButton; - private ImageView mPauseButton; - - private boolean mShowAsIncoming; - - public AudioAttachmentPlayPauseButton(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mPlayButton = (ImageView) findViewById(R.id.play_button); - mPauseButton = (ImageView) findViewById(R.id.pause_button); - updateAppearance(); - } - - public void setVisualStyle(final boolean showAsIncoming) { - if (mShowAsIncoming != showAsIncoming) { - mShowAsIncoming = showAsIncoming; - updateAppearance(); - } - } - - private void updateAppearance() { - mPlayButton.setImageDrawable(ConversationDrawables.get() - .getPlayButtonDrawable(mShowAsIncoming)); - mPauseButton.setImageDrawable(ConversationDrawables.get() - .getPauseButtonDrawable(mShowAsIncoming)); - } -} diff --git a/src/com/android/messaging/ui/AudioAttachmentView.java b/src/com/android/messaging/ui/AudioAttachmentView.java deleted file mode 100644 index bb649b0..0000000 --- a/src/com/android/messaging/ui/AudioAttachmentView.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.RectF; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.net.Uri; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.mediapicker.PausableChronometer; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; - -/** - * A reusable widget that hosts an audio player for audio attachment playback. This widget is used - * by both the media picker and the conversation message view to show audio attachments. - */ -public class AudioAttachmentView extends LinearLayout { - /** The normal layout mode where we have the play button, timer and progress bar */ - private static final int LAYOUT_MODE_NORMAL = 0; - - /** The compact layout mode with only the play button and the timer beneath it. Suitable - * for displaying in limited space such as multi-attachment layout */ - private static final int LAYOUT_MODE_COMPACT = 1; - - /** The sub-compact layout mode with only the play button. */ - private static final int LAYOUT_MODE_SUB_COMPACT = 2; - - private static final int PLAY_BUTTON = 0; - private static final int PAUSE_BUTTON = 1; - - private AudioAttachmentPlayPauseButton mPlayPauseButton; - private PausableChronometer mChronometer; - private AudioPlaybackProgressBar mProgressBar; - private MediaPlayer mMediaPlayer; - - private Uri mDataSourceUri; - - // The corner radius for drawing rounded corners. The default value is zero (no rounded corners) - private final int mCornerRadius; - private final Path mRoundedCornerClipPath; - private int mClipPathWidth; - private int mClipPathHeight; - - // Indicates whether the attachment view is to be styled as a part of an incoming message. - private boolean mShowAsIncoming; - - private boolean mPrepared; - private boolean mPlaybackFinished; - private final int mMode; - - public AudioAttachmentView(final Context context, final AttributeSet attrs) { - super(context, attrs); - final TypedArray typedAttributes = - context.obtainStyledAttributes(attrs, R.styleable.AudioAttachmentView); - mMode = typedAttributes.getInt(R.styleable.AudioAttachmentView_layoutMode, - LAYOUT_MODE_NORMAL); - final LayoutInflater inflater = LayoutInflater.from(getContext()); - inflater.inflate(R.layout.audio_attachment_view, this, true); - typedAttributes.recycle(); - - setWillNotDraw(mMode != LAYOUT_MODE_SUB_COMPACT); - mRoundedCornerClipPath = new Path(); - mCornerRadius = context.getResources().getDimensionPixelSize( - R.dimen.conversation_list_image_preview_corner_radius); - setContentDescription(context.getString(R.string.audio_attachment_content_description)); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mPlayPauseButton = (AudioAttachmentPlayPauseButton) findViewById(R.id.play_pause_button); - mChronometer = (PausableChronometer) findViewById(R.id.timer); - mProgressBar = (AudioPlaybackProgressBar) findViewById(R.id.progress); - mPlayPauseButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - setupMediaPlayer(); - if (mMediaPlayer != null && mPrepared) { - if (mMediaPlayer.isPlaying()) { - mMediaPlayer.pause(); - mChronometer.pause(); - mProgressBar.pause(); - } else { - playAudio(); - } - } - updatePlayPauseButtonState(); - } - }); - updatePlayPauseButtonState(); - initializeViewsForMode(); - } - - /** - * Bind the audio attachment view with a MessagePartData. - * @param incoming indicates whether the attachment view is to be styled as a part of an - * incoming message. - */ - public void bindMessagePartData(final MessagePartData messagePartData, - final boolean incoming) { - Assert.isTrue(messagePartData == null || - ContentType.isAudioType(messagePartData.getContentType())); - final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri(); - bind(contentUri, incoming); - } - - public void bind(final Uri dataSourceUri, final boolean incoming) { - final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString(); - final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString(); - mShowAsIncoming = incoming; - if (!TextUtils.equals(currentUriString, newUriString)) { - mDataSourceUri = dataSourceUri; - resetToZeroState(); - } - } - - private void playAudio() { - Assert.notNull(mMediaPlayer); - if (mPlaybackFinished) { - mMediaPlayer.seekTo(0); - mChronometer.restart(); - mProgressBar.restart(); - mPlaybackFinished = false; - } else { - mChronometer.resume(); - mProgressBar.resume(); - } - mMediaPlayer.start(); - } - - private void onAudioReplayError(final int what, final int extra, final Exception exception) { - if (exception == null) { - LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, what=" + what + - ", extra=" + extra); - } else { - LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, exception=" + exception); - } - UiUtils.showToastAtBottom(R.string.audio_recording_replay_failed); - releaseMediaPlayer(); - } - - private void setupMediaPlayer() { - Assert.notNull(mDataSourceUri); - if (mMediaPlayer == null) { - Assert.isTrue(!mPrepared); - mMediaPlayer = new MediaPlayer(); - try { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri); - mMediaPlayer.setOnCompletionListener(new OnCompletionListener() { - @Override - public void onCompletion(final MediaPlayer mp) { - updatePlayPauseButtonState(); - mChronometer.reset(); - mChronometer.setBase(SystemClock.elapsedRealtime() - - mMediaPlayer.getDuration()); - mProgressBar.reset(); - - mPlaybackFinished = true; - } - }); - - mMediaPlayer.setOnPreparedListener(new OnPreparedListener() { - @Override - public void onPrepared(final MediaPlayer mp) { - // Set base on the chronometer so we can show the full length of the audio. - mChronometer.setBase(SystemClock.elapsedRealtime() - - mMediaPlayer.getDuration()); - mProgressBar.setDuration(mMediaPlayer.getDuration()); - mMediaPlayer.seekTo(0); - mPrepared = true; - } - }); - - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - onAudioReplayError(what, extra, null); - return true; - } - }); - mMediaPlayer.prepareAsync(); - } catch (final Exception exception) { - onAudioReplayError(0, 0, exception); - releaseMediaPlayer(); - } - } - } - - private void releaseMediaPlayer() { - if (mMediaPlayer != null) { - mMediaPlayer.release(); - mMediaPlayer = null; - mPrepared = false; - mPlaybackFinished = false; - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - releaseMediaPlayer(); - } - - @Override - protected void onDraw(final Canvas canvas) { - if (mMode != LAYOUT_MODE_SUB_COMPACT) { - return; - } - - final int currentWidth = this.getWidth(); - final int currentHeight = this.getHeight(); - if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) { - final RectF rect = new RectF(0, 0, currentWidth, currentHeight); - mRoundedCornerClipPath.reset(); - mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius, - Path.Direction.CW); - mClipPathWidth = currentWidth; - mClipPathHeight = currentHeight; - } - - canvas.clipPath(mRoundedCornerClipPath); - super.onDraw(canvas); - } - - private void updatePlayPauseButtonState() { - if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) { - mPlayPauseButton.setDisplayedChild(PLAY_BUTTON); - } else { - mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON); - } - } - - private void resetToZeroState() { - // Release the media player so it may be set up with the new audio source. - releaseMediaPlayer(); - mChronometer.reset(); - mProgressBar.reset(); - updateVisualStyle(); - - if (mDataSourceUri != null) { - // Re-ensure the media player, so we can read the duration of the audio. - setupMediaPlayer(); - } - } - - private void updateVisualStyle() { - if (mMode == LAYOUT_MODE_SUB_COMPACT) { - // Sub-compact mode has static visual appearance already set up during initialization. - return; - } - - if (mShowAsIncoming) { - mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming)); - } else { - mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing)); - } - mProgressBar.setVisualStyle(mShowAsIncoming); - mPlayPauseButton.setVisualStyle(mShowAsIncoming); - updatePlayPauseButtonState(); - } - - private void initializeViewsForMode() { - switch (mMode) { - case LAYOUT_MODE_NORMAL: - setOrientation(HORIZONTAL); - mProgressBar.setVisibility(VISIBLE); - break; - - case LAYOUT_MODE_COMPACT: - setOrientation(VERTICAL); - mProgressBar.setVisibility(GONE); - ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0); - ((MarginLayoutParams) mChronometer.getLayoutParams()).setMargins(0, 0, 0, 0); - break; - - case LAYOUT_MODE_SUB_COMPACT: - setOrientation(VERTICAL); - mProgressBar.setVisibility(GONE); - mChronometer.setVisibility(GONE); - ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0); - final ImageView playButton = (ImageView) findViewById(R.id.play_button); - playButton.setImageDrawable( - getResources().getDrawable(R.drawable.ic_preview_play)); - final ImageView pauseButton = (ImageView) findViewById(R.id.pause_button); - pauseButton.setImageDrawable( - getResources().getDrawable(R.drawable.ic_preview_pause)); - break; - - default: - Assert.fail("Unsupported mode for AudioAttachmentView!"); - break; - } - } -} diff --git a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java b/src/com/android/messaging/ui/AudioPlaybackProgressBar.java deleted file mode 100644 index a5b3a57..0000000 --- a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.animation.ObjectAnimator; -import android.animation.TimeAnimator; -import android.animation.TimeAnimator.TimeListener; -import android.content.Context; -import android.graphics.drawable.ClipDrawable; -import android.graphics.drawable.Drawable; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.Gravity; -import android.widget.ProgressBar; - -/** - * Shows a styled progress bar that is synchronized with the playback state of an audio attachment. - */ -public class AudioPlaybackProgressBar extends ProgressBar implements PlaybackStateView { - private long mDurationInMillis; - private final TimeAnimator mUpdateAnimator; - private long mCumulativeTime = 0; - private long mCurrentPlayStartTime = 0; - private boolean mIncoming = false; - - public AudioPlaybackProgressBar(final Context context, final AttributeSet attrs) { - super(context, attrs); - - mUpdateAnimator = new TimeAnimator(); - mUpdateAnimator.setRepeatCount(ObjectAnimator.INFINITE); - mUpdateAnimator.setTimeListener(new TimeListener() { - @Override - public void onTimeUpdate(final TimeAnimator animation, final long totalTime, - final long deltaTime) { - int progress = 0; - if (mDurationInMillis > 0) { - progress = (int) (((mCumulativeTime + SystemClock.elapsedRealtime() - - mCurrentPlayStartTime) * 1.0f / mDurationInMillis) * 100); - progress = Math.max(Math.min(progress, 100), 0); - } - setProgress(progress); - } - }); - updateAppearance(); - } - - /** - * Sets the duration of the audio that's being played, in milliseconds. - */ - public void setDuration(final long durationInMillis) { - mDurationInMillis = durationInMillis; - } - - @Override - public void restart() { - reset(); - resume(); - } - - @Override - public void reset() { - stopUpdateTicks(); - setProgress(0); - mCumulativeTime = 0; - mCurrentPlayStartTime = 0; - } - - @Override - public void resume() { - mCurrentPlayStartTime = SystemClock.elapsedRealtime(); - startUpdateTicks(); - } - - @Override - public void pause() { - mCumulativeTime += SystemClock.elapsedRealtime() - mCurrentPlayStartTime; - stopUpdateTicks(); - } - - private void startUpdateTicks() { - if (!mUpdateAnimator.isStarted()) { - mUpdateAnimator.start(); - } - } - - private void stopUpdateTicks() { - if (mUpdateAnimator.isStarted()) { - mUpdateAnimator.end(); - } - } - - private void updateAppearance() { - final Drawable drawable = - ConversationDrawables.get().getAudioProgressDrawable(mIncoming); - final ClipDrawable clipDrawable = new ClipDrawable(drawable, Gravity.START, - ClipDrawable.HORIZONTAL); - setProgressDrawable(clipDrawable); - setBackground(ConversationDrawables.get() - .getAudioProgressBackgroundDrawable(mIncoming)); - } - - public void setVisualStyle(final boolean incoming) { - if (mIncoming != incoming) { - mIncoming = incoming; - updateAppearance(); - } - } -} diff --git a/src/com/android/messaging/ui/BaseBugleActivity.java b/src/com/android/messaging/ui/BaseBugleActivity.java deleted file mode 100644 index 1236282..0000000 --- a/src/com/android/messaging/ui/BaseBugleActivity.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; -import android.os.Bundle; - -import com.android.messaging.util.BugleActivityUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; - -/** - * Base class for app activities that would normally derive from Activity. Responsible for - * ensuring app requirements are met during onResume() - */ -public class BaseBugleActivity extends Activity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (UiUtils.redirectToPermissionCheckIfNeeded(this)) { - return; - } - } - - @Override - protected void onResume() { - super.onResume(); - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume"); - BugleActivityUtil.onActivityResume(this, BaseBugleActivity.this); - } - - @Override - protected void onPause() { - super.onPause(); - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause"); - } -} diff --git a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java b/src/com/android/messaging/ui/BaseBugleFragmentActivity.java deleted file mode 100644 index 947970f..0000000 --- a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; - -import com.android.messaging.util.BugleActivityUtil; -import com.android.messaging.util.LogUtil; - -/** - * Base class for app activities that would normally derive from FragmentActivity. Responsible for - * ensuring app requirements are met during onResume() - */ -public class BaseBugleFragmentActivity extends Activity { - @Override - protected void onResume() { - super.onResume(); - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume"); - // Ensure we have a sufficient version of Google Play Services, prompting for upgrade and - // disabling the data updates if we don't have the correct version. - BugleActivityUtil.onActivityResume(this, BaseBugleFragmentActivity.this); - } - - @Override - protected void onPause() { - super.onPause(); - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause"); - } -} diff --git a/src/com/android/messaging/ui/BasePagerViewHolder.java b/src/com/android/messaging/ui/BasePagerViewHolder.java deleted file mode 100644 index a82ecce..0000000 --- a/src/com/android/messaging/ui/BasePagerViewHolder.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.os.Parcelable; -import android.view.View; -import android.view.ViewGroup; - -/** - * The base pager view holder implementation that handles basic view creation/destruction logic, - * as well as logic to save/restore instance state that's persisted not only for activity - * reconstruction (e.g. during a configuration change), but also cases where the individual - * page view gets destroyed and recreated. - * - * To opt into saving/restoring instance state behavior for a particular page view, let the - * PageView implement PersistentInstanceState to save and restore instance states to/from a - * Parcelable. - */ -public abstract class BasePagerViewHolder implements PagerViewHolder { - protected View mView; - protected Parcelable mSavedState; - - /** - * This is called when the entire view pager is being torn down (due to configuration change - * for example) that will be restored later. - */ - @Override - public Parcelable saveState() { - savePendingState(); - return mSavedState; - } - - /** - * This is called when the view pager is being restored. - */ - @Override - public void restoreState(final Parcelable restoredState) { - if (restoredState != null) { - mSavedState = restoredState; - // If the view is already there, let it restore the state. Otherwise, it will be - // restored the next time the view gets created. - restorePendingState(); - } - } - - @Override - public void resetState() { - mSavedState = null; - if (mView != null && (mView instanceof PersistentInstanceState)) { - ((PersistentInstanceState) mView).resetState(); - } - } - - /** - * This is called when the view itself is being torn down. This may happen when the user - * has flipped to another page in the view pager, so we want to save the current state if - * possible. - */ - @Override - public View destroyView() { - savePendingState(); - final View retView = mView; - mView = null; - return retView; - } - - @Override - public View getView(ViewGroup container) { - if (mView == null) { - mView = createView(container); - // When initially created, check if the view has any saved state. If so restore it. - restorePendingState(); - } - return mView; - } - - private void savePendingState() { - if (mView != null && (mView instanceof PersistentInstanceState)) { - mSavedState = ((PersistentInstanceState) mView).saveState(); - } - } - - private void restorePendingState() { - if (mView != null && (mView instanceof PersistentInstanceState) && (mSavedState != null)) { - ((PersistentInstanceState) mView).restoreState(mSavedState); - } - } - - /** - * Create and initialize a new page view. - */ - protected abstract View createView(ViewGroup container); -} diff --git a/src/com/android/messaging/ui/BlockedParticipantListItemView.java b/src/com/android/messaging/ui/BlockedParticipantListItemView.java deleted file mode 100644 index 2ccdebf..0000000 --- a/src/com/android/messaging/ui/BlockedParticipantListItemView.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantListItemData; - -/** - * View for individual participant in blocked participants list. - * - * Unblocks participant when clicked. - */ -public class BlockedParticipantListItemView extends LinearLayout { - private TextView mNameTextView; - private ContactIconView mContactIconView; - private ParticipantListItemData mData; - - public BlockedParticipantListItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - mNameTextView = (TextView) findViewById(R.id.name); - mContactIconView = (ContactIconView) findViewById(R.id.contact_icon); - setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mData.unblock(getContext()); - } - }); - } - - public void bind(final ParticipantListItemData data) { - mData = data; - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - mNameTextView.setText(bidiFormatter.unicodeWrap( - data.getDisplayName(), TextDirectionHeuristicsCompat.LTR)); - mContactIconView.setImageResourceUri(data.getAvatarUri(), data.getContactId(), - data.getLookupKey(), data.getNormalizedDestination()); - mNameTextView.setText(data.getDisplayName()); - } -} diff --git a/src/com/android/messaging/ui/BlockedParticipantsActivity.java b/src/com/android/messaging/ui/BlockedParticipantsActivity.java deleted file mode 100644 index b740264..0000000 --- a/src/com/android/messaging/ui/BlockedParticipantsActivity.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Fragment; -import android.os.Bundle; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; - -/** - * Show a list of currently blocked participants. - */ -public class BlockedParticipantsActivity extends BugleActionBarActivity { - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.blocked_participants_activity); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - Assert.isTrue(fragment instanceof BlockedParticipantsFragment); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // Treat the home press as back press so that when we go back to - // ConversationActivity, it doesn't lose its original intent (conversation id etc.) - onBackPressed(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/src/com/android/messaging/ui/BlockedParticipantsFragment.java b/src/com/android/messaging/ui/BlockedParticipantsFragment.java deleted file mode 100644 index 3ab54ab..0000000 --- a/src/com/android/messaging/ui/BlockedParticipantsFragment.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.BlockedParticipantsData; -import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.util.Assert; - -/** - * Show a list of currently blocked participants. - */ -public class BlockedParticipantsFragment extends Fragment - implements BlockedParticipantsDataListener { - private ListView mListView; - private BlockedParticipantListAdapter mAdapter; - private final Binding mBinding = - BindingBase.createBinding(this); - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = - inflater.inflate(R.layout.blocked_participants_fragment, container, false); - mListView = (ListView) view.findViewById(android.R.id.list); - mAdapter = new BlockedParticipantListAdapter(getActivity(), null); - mListView.setAdapter(mAdapter); - mBinding.bind(DataModel.get().createBlockedParticipantsData(getActivity(), this)); - mBinding.getData().init(getLoaderManager(), mBinding); - return view; - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mBinding.unbind(); - } - - /** - * An adapter to display ParticipantListItemView based on ParticipantData. - */ - private class BlockedParticipantListAdapter extends CursorAdapter { - public BlockedParticipantListAdapter(final Context context, final Cursor cursor) { - super(context, cursor, 0); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return LayoutInflater.from(context) - .inflate(R.layout.blocked_participant_list_item_view, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - Assert.isTrue(view instanceof BlockedParticipantListItemView); - ((BlockedParticipantListItemView) view).bind( - mBinding.getData().createParticipantListItemData(cursor)); - } - } - - @Override - public void onBlockedParticipantsCursorUpdated(Cursor cursor) { - mAdapter.swapCursor(cursor); - } -} diff --git a/src/com/android/messaging/ui/BugleActionBarActivity.java b/src/com/android/messaging/ui/BugleActionBarActivity.java deleted file mode 100644 index 80dabb8..0000000 --- a/src/com/android/messaging/ui/BugleActionBarActivity.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.util.BugleActivityUtil; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; - -import java.util.HashSet; -import java.util.Set; - -/** - * Base class for app activities that use an action bar. Responsible for logging/telemetry/other - * needs that will be common for all activities. We can break out the common code if/when we need - * a version that doesn't use an actionbar. - */ -public class BugleActionBarActivity extends ActionBarActivity implements ImeUtil.ImeStateHost { - // Tracks the list of observers opting in for IME state change. - private final Set mImeStateObservers = new HashSet<>(); - - // Tracks the soft keyboard display state - private boolean mImeOpen; - - // The ActionMode that represents the modal contextual action bar, using our own implementation - // rather than the built in contextual action bar to reduce jank - private CustomActionMode mActionMode; - - // The menu for the action bar - private Menu mActionBarMenu; - - // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the - // device - private int mLastScreenHeight; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (UiUtils.redirectToPermissionCheckIfNeeded(this)) { - return; - } - - mLastScreenHeight = getResources().getDisplayMetrics().heightPixels; - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate"); - } - } - - @Override - protected void onStart() { - super.onStart(); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart"); - } - } - - @Override - protected void onRestart() { - super.onStop(); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart"); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume"); - } - BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this); - } - - @Override - protected void onPause() { - super.onPause(); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause"); - } - } - - @Override - protected void onStop() { - super.onStop(); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop"); - } - } - - private boolean mDestroyed; - - @Override - protected void onDestroy() { - super.onDestroy(); - mDestroyed = true; - } - - public boolean getIsDestroyed() { - return mDestroyed; - } - - @Override - public void onDisplayHeightChanged(final int heightSpecification) { - int screenHeight = getResources().getDisplayMetrics().heightPixels; - - if (screenHeight != mLastScreenHeight) { - // Appears to be an orientation change, don't fire ime updates - mLastScreenHeight = screenHeight; - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " + - " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight + - " Skipped, appears to be orientation change."); - return; - } - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && actionBar.isShowing()) { - screenHeight -= actionBar.getHeight(); - } - final int height = View.MeasureSpec.getSize(heightSpecification); - - final boolean imeWasOpen = mImeOpen; - mImeOpen = screenHeight - height > 100; - - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " + - "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " + - screenHeight + " height: " + height); - } - - if (imeWasOpen != mImeOpen) { - for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) { - observer.onImeStateChanged(mImeOpen); - } - } - } - - @Override - public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) { - mImeStateObservers.add(observer); - } - - @Override - public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) { - mImeStateObservers.remove(observer); - } - - @Override - public boolean isImeOpen() { - return mImeOpen; - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - mActionBarMenu = menu; - if (mActionMode != null && - mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) { - return true; - } - return false; - } - - @Override - public boolean onPrepareOptionsMenu(final Menu menu) { - mActionBarMenu = menu; - if (mActionMode != null && - mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) { - return true; - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem menuItem) { - if (mActionMode != null && - mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) { - return true; - } - - switch (menuItem.getItemId()) { - case android.R.id.home: - if (mActionMode != null) { - dismissActionMode(); - return true; - } - } - return super.onOptionsItemSelected(menuItem); - } - - @Override - public ActionMode startActionMode(final ActionMode.Callback callback) { - mActionMode = new CustomActionMode(callback); - supportInvalidateOptionsMenu(); - invalidateActionBar(); - return mActionMode; - } - - public void dismissActionMode() { - if (mActionMode != null) { - mActionMode.finish(); - mActionMode = null; - invalidateActionBar(); - } - } - - public ActionMode getActionMode() { - return mActionMode; - } - - protected ActionMode.Callback getActionModeCallback() { - if (mActionMode == null) { - return null; - } - - return mActionMode.getCallback(); - } - - /** - * Receives and handles action bar invalidation request from sub-components of this activity. - * - *

Normally actions have sole control over the action bar, but in order to support seamless - * transitions for components such as the full screen media picker, we have to let it take over - * the action bar and then restore its state afterwards

- * - *

If a fragment does anything that may change the action bar, it should call this method - * and then it is this method's responsibility to figure out which component "controls" the - * action bar and delegate the updating of the action bar to that component

- */ - public final void invalidateActionBar() { - if (mActionMode != null) { - mActionMode.updateActionBar(getSupportActionBar()); - } else { - updateActionBar(getSupportActionBar()); - } - } - - protected void updateActionBar(final ActionBar actionBar) { - actionBar.setHomeAsUpIndicator(null); - } - - /** - * Custom ActionMode implementation which allows us to just replace the contents of the main - * action bar rather than overlay over it - */ - private class CustomActionMode extends ActionMode { - private CharSequence mTitle; - private CharSequence mSubtitle; - private View mCustomView; - private final Callback mCallback; - - public CustomActionMode(final Callback callback) { - mCallback = callback; - } - - @Override - public void setTitle(final CharSequence title) { - mTitle = title; - } - - @Override - public void setTitle(final int resId) { - mTitle = getResources().getString(resId); - } - - @Override - public void setSubtitle(final CharSequence subtitle) { - mSubtitle = subtitle; - } - - @Override - public void setSubtitle(final int resId) { - mSubtitle = getResources().getString(resId); - } - - @Override - public void setCustomView(final View view) { - mCustomView = view; - } - - @Override - public void invalidate() { - invalidateActionBar(); - } - - @Override - public void finish() { - mActionMode = null; - mCallback.onDestroyActionMode(this); - supportInvalidateOptionsMenu(); - invalidateActionBar(); - } - - @Override - public Menu getMenu() { - return mActionBarMenu; - } - - @Override - public CharSequence getTitle() { - return mTitle; - } - - @Override - public CharSequence getSubtitle() { - return mSubtitle; - } - - @Override - public View getCustomView() { - return mCustomView; - } - - @Override - public MenuInflater getMenuInflater() { - return BugleActionBarActivity.this.getMenuInflater(); - } - - public Callback getCallback() { - return mCallback; - } - - public void updateActionBar(final ActionBar actionBar) { - actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowCustomEnabled(false); - mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu); - actionBar.setBackgroundDrawable(new ColorDrawable( - getResources().getColor(R.color.contextual_action_bar_background_color))); - actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark); - actionBar.show(); - } - } -} diff --git a/src/com/android/messaging/ui/BugleAnimationTags.java b/src/com/android/messaging/ui/BugleAnimationTags.java deleted file mode 100644 index b141f5b..0000000 --- a/src/com/android/messaging/ui/BugleAnimationTags.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - - -/** - * Defines a list of animation tags used as android:transitionName properties used for L's - * hero transitions. - */ -public class BugleAnimationTags { - /** - * The tag for the FAB in the conversation list activity. - */ - public static final String TAG_FABICON = "bugle:fabicon"; - - /** - * The tag for the content view of a conversation list item view. - */ - public static final String TAG_CLIVCONTENT = "bugle:clivcontent"; - - /** - * The tag for the action bar. - */ - public static final String TAG_ACTIONBAR = "bugle:actionbar"; -} diff --git a/src/com/android/messaging/ui/ClassZeroActivity.java b/src/com/android/messaging/ui/ClassZeroActivity.java deleted file mode 100644 index 129ec19..0000000 --- a/src/com/android/messaging/ui/ClassZeroActivity.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ContentValues; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.provider.Telephony.Sms; -import android.text.TextUtils; -import android.util.Log; -import android.view.Window; - -import com.android.messaging.R; -import com.android.messaging.datamodel.action.ReceiveSmsMessageAction; -import com.android.messaging.util.Assert; - -import java.util.ArrayList; - -/** - * Display a class-zero SMS message to the user. Wait for the user to dismiss - * it. - */ -public class ClassZeroActivity extends Activity { - private static final boolean VERBOSE = false; - private static final String TAG = "display_00"; - private static final int ON_AUTO_SAVE = 1; - - /** Default timer to dismiss the dialog. */ - private static final long DEFAULT_TIMER = 5 * 60 * 1000; - - /** To remember the exact time when the timer should fire. */ - private static final String TIMER_FIRE = "timer_fire"; - - private ContentValues mMessageValues = null; - - /** Is the message read. */ - private boolean mRead = false; - - /** The timer to dismiss the dialog automatically. */ - private long mTimerSet = 0; - private AlertDialog mDialog = null; - - private ArrayList mMessageQueue = null; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(final Message msg) { - // Do not handle an invalid message. - if (msg.what == ON_AUTO_SAVE) { - mRead = false; - mDialog.dismiss(); - saveMessage(); - processNextMessage(); - } - } - }; - - private boolean queueMsgFromIntent(final Intent msgIntent) { - final ContentValues messageValues = - msgIntent.getParcelableExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_VALUES); - // that takes the format argument is a hidden API right now. - final String message = messageValues.getAsString(Sms.BODY); - if (TextUtils.isEmpty(message)) { - if (mMessageQueue.size() == 0) { - finish(); - } - return false; - } - mMessageQueue.add(messageValues); - return true; - } - - private void processNextMessage() { - if (mMessageQueue.size() > 0) { - mMessageQueue.remove(0); - } - if (mMessageQueue.size() == 0) { - finish(); - } else { - displayZeroMessage(mMessageQueue.get(0)); - } - } - - private void saveMessage() { - mMessageValues.put(Sms.Inbox.READ, mRead ? Integer.valueOf(1) : Integer.valueOf(0)); - final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(mMessageValues); - action.start(); - } - - @Override - protected void onCreate(final Bundle icicle) { - super.onCreate(icicle); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - if (mMessageQueue == null) { - mMessageQueue = new ArrayList(); - } - if (!queueMsgFromIntent(getIntent())) { - return; - } - Assert.isTrue(mMessageQueue.size() == 1); - if (mMessageQueue.size() == 1) { - displayZeroMessage(mMessageQueue.get(0)); - } - - if (icicle != null) { - mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet); - } - } - - private void displayZeroMessage(final ContentValues messageValues) { - /* This'll be used by the save action */ - mMessageValues = messageValues; - final String message = messageValues.getAsString(Sms.BODY);; - - mDialog = new AlertDialog.Builder(this).setMessage(message) - .setPositiveButton(R.string.save, mSaveListener) - .setNegativeButton(android.R.string.cancel, mCancelListener) - .setTitle(R.string.class_0_message_activity) - .setCancelable(false).show(); - final long now = SystemClock.uptimeMillis(); - mTimerSet = now + DEFAULT_TIMER; - } - - @Override - protected void onNewIntent(final Intent msgIntent) { - // Running with another visible message, queue this one - queueMsgFromIntent(msgIntent); - } - - @Override - protected void onStart() { - super.onStart(); - final long now = SystemClock.uptimeMillis(); - if (mTimerSet <= now) { - // Save the message if the timer already expired. - mHandler.sendEmptyMessage(ON_AUTO_SAVE); - } else { - mHandler.sendEmptyMessageAtTime(ON_AUTO_SAVE, mTimerSet); - if (VERBOSE) { - Log.d(TAG, "onRestart time = " + Long.toString(mTimerSet) + " " - + this.toString()); - } - } - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putLong(TIMER_FIRE, mTimerSet); - if (VERBOSE) { - Log.d(TAG, "onSaveInstanceState time = " + Long.toString(mTimerSet) - + " " + this.toString()); - } - } - - @Override - protected void onStop() { - super.onStop(); - mHandler.removeMessages(ON_AUTO_SAVE); - if (VERBOSE) { - Log.d(TAG, "onStop time = " + Long.toString(mTimerSet) - + " " + this.toString()); - } - } - - private final OnClickListener mCancelListener = new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int whichButton) { - dialog.dismiss(); - processNextMessage(); - } - }; - - private final OnClickListener mSaveListener = new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int whichButton) { - mRead = true; - saveMessage(); - dialog.dismiss(); - processNextMessage(); - } - }; -} diff --git a/src/com/android/messaging/ui/CompositeAdapter.java b/src/com/android/messaging/ui/CompositeAdapter.java deleted file mode 100644 index 620e511..0000000 --- a/src/com/android/messaging/ui/CompositeAdapter.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.database.DataSetObserver; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -/** - * A general purpose adapter that composes one or more other adapters. It - * appends them in the order they are added. - */ -public class CompositeAdapter extends BaseAdapter { - - private static final int INITIAL_CAPACITY = 2; - - public static class Partition { - boolean mShowIfEmpty; - boolean mHasHeader; - BaseAdapter mAdapter; - - public Partition(final boolean showIfEmpty, final boolean hasHeader, - final BaseAdapter adapter) { - this.mShowIfEmpty = showIfEmpty; - this.mHasHeader = hasHeader; - this.mAdapter = adapter; - } - - /** - * True if the directory should be shown even if no contacts are found. - */ - public boolean showIfEmpty() { - return mShowIfEmpty; - } - - public boolean hasHeader() { - return mHasHeader; - } - - public int getCount() { - int count = mAdapter.getCount(); - if (mHasHeader && (count != 0 || mShowIfEmpty)) { - count++; - } - return count; - } - - public BaseAdapter getAdapter() { - return mAdapter; - } - - public View getHeaderView(final View convertView, final ViewGroup parentView) { - return null; - } - - public void close() { - // do nothing in base class. - } - } - - private class Observer extends DataSetObserver { - @Override - public void onChanged() { - CompositeAdapter.this.notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - CompositeAdapter.this.notifyDataSetInvalidated(); - } - }; - - protected final Context mContext; - private Partition[] mPartitions; - private int mSize = 0; - private int mCount = 0; - private boolean mCacheValid = true; - private final Observer mObserver; - - public CompositeAdapter(final Context context) { - mContext = context; - mObserver = new Observer(); - mPartitions = new Partition[INITIAL_CAPACITY]; - } - - public Context getContext() { - return mContext; - } - - public void addPartition(final Partition partition) { - if (mSize >= mPartitions.length) { - final int newCapacity = mSize + 2; - final Partition[] newAdapters = new Partition[newCapacity]; - System.arraycopy(mPartitions, 0, newAdapters, 0, mSize); - mPartitions = newAdapters; - } - mPartitions[mSize++] = partition; - partition.getAdapter().registerDataSetObserver(mObserver); - invalidate(); - notifyDataSetChanged(); - } - - public void removePartition(final int index) { - final Partition partition = mPartitions[index]; - partition.close(); - System.arraycopy(mPartitions, index + 1, mPartitions, index, - mSize - index - 1); - mSize--; - partition.getAdapter().unregisterDataSetObserver(mObserver); - invalidate(); - notifyDataSetChanged(); - } - - public void clearPartitions() { - for (int i = 0; i < mSize; i++) { - final Partition partition = mPartitions[i]; - partition.close(); - partition.getAdapter().unregisterDataSetObserver(mObserver); - } - invalidate(); - notifyDataSetChanged(); - } - - public Partition getPartition(final int index) { - return mPartitions[index]; - } - - public int getPartitionAtPosition(final int position) { - ensureCacheValid(); - int start = 0; - for (int i = 0; i < mSize; i++) { - final int end = start + mPartitions[i].getCount(); - if (position >= start && position < end) { - int offset = position - start; - if (mPartitions[i].hasHeader() && - (mPartitions[i].getCount() > 0 || mPartitions[i].showIfEmpty())) { - offset--; - } - if (offset == -1) { - return -1; - } - return i; - } - start = end; - } - return mSize - 1; - } - - public int getPartitionCount() { - return mSize; - } - - public void invalidate() { - mCacheValid = false; - } - - private void ensureCacheValid() { - if (mCacheValid) { - return; - } - mCount = 0; - for (int i = 0; i < mSize; i++) { - mCount += mPartitions[i].getCount(); - } - } - - @Override - public int getCount() { - ensureCacheValid(); - return mCount; - } - - public int getCount(final int index) { - ensureCacheValid(); - return mPartitions[index].getCount(); - } - - @Override - public Object getItem(final int position) { - ensureCacheValid(); - int start = 0; - for (int i = 0; i < mSize; i++) { - final int end = start + mPartitions[i].getCount(); - if (position >= start && position < end) { - final int offset = position - start; - final Partition partition = mPartitions[i]; - if (partition.hasHeader() && offset == 0 && - (partition.getCount() > 0 || partition.showIfEmpty())) { - // This is the header - return null; - } - return mPartitions[i].getAdapter().getItem(offset); - } - start = end; - } - - return null; - } - - @Override - public long getItemId(final int position) { - ensureCacheValid(); - int start = 0; - for (int i = 0; i < mSize; i++) { - final int end = start + mPartitions[i].getCount(); - if (position >= start && position < end) { - final int offset = position - start; - final Partition partition = mPartitions[i]; - if (partition.hasHeader() && offset == 0 && - (partition.getCount() > 0 || partition.showIfEmpty())) { - // Header - return 0; - } - return mPartitions[i].getAdapter().getItemId(offset); - } - start = end; - } - - return 0; - } - - @Override - public boolean isEnabled(int position) { - ensureCacheValid(); - int start = 0; - for (int i = 0; i < mSize; i++) { - final int end = start + mPartitions[i].getCount(); - if (position >= start && position < end) { - final int offset = position - start; - final Partition partition = mPartitions[i]; - if (partition.hasHeader() && offset == 0 && - (partition.getCount() > 0 || partition.showIfEmpty())) { - // This is the header - return false; - } - return true; - } - start = end; - } - return true; - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parentView) { - ensureCacheValid(); - int start = 0; - for (int i = 0; i < mSize; i++) { - final Partition partition = mPartitions[i]; - final int end = start + partition.getCount(); - if (position >= start && position < end) { - int offset = position - start; - View view; - if (partition.hasHeader() && - (partition.getCount() > 0 || partition.showIfEmpty())) { - offset = offset - 1; - } - if (offset == -1) { - view = partition.getHeaderView(convertView, parentView); - } else { - view = partition.getAdapter().getView(offset, convertView, parentView); - } - if (view == null) { - throw new NullPointerException("View should not be null, partition: " + i - + " position: " + offset); - } - return view; - } - start = end; - } - - throw new ArrayIndexOutOfBoundsException(position); - } -} diff --git a/src/com/android/messaging/ui/ContactIconView.java b/src/com/android/messaging/ui/ContactIconView.java deleted file mode 100644 index 44983ab..0000000 --- a/src/com/android/messaging/ui/ContactIconView.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.net.Uri; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor; -import com.android.messaging.datamodel.media.AvatarRequestDescriptor; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ContactUtil; - -/** - * A view used to render contact icons. This class derives from AsyncImageView, so it loads contact - * icons from MediaResourceManager, and it handles more rendering logic than an AsyncImageView - * (draws a circular bitmap). - */ -public class ContactIconView extends AsyncImageView { - private static final int NORMAL_ICON_SIZE_ID = 0; - private static final int LARGE_ICON_SIZE_ID = 1; - private static final int SMALL_ICON_SIZE_ID = 2; - - protected final int mIconSize; - private final int mColorPressedId; - - private long mContactId; - private String mContactLookupKey; - private String mNormalizedDestination; - private Uri mAvatarUri; - private boolean mDisableClickHandler; - - public ContactIconView(final Context context, final AttributeSet attrs) { - super(context, attrs); - - final Resources resources = context.getResources(); - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactIconView); - - final int iconSizeId = a.getInt(R.styleable.ContactIconView_iconSize, 0); - switch (iconSizeId) { - case NORMAL_ICON_SIZE_ID: - mIconSize = (int) resources.getDimension( - R.dimen.contact_icon_view_normal_size); - break; - case LARGE_ICON_SIZE_ID: - mIconSize = (int) resources.getDimension( - R.dimen.contact_icon_view_large_size); - break; - case SMALL_ICON_SIZE_ID: - mIconSize = (int) resources.getDimension( - R.dimen.contact_icon_view_small_size); - break; - default: - // For the compiler, something has to be set even with the assert. - mIconSize = 0; - Assert.fail("Unsupported ContactIconView icon size attribute"); - } - mColorPressedId = resources.getColor(R.color.contact_avatar_pressed_color); - - setImage(null); - a.recycle(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - setColorFilter(mColorPressedId); - } else { - clearColorFilter(); - } - return super.onTouchEvent(event); - } - - /** - * Method which allows the automatic hookup of a click handler when the Uri is changed - */ - public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) { - mDisableClickHandler = isHandlerDisabled; - setOnClickListener(null); - setClickable(false); - } - - /** - * A convenience method that sets the URI of the contact icon by creating a new image request. - */ - public void setImageResourceUri(final Uri uri) { - setImageResourceUri(uri, 0, null, null); - } - - public void setImageResourceUri(final Uri uri, final long contactId, - final String contactLookupKey, final String normalizedDestination) { - if (uri == null) { - setImageResourceId(null); - } else { - final String avatarType = AvatarUriUtil.getAvatarType(uri); - if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) { - setImageResourceId(new AvatarGroupRequestDescriptor(uri, mIconSize, mIconSize)); - } else { - setImageResourceId(new AvatarRequestDescriptor(uri, mIconSize, mIconSize)); - } - } - - mContactId = contactId; - mContactLookupKey = contactLookupKey; - mNormalizedDestination = normalizedDestination; - mAvatarUri = uri; - - maybeInitializeOnClickListener(); - } - - protected void maybeInitializeOnClickListener() { - if ((mContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED - && !TextUtils.isEmpty(mContactLookupKey)) || - !TextUtils.isEmpty(mNormalizedDestination)) { - if (!mDisableClickHandler) { - setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - ContactUtil.showOrAddContact(view, mContactId, mContactLookupKey, - mAvatarUri, mNormalizedDestination); - } - }); - } - } else { - // This should happen when the phone number is not in the user's contacts or it is a - // group conversation, group conversations don't have contact phone numbers. If this - // is the case then absorb the click to prevent propagation. - setOnClickListener(null); - } - } -} diff --git a/src/com/android/messaging/ui/ConversationDrawables.java b/src/com/android/messaging/ui/ConversationDrawables.java deleted file mode 100644 index cf858e2..0000000 --- a/src/com/android/messaging/ui/ConversationDrawables.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.util.ImageUtils; - -/** - * A singleton cache that holds tinted drawable resources for displaying messages, such as - * message bubbles, audio attachments etc. - */ -public class ConversationDrawables { - private static ConversationDrawables sInstance; - - // Cache the color filtered bubble drawables so that we don't need to create a - // new one for each ConversationMessageView. - private Drawable mIncomingBubbleDrawable; - private Drawable mOutgoingBubbleDrawable; - private Drawable mIncomingErrorBubbleDrawable; - private Drawable mIncomingBubbleNoArrowDrawable; - private Drawable mOutgoingBubbleNoArrowDrawable; - private Drawable mAudioPlayButtonDrawable; - private Drawable mAudioPauseButtonDrawable; - private Drawable mIncomingAudioProgressBackgroundDrawable; - private Drawable mOutgoingAudioProgressBackgroundDrawable; - private Drawable mAudioProgressForegroundDrawable; - private Drawable mFastScrollThumbDrawable; - private Drawable mFastScrollThumbPressedDrawable; - private Drawable mFastScrollPreviewDrawableLeft; - private Drawable mFastScrollPreviewDrawableRight; - private final Context mContext; - private int mOutgoingBubbleColor; - private int mIncomingErrorBubbleColor; - private int mIncomingAudioButtonColor; - private int mSelectedBubbleColor; - private int mThemeColor; - - public static ConversationDrawables get() { - if (sInstance == null) { - sInstance = new ConversationDrawables(Factory.get().getApplicationContext()); - } - return sInstance; - } - - private ConversationDrawables(final Context context) { - mContext = context; - // Pre-create all the drawables. - updateDrawables(); - } - - public int getConversationThemeColor() { - return mThemeColor; - } - - public void updateDrawables() { - final Resources resources = mContext.getResources(); - - mIncomingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_incoming); - mIncomingBubbleNoArrowDrawable = - resources.getDrawable(R.drawable.message_bubble_incoming_no_arrow); - mIncomingErrorBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_error); - mOutgoingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_outgoing); - mOutgoingBubbleNoArrowDrawable = - resources.getDrawable(R.drawable.message_bubble_outgoing_no_arrow); - mAudioPlayButtonDrawable = resources.getDrawable(R.drawable.ic_audio_play); - mAudioPauseButtonDrawable = resources.getDrawable(R.drawable.ic_audio_pause); - mIncomingAudioProgressBackgroundDrawable = - resources.getDrawable(R.drawable.audio_progress_bar_background_incoming); - mOutgoingAudioProgressBackgroundDrawable = - resources.getDrawable(R.drawable.audio_progress_bar_background_outgoing); - mAudioProgressForegroundDrawable = - resources.getDrawable(R.drawable.audio_progress_bar_progress); - mFastScrollThumbDrawable = resources.getDrawable(R.drawable.fastscroll_thumb); - mFastScrollThumbPressedDrawable = - resources.getDrawable(R.drawable.fastscroll_thumb_pressed); - mFastScrollPreviewDrawableLeft = - resources.getDrawable(R.drawable.fastscroll_preview_left); - mFastScrollPreviewDrawableRight = - resources.getDrawable(R.drawable.fastscroll_preview_right); - mOutgoingBubbleColor = resources.getColor(R.color.message_bubble_color_outgoing); - mIncomingErrorBubbleColor = - resources.getColor(R.color.message_error_bubble_color_incoming); - mIncomingAudioButtonColor = - resources.getColor(R.color.message_audio_button_color_incoming); - mSelectedBubbleColor = resources.getColor(R.color.message_bubble_color_selected); - mThemeColor = resources.getColor(R.color.primary_color); - } - - public Drawable getBubbleDrawable(final boolean selected, final boolean incoming, - final boolean needArrow, final boolean isError) { - final Drawable protoDrawable; - if (needArrow) { - if (incoming) { - protoDrawable = isError && !selected ? - mIncomingErrorBubbleDrawable : mIncomingBubbleDrawable; - } else { - protoDrawable = mOutgoingBubbleDrawable; - } - } else if (incoming) { - protoDrawable = mIncomingBubbleNoArrowDrawable; - } else { - protoDrawable = mOutgoingBubbleNoArrowDrawable; - } - - int color; - if (selected) { - color = mSelectedBubbleColor; - } else if (incoming) { - if (isError) { - color = mIncomingErrorBubbleColor; - } else { - color = mThemeColor; - } - } else { - color = mOutgoingBubbleColor; - } - - return ImageUtils.getTintedDrawable(mContext, protoDrawable, color); - } - - private int getAudioButtonColor(final boolean incoming) { - return incoming ? mIncomingAudioButtonColor : mThemeColor; - } - - public Drawable getPlayButtonDrawable(final boolean incoming) { - return ImageUtils.getTintedDrawable( - mContext, mAudioPlayButtonDrawable, getAudioButtonColor(incoming)); - } - - public Drawable getPauseButtonDrawable(final boolean incoming) { - return ImageUtils.getTintedDrawable( - mContext, mAudioPauseButtonDrawable, getAudioButtonColor(incoming)); - } - - public Drawable getAudioProgressDrawable(final boolean incoming) { - return ImageUtils.getTintedDrawable( - mContext, mAudioProgressForegroundDrawable, getAudioButtonColor(incoming)); - } - - public Drawable getAudioProgressBackgroundDrawable(final boolean incoming) { - return incoming ? mIncomingAudioProgressBackgroundDrawable : - mOutgoingAudioProgressBackgroundDrawable; - } - - public Drawable getFastScrollThumbDrawable(final boolean pressed) { - if (pressed) { - return ImageUtils.getTintedDrawable(mContext, mFastScrollThumbPressedDrawable, - mThemeColor); - } else { - return mFastScrollThumbDrawable; - } - } - - public Drawable getFastScrollPreviewDrawable(boolean positionRight) { - Drawable protoDrawable = positionRight ? mFastScrollPreviewDrawableRight : - mFastScrollPreviewDrawableLeft; - return ImageUtils.getTintedDrawable(mContext, protoDrawable, mThemeColor); - } -} diff --git a/src/com/android/messaging/ui/CursorRecyclerAdapter.java b/src/com/android/messaging/ui/CursorRecyclerAdapter.java deleted file mode 100644 index f1a7b7d..0000000 --- a/src/com/android/messaging/ui/CursorRecyclerAdapter.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.os.Handler; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.ViewGroup; -import android.widget.FilterQueryProvider; - -/** - * Copy of CursorAdapter suited for RecyclerView. - * - * TODO: BUG 16327984. Replace this with a framework supported CursorAdapter for - * RecyclerView when one is available. - */ -public abstract class CursorRecyclerAdapter - extends RecyclerView.Adapter { - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected boolean mDataValid; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected boolean mAutoRequery; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected Cursor mCursor; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected Context mContext; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected int mRowIDColumn; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected ChangeObserver mChangeObserver; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected DataSetObserver mDataSetObserver; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected FilterQueryProvider mFilterQueryProvider; - - /** - * If set the adapter will call requery() on the cursor whenever a content change - * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}. - * - * @deprecated This option is discouraged, as it results in Cursor queries - * being performed on the application's UI thread and thus can cause poor - * responsiveness or even Application Not Responding errors. As an alternative, - * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}. - */ - @Deprecated - public static final int FLAG_AUTO_REQUERY = 0x01; - - /** - * If set the adapter will register a content observer on the cursor and will call - * {@link #onContentChanged()} when a notification comes in. Be careful when - * using this flag: you will need to unset the current Cursor from the adapter - * to avoid leaks due to its registered observers. This flag is not needed - * when using a CursorAdapter with a - * {@link android.content.CursorLoader}. - */ - public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02; - - /** - * Recommended constructor. - * - * @param c The cursor from which to get the data. - * @param context The context - * @param flags Flags used to determine the behavior of the adapter; may - * be any combination of {@link #FLAG_AUTO_REQUERY} and - * {@link #FLAG_REGISTER_CONTENT_OBSERVER}. - */ - public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) { - init(context, c, flags); - } - - void init(final Context context, final Cursor c, int flags) { - if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) { - flags |= FLAG_REGISTER_CONTENT_OBSERVER; - mAutoRequery = true; - } else { - mAutoRequery = false; - } - final boolean cursorPresent = c != null; - mCursor = c; - mDataValid = cursorPresent; - mContext = context; - mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1; - if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { - mChangeObserver = new ChangeObserver(); - mDataSetObserver = new MyDataSetObserver(); - } else { - mChangeObserver = null; - mDataSetObserver = null; - } - - if (cursorPresent) { - if (mChangeObserver != null) { - c.registerContentObserver(mChangeObserver); - } - if (mDataSetObserver != null) { - c.registerDataSetObserver(mDataSetObserver); - } - } - } - - /** - * Returns the cursor. - * @return the cursor. - */ - public Cursor getCursor() { - return mCursor; - } - - @Override - public int getItemCount() { - if (mDataValid && mCursor != null) { - return mCursor.getCount(); - } else { - return 0; - } - } - - /** - * @see android.support.v7.widget.RecyclerView.Adapter#getItem(int) - */ - public Object getItem(final int position) { - if (mDataValid && mCursor != null) { - mCursor.moveToPosition(position); - return mCursor; - } else { - return null; - } - } - - /** - * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int) - */ - @Override - public long getItemId(final int position) { - if (mDataValid && mCursor != null) { - if (mCursor.moveToPosition(position)) { - return mCursor.getLong(mRowIDColumn); - } else { - return 0; - } - } else { - return 0; - } - } - - @Override - public VH onCreateViewHolder(final ViewGroup parent, final int viewType) { - return createViewHolder(mContext, parent, viewType); - } - - @Override - public void onBindViewHolder(final VH holder, final int position) { - if (!mDataValid) { - throw new IllegalStateException("this should only be called when the cursor is valid"); - } - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("couldn't move cursor to position " + position); - } - bindViewHolder(holder, mContext, mCursor); - } - /** - * Bind an existing view to the data pointed to by cursor - * @param view Existing view, returned earlier by newView - * @param context Interface to application's global information - * @param cursor The cursor from which to get the data. The cursor is already - * moved to the correct position. - */ - public abstract void bindViewHolder(VH holder, Context context, Cursor cursor); - - /** - * @see android.support.v7.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int) - */ - public abstract VH createViewHolder(Context context, ViewGroup parent, int viewType); - - /** - * Change the underlying cursor to a new cursor. If there is an existing cursor it will be - * closed. - * - * @param cursor The new cursor to be used - */ - public void changeCursor(final Cursor cursor) { - final Cursor old = swapCursor(cursor); - if (old != null) { - old.close(); - } - } - - /** - * Swap in a new Cursor, returning the old Cursor. Unlike - * {@link #changeCursor(Cursor)}, the returned old Cursor is not - * closed. - * - * @param newCursor The new cursor to be used. - * @return Returns the previously set Cursor, or null if there wasa not one. - * If the given new Cursor is the same instance is the previously set - * Cursor, null is also returned. - */ - public Cursor swapCursor(final Cursor newCursor) { - if (newCursor == mCursor) { - return null; - } - final Cursor oldCursor = mCursor; - if (oldCursor != null) { - if (mChangeObserver != null) { - oldCursor.unregisterContentObserver(mChangeObserver); - } - if (mDataSetObserver != null) { - oldCursor.unregisterDataSetObserver(mDataSetObserver); - } - } - mCursor = newCursor; - if (newCursor != null) { - if (mChangeObserver != null) { - newCursor.registerContentObserver(mChangeObserver); - } - if (mDataSetObserver != null) { - newCursor.registerDataSetObserver(mDataSetObserver); - } - mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); - mDataValid = true; - // notify the observers about the new cursor - notifyDataSetChanged(); - } else { - mRowIDColumn = -1; - mDataValid = false; - // notify the observers about the lack of a data set - notifyDataSetChanged(); - } - return oldCursor; - } - - /** - *

Converts the cursor into a CharSequence. Subclasses should override this - * method to convert their results. The default implementation returns an - * empty String for null values or the default String representation of - * the value.

- * - * @param cursor the cursor to convert to a CharSequence - * @return a CharSequence representing the value - */ - public CharSequence convertToString(final Cursor cursor) { - return cursor == null ? "" : cursor.toString(); - } - - /** - * Called when the {@link ContentObserver} on the cursor receives a change notification. - * The default implementation provides the auto-requery logic, but may be overridden by - * sub classes. - * - * @see ContentObserver#onChange(boolean) - */ - protected void onContentChanged() { - if (mAutoRequery && mCursor != null && !mCursor.isClosed()) { - if (false) { - Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); - } - mDataValid = mCursor.requery(); - } - } - - private class ChangeObserver extends ContentObserver { - public ChangeObserver() { - super(new Handler()); - } - - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(final boolean selfChange) { - onContentChanged(); - } - } - - private class MyDataSetObserver extends DataSetObserver { - @Override - public void onChanged() { - mDataValid = true; - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - mDataValid = false; - notifyDataSetChanged(); - } - } - -} diff --git a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java deleted file mode 100644 index 1268c53..0000000 --- a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.CursorAdapter; -import android.widget.ListView; - -import com.android.messaging.util.ImeUtil; - -/** - * Produces and holds a list view and its tab header to be displayed in a ViewPager. - */ -public abstract class CustomHeaderPagerListViewHolder extends BasePagerViewHolder - implements CustomHeaderPagerViewHolder { - private final Context mContext; - private final CursorAdapter mListAdapter; - private boolean mListCursorInitialized; - private ListView mListView; - - public CustomHeaderPagerListViewHolder(final Context context, - final CursorAdapter adapter) { - mContext = context; - mListAdapter = adapter; - } - - @Override - protected View createView(ViewGroup container) { - final LayoutInflater inflater = (LayoutInflater) - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final View view = inflater.inflate( - getLayoutResId(), - null /* root */, - false /* attachToRoot */); - final ListView listView = (ListView) view.findViewById(getListViewResId()); - listView.setAdapter(mListAdapter); - listView.setOnScrollListener(new OnScrollListener() { - @Override - public void onScrollStateChanged(final AbsListView view, final int scrollState) { - if (scrollState != SCROLL_STATE_IDLE) { - ImeUtil.get().hideImeKeyboard(mContext, view); - } - } - - @Override - public void onScroll(final AbsListView view, final int firstVisibleItem, - final int visibleItemCount, final int totalItemCount) { - } - }); - mListView = listView; - maybeSetEmptyView(); - return view; - } - - public void onContactsCursorUpdated(final Cursor data) { - mListAdapter.swapCursor(data); - if (!mListCursorInitialized) { - // We set the emptyView here instead of in create so that the initial load won't show - // the empty UI - the system handles this and doesn't do what we would like. - mListCursorInitialized = true; - maybeSetEmptyView(); - } - } - - /** - * We don't want to show the empty view hint until BOTH conditions are met: - * 1. The view has been created. - * 2. Cursor data has been loaded once. - * Due to timing when data is loaded, the view may not be ready (and vice versa). So we - * are calling this method from both onContactsCursorUpdated & createView. - */ - private void maybeSetEmptyView() { - if (mView != null && mListCursorInitialized) { - final ListEmptyView emptyView = (ListEmptyView) mView.findViewById(getEmptyViewResId()); - if (emptyView != null) { - emptyView.setTextHint(getEmptyViewTitleResId()); - emptyView.setImageHint(getEmptyViewImageResId()); - final ListView listView = (ListView) mView.findViewById(getListViewResId()); - listView.setEmptyView(emptyView); - } - } - } - - public void invalidateList() { - mListAdapter.notifyDataSetChanged(); - } - - /** - * In order for scene transition to work, we toggle the visibility for each individual list - * view items so that they can be properly tracked by the scene transition manager. - * @param show whether the pending transition is to show or hide the list. - */ - public void toggleVisibilityForPendingTransition(final boolean show, final View epicenterView) { - if (mListView == null) { - return; - } - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View childView = mListView.getChildAt(i); - if (childView != epicenterView) { - childView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); - } - } - } - - @Override - public CharSequence getPageTitle(Context context) { - return context.getString(getPageTitleResId()); - } - - protected abstract int getLayoutResId(); - protected abstract int getPageTitleResId(); - protected abstract int getEmptyViewResId(); - protected abstract int getEmptyViewTitleResId(); - protected abstract int getEmptyViewImageResId(); - protected abstract int getListViewResId(); -} diff --git a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java deleted file mode 100644 index 43802cd..0000000 --- a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; - -/** - * An extension on the standard PagerViewHolder to return a custom header view to be used by - * CustomHeaderViewPager - */ -public interface CustomHeaderPagerViewHolder extends PagerViewHolder { - CharSequence getPageTitle(Context context); -} diff --git a/src/com/android/messaging/ui/CustomHeaderViewPager.java b/src/com/android/messaging/ui/CustomHeaderViewPager.java deleted file mode 100644 index 2e8df42..0000000 --- a/src/com/android/messaging/ui/CustomHeaderViewPager.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.widget.LinearLayout; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; - -/** - * A view that contains both a view pager and a tab strip wrapped in a linear layout. - */ -public class CustomHeaderViewPager extends LinearLayout { - public final static int DEFAULT_TAB_STRIP_SIZE = -1; - private final int mDefaultTabStripSize; - private ViewPager mViewPager; - private ViewPagerTabs mTabstrip; - - public CustomHeaderViewPager(final Context context, final AttributeSet attrs) { - super(context, attrs); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.custom_header_view_pager, this, true); - setOrientation(LinearLayout.VERTICAL); - - mTabstrip = (ViewPagerTabs) findViewById(R.id.tab_strip); - mViewPager = (ViewPager) findViewById(R.id.pager); - - TypedValue tv = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true); - mDefaultTabStripSize = context.getResources().getDimensionPixelSize(tv.resourceId); - } - - public void setCurrentItem(final int position) { - mViewPager.setCurrentItem(position); - } - - public void setViewPagerTabHeight(final int tabHeight) { - mTabstrip.getLayoutParams().height = tabHeight == DEFAULT_TAB_STRIP_SIZE ? - mDefaultTabStripSize : tabHeight; - } - - public void setViewHolders(final CustomHeaderPagerViewHolder[] viewHolders) { - Assert.notNull(mViewPager); - final PagerAdapter adapter = new CustomHeaderViewPagerAdapter(viewHolders); - mViewPager.setAdapter(adapter); - mTabstrip.setViewPager(mViewPager); - mViewPager.setOnPageChangeListener(new OnPageChangeListener() { - - @Override - public void onPageScrollStateChanged(int state) { - mTabstrip.onPageScrollStateChanged(state); - } - - @Override - public void onPageScrolled(int position, float positionOffset, - int positionOffsetPixels) { - mTabstrip.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - - @Override - public void onPageSelected(int position) { - mTabstrip.onPageSelected(position); - } - }); - } - - public int getSelectedItemPosition() { - return mTabstrip.getSelectedItemPosition(); - } -} diff --git a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java b/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java deleted file mode 100644 index 1a5cf6a..0000000 --- a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import com.android.messaging.Factory; - -public class CustomHeaderViewPagerAdapter extends - FixedViewPagerAdapter { - - public CustomHeaderViewPagerAdapter(final CustomHeaderPagerViewHolder[] viewHolders) { - super(viewHolders); - } - - @Override - public CharSequence getPageTitle(int position) { - // The tab strip will handle RTL internally so we should use raw position. - return getViewHolder(position, false /* rtlAware */) - .getPageTitle(Factory.get().getApplicationContext()); - } -} diff --git a/src/com/android/messaging/ui/FixedViewPagerAdapter.java b/src/com/android/messaging/ui/FixedViewPagerAdapter.java deleted file mode 100644 index bd73b71..0000000 --- a/src/com/android/messaging/ui/FixedViewPagerAdapter.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.os.Bundle; -import android.os.Parcelable; -import android.support.v4.view.PagerAdapter; -import android.view.View; -import android.view.ViewGroup; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -/** - * A PagerAdapter that provides a fixed number of paged Views provided by a fixed set of - * {@link PagerViewHolder}'s. This allows us to put a fixed number of Views, instead of fragments, - * into a given ViewPager. - */ -public class FixedViewPagerAdapter extends PagerAdapter { - private final T[] mViewHolders; - - public FixedViewPagerAdapter(final T[] viewHolders) { - Assert.notNull(viewHolders); - mViewHolders = viewHolders; - } - - @Override - public Object instantiateItem(final ViewGroup container, final int position) { - final PagerViewHolder viewHolder = getViewHolder(position); - final View view = viewHolder.getView(container); - if (view == null) { - return null; - } - view.setTag(viewHolder); - container.addView(view); - return viewHolder; - } - - @Override - public void destroyItem(final ViewGroup container, final int position, final Object object) { - final PagerViewHolder viewHolder = getViewHolder(position); - final View destroyedView = viewHolder.destroyView(); - if (destroyedView != null) { - container.removeView(destroyedView); - } - } - - @Override - public int getCount() { - return mViewHolders.length; - } - - @Override - public boolean isViewFromObject(final View view, final Object object) { - return view.getTag() == object; - } - - public T getViewHolder(final int i) { - return getViewHolder(i, true /* rtlAware */); - } - - @VisibleForTesting - public T getViewHolder(final int i, final boolean rtlAware) { - return mViewHolders[rtlAware ? getRtlPosition(i) : i]; - } - - @Override - public Parcelable saveState() { - // The paged views in the view pager gets created and destroyed as the user scrolls through - // them. By default, only the pages to the immediate left and right of the current visible - // page are realized. Moreover, if the activity gets destroyed and recreated, the pages are - // automatically destroyed. Therefore, in order to preserve transient page UI states that - // are not persisted in the DB we'd like to store them in a Bundle when views get - // destroyed. When the views get recreated, we rehydrate them by passing them the saved - // data. When the activity gets destroyed, it invokes saveState() on this adapter to - // add this saved Bundle to the overall saved instance state. - final Bundle savedViewHolderState = new Bundle(Factory.get().getApplicationContext() - .getClassLoader()); - for (int i = 0; i < mViewHolders.length; i++) { - final Parcelable pageState = getViewHolder(i).saveState(); - savedViewHolderState.putParcelable(getInstanceStateKeyForPage(i), pageState); - } - return savedViewHolderState; - } - - @Override - public void restoreState(final Parcelable state, final ClassLoader loader) { - if (state instanceof Bundle) { - final Bundle restoredViewHolderState = (Bundle) state; - ((Bundle) state).setClassLoader(Factory.get().getApplicationContext().getClassLoader()); - for (int i = 0; i < mViewHolders.length; i++) { - final Parcelable pageState = restoredViewHolderState - .getParcelable(getInstanceStateKeyForPage(i)); - getViewHolder(i).restoreState(pageState); - } - } else { - super.restoreState(state, loader); - } - } - - public void resetState() { - for (int i = 0; i < mViewHolders.length; i++) { - getViewHolder(i).resetState(); - } - } - - private String getInstanceStateKeyForPage(final int i) { - return getViewHolder(i).getClass().getCanonicalName() + "_savedstate_" + i; - } - - protected int getRtlPosition(final int position) { - if (UiUtils.isRtlMode()) { - return mViewHolders.length - 1 - position; - } - return position; - } -} diff --git a/src/com/android/messaging/ui/ImeDetectFrameLayout.java b/src/com/android/messaging/ui/ImeDetectFrameLayout.java deleted file mode 100644 index 32564ea..0000000 --- a/src/com/android/messaging/ui/ImeDetectFrameLayout.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.FrameLayout; - -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.LogUtil; - -public class ImeDetectFrameLayout extends FrameLayout { - public ImeDetectFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int measuredHeight = getMeasuredHeight(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, "ImeDetectFrameLayout " + - "measuredHeight: " + measuredHeight + " getMeasuredHeight(): " + - getMeasuredHeight()); - } - - if (measuredHeight != getMeasuredHeight() && getContext() instanceof ImeUtil.ImeStateHost) { - ((ImeUtil.ImeStateHost) getContext()).onDisplayHeightChanged(heightMeasureSpec); - } - } -} diff --git a/src/com/android/messaging/ui/LicenseActivity.java b/src/com/android/messaging/ui/LicenseActivity.java deleted file mode 100644 index a28da81..0000000 --- a/src/com/android/messaging/ui/LicenseActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; -import android.os.Bundle; -import android.webkit.WebView; - -import com.android.messaging.R; - -public class LicenseActivity extends Activity { - private final String LICENSE_URL = "file:///android_asset/licenses.html"; - - @Override - public void onCreate(final Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.license_activity); - final WebView webView = (WebView) findViewById(R.id.content); - webView.loadUrl(LICENSE_URL); - } -} diff --git a/src/com/android/messaging/ui/LineWrapLayout.java b/src/com/android/messaging/ui/LineWrapLayout.java deleted file mode 100644 index 5219811..0000000 --- a/src/com/android/messaging/ui/LineWrapLayout.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; - -/** -* A line-wrapping flow layout. Arranges children in horizontal flow, packing as many -* child views as possible on each line. When the current line does not -* have enough horizontal space, the layout continues on the next line. -*/ -public class LineWrapLayout extends ViewGroup { - public LineWrapLayout(Context context) { - this(context, null); - } - - public LineWrapLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int startPadding = UiUtils.getPaddingStart(this); - final int endPadding = UiUtils.getPaddingEnd(this); - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding; - final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY); - - int height = 0; - - int childCount = getChildCount(); - int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); - - int x = startPadding; - int currLineWidth = 0; - int currLineHeight = 0; - int maxLineWidth = 0; - - for (int i = 0; i < childCount; i++) { - View currChild = getChildAt(i); - if (currChild.getVisibility() == GONE) { - continue; - } - LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); - int startMargin = layoutParams.getStartMargin(); - int endMargin = layoutParams.getEndMargin(); - currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED); - int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin; - int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin + - layoutParams.bottomMargin; - - if ((x + childMeasuredWidth) > widthSize) { - // New line. Update the overall height and reset trackers. - height += currLineHeight; - currLineHeight = 0; - x = startPadding; - currLineWidth = 0; - startMargin = 0; - } - - x += childMeasuredWidth; - currLineWidth += childMeasuredWidth; - currLineHeight = Math.max(currLineHeight, childMeasuredHeight); - maxLineWidth = Math.max(currLineWidth, maxLineWidth); - } - // And account for the height of the last line. - height += currLineHeight; - - int width = isFixedSize ? widthSize : maxLineWidth; - setMeasuredDimension(width + startPadding + endPadding, - height + getPaddingTop() + getPaddingBottom()); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int startPadding = UiUtils.getPaddingStart(this); - final int endPadding = UiUtils.getPaddingEnd(this); - int width = getWidth() - startPadding - endPadding; - int y = getPaddingTop(); - int x = startPadding; - int childCount = getChildCount(); - - int currLineHeight = 0; - - // Do a dry-run first to get the line heights. - final ArrayList lineHeights = new ArrayList(); - for (int i = 0; i < childCount; i++) { - View currChild = getChildAt(i); - if (currChild.getVisibility() == GONE) { - continue; - } - LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); - int childWidth = currChild.getMeasuredWidth(); - int childHeight = currChild.getMeasuredHeight(); - int startMargin = layoutParams.getStartMargin(); - int endMargin = layoutParams.getEndMargin(); - - if ((x + childWidth + startMargin + endMargin) > width) { - // new line - lineHeights.add(currLineHeight); - currLineHeight = 0; - x = startPadding; - startMargin = 0; - } - currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin + - layoutParams.bottomMargin); - x += childWidth + startMargin + endMargin; - } - // Add the last line height. - lineHeights.add(currLineHeight); - - // Now perform the actual layout. - x = startPadding; - currLineHeight = 0; - int lineIndex = 0; - for (int i = 0; i < childCount; i++) { - View currChild = getChildAt(i); - if (currChild.getVisibility() == GONE) { - continue; - } - LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams(); - int childWidth = currChild.getMeasuredWidth(); - int childHeight = currChild.getMeasuredHeight(); - int startMargin = layoutParams.getStartMargin(); - int endMargin = layoutParams.getEndMargin(); - - if ((x + childWidth + startMargin + endMargin) > width) { - // new line - y += currLineHeight; - currLineHeight = 0; - x = startPadding; - startMargin = 0; - lineIndex++; - } - final int startPositionX = x + startMargin; - int startPositionY = y + layoutParams.topMargin; // default to top gravity - final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK; - if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) { - final int lineHeight = lineHeights.get(lineIndex); - switch (majorGravity) { - case Gravity.BOTTOM: - startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin; - break; - - case Gravity.CENTER_VERTICAL: - startPositionY = y + (lineHeight - childHeight) / 2; - break; - } - } - - if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration() - .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - currChild.layout(width - startPositionX - childWidth, startPositionY, - width - startPositionX, startPositionY + childHeight); - } else { - currChild.layout(startPositionX, startPositionY, startPositionX + childWidth, - startPositionY + childHeight); - } - currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin + - layoutParams.bottomMargin); - x += childWidth + startMargin + endMargin; - } - } - - @Override - protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return new LayoutParams(p); - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - } - - public static final class LayoutParams extends FrameLayout.LayoutParams { - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public int getStartMargin() { - if (OsUtil.isAtLeastJB_MR2()) { - return getMarginStart(); - } else { - return leftMargin; - } - } - - public int getEndMargin() { - if (OsUtil.isAtLeastJB_MR2()) { - return getMarginEnd(); - } else { - return rightMargin; - } - } - } -} diff --git a/src/com/android/messaging/ui/ListEmptyView.java b/src/com/android/messaging/ui/ListEmptyView.java deleted file mode 100644 index 8cf3049..0000000 --- a/src/com/android/messaging/ui/ListEmptyView.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Gravity; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; -import android.widget.TextView; - -import com.android.messaging.R; - -/** - * A common reusable view that shows a hint image and text for an empty list view. - */ -public class ListEmptyView extends LinearLayout { - private ImageView mEmptyImageHint; - private TextView mEmptyTextHint; - - public ListEmptyView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mEmptyImageHint = (ImageView) findViewById(R.id.empty_image_hint); - mEmptyTextHint = (TextView) findViewById(R.id.empty_text_hint); - } - - public void setImageHint(final int resId) { - mEmptyImageHint.setImageResource(resId); - } - - public void setTextHint(final int resId) { - mEmptyTextHint.setText(getResources().getText(resId)); - } - - public void setTextHint(final CharSequence hintText) { - mEmptyTextHint.setText(hintText); - } - - public void setIsImageVisible(final boolean isImageVisible) { - mEmptyImageHint.setVisibility(isImageVisible ? VISIBLE : GONE); - } - - public void setIsVerticallyCentered(final boolean isVerticallyCentered) { - int gravity = - isVerticallyCentered ? Gravity.CENTER : Gravity.TOP | Gravity.CENTER_HORIZONTAL; - ((LinearLayout.LayoutParams) mEmptyImageHint.getLayoutParams()).gravity = gravity; - ((LinearLayout.LayoutParams) mEmptyTextHint.getLayoutParams()).gravity = gravity; - getLayoutParams().height = - isVerticallyCentered ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; - requestLayout(); - } -} diff --git a/src/com/android/messaging/ui/MaxHeightScrollView.java b/src/com/android/messaging/ui/MaxHeightScrollView.java deleted file mode 100644 index a000cd1..0000000 --- a/src/com/android/messaging/ui/MaxHeightScrollView.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.ScrollView; - -import com.android.messaging.R; - -/** - * A ScrollView that limits the maximum height that it can take. This is to work around android - * layout's limitation of not having android:maxHeight. - */ -public class MaxHeightScrollView extends ScrollView { - private static final int NO_MAX_HEIGHT = -1; - - private final int mMaxHeight; - - public MaxHeightScrollView(final Context context, final AttributeSet attrs) { - super(context, attrs); - final TypedArray attr = context.obtainStyledAttributes(attrs, - R.styleable.MaxHeightScrollView, 0, 0); - mMaxHeight = attr.getDimensionPixelSize(R.styleable.MaxHeightScrollView_android_maxHeight, - NO_MAX_HEIGHT); - attr.recycle(); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mMaxHeight != NO_MAX_HEIGHT) { - setMeasuredDimension(getMeasuredWidth(), Math.min(getMeasuredHeight(), mMaxHeight)); - } - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/MultiAttachmentLayout.java b/src/com/android/messaging/ui/MultiAttachmentLayout.java deleted file mode 100644 index f620245..0000000 --- a/src/com/android/messaging/ui/MultiAttachmentLayout.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.animation.AnimationSet; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MediaPickerMessagePartData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -/** - * Holds and displays multiple attachments in a 4x2 grid. Each preview image "tile" can take - * one of three sizes - small (1x1), wide (2x1) and large (2x2). We have a number of predefined - * layout settings designed for holding 2, 3, 4+ attachments (these layout settings are - * tweakable by design request to allow for max flexibility). For a visual example, consider the - * following attachment layout: - * - * +---------------+----------------+ - * | | | - * | | B | - * | | | - * | A |-------+--------| - * | | | | - * | | C | D | - * | | | | - * +---------------+-------+--------+ - * - * In the above example, the layout consists of four tiles, A-D. A is a large tile, B is a - * wide tile and C & D are both small tiles. A starts at (0,0) and ends at (1,1), B starts at - * (2,0) and ends at (3,0), and so on. In our layout class we'd have these tiles in the order - * of A-D, so that we make sure the last tile is always the one where we can put the overflow - * indicator (e.g. "+2"). - */ -public class MultiAttachmentLayout extends FrameLayout { - - public interface OnAttachmentClickListener { - boolean onAttachmentClick(MessagePartData attachment, Rect viewBoundsOnScreen, - boolean longPress); - } - - private static final int GRID_WIDTH = 4; // in # of cells - private static final int GRID_HEIGHT = 2; // in # of cells - - /** - * Represents a preview image tile in the layout - */ - private static class Tile { - public final int startX; - public final int startY; - public final int endX; - public final int endY; - - private Tile(final int startX, final int startY, final int endX, final int endY) { - this.startX = startX; - this.startY = startY; - this.endX = endX; - this.endY = endY; - } - - public int getWidthMeasureSpec(final int cellWidth, final int padding) { - return MeasureSpec.makeMeasureSpec((endX - startX + 1) * cellWidth - padding * 2, - MeasureSpec.EXACTLY); - } - - public int getHeightMeasureSpec(final int cellHeight, final int padding) { - return MeasureSpec.makeMeasureSpec((endY - startY + 1) * cellHeight - padding * 2, - MeasureSpec.EXACTLY); - } - - public static Tile large(final int startX, final int startY) { - return new Tile(startX, startY, startX + 1, startY + 1); - } - - public static Tile wide(final int startX, final int startY) { - return new Tile(startX, startY, startX + 1, startY); - } - - public static Tile small(final int startX, final int startY) { - return new Tile(startX, startY, startX, startY); - } - } - - /** - * A layout simply contains a list of tiles, in the order of top-left -> bottom-right. - */ - private static class Layout { - public final List tiles; - public Layout(final Tile[] tilesArray) { - tiles = Arrays.asList(tilesArray); - } - } - - /** - * List of predefined layout configurations w.r.t no. of attachments. - */ - private static final Layout[] ATTACHMENT_LAYOUTS_BY_COUNT = { - null, // Doesn't support zero attachments. - null, // Doesn't support one attachment. Single attachment preview is used instead. - new Layout(new Tile[] { Tile.large(0, 0), Tile.large(2, 0) }), // 2 items - new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.wide(2, 1) }), // 3 items - new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.small(2, 1), // 4+ items - Tile.small(3, 1) }), - }; - - /** - * List of predefined RTL layout configurations w.r.t no. of attachments. - */ - private static final Layout[] ATTACHMENT_RTL_LAYOUTS_BY_COUNT = { - null, // Doesn't support zero attachments. - null, // Doesn't support one attachment. Single attachment preview is used instead. - new Layout(new Tile[] { Tile.large(2, 0), Tile.large(0, 0)}), // 2 items - new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.wide(0, 1) }), // 3 items - new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.small(1, 1), // 4+ items - Tile.small(0, 1) }), - }; - - private Layout mCurrentLayout; - private ArrayList mPreviewViews; - private int mPlusNumber; - private TextView mPlusTextView; - private OnAttachmentClickListener mAttachmentClickListener; - private AsyncImageViewDelayLoader mImageViewDelayLoader; - - public MultiAttachmentLayout(final Context context, final AttributeSet attrs) { - super(context, attrs); - mPreviewViews = new ArrayList(); - } - - public void bindAttachments(final Iterable attachments, - final Rect transitionRect, final int count) { - final ArrayList previousViews = mPreviewViews; - mPreviewViews = new ArrayList(); - removeView(mPlusTextView); - mPlusTextView = null; - - determineLayout(attachments, count); - buildViews(attachments, previousViews, transitionRect); - - // Remove all previous views that couldn't be recycled. - for (final ViewWrapper viewWrapper : previousViews) { - removeView(viewWrapper.view); - } - requestLayout(); - } - - public OnAttachmentClickListener getOnAttachmentClickListener() { - return mAttachmentClickListener; - } - - public void setOnAttachmentClickListener(final OnAttachmentClickListener listener) { - mAttachmentClickListener = listener; - } - - public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) { - mImageViewDelayLoader = delayLoader; - } - - public void setColorFilter(int color) { - for (ViewWrapper viewWrapper : mPreviewViews) { - if (viewWrapper.view instanceof AsyncImageView) { - ((AsyncImageView) viewWrapper.view).setColorFilter(color); - } - } - } - - public void clearColorFilter() { - for (ViewWrapper viewWrapper : mPreviewViews) { - if (viewWrapper.view instanceof AsyncImageView) { - ((AsyncImageView) viewWrapper.view).clearColorFilter(); - } - } - } - - private void determineLayout(final Iterable attachments, final int count) { - Assert.isTrue(attachments != null); - final boolean isRtl = AccessibilityUtil.isLayoutRtl(getRootView()); - if (isRtl) { - mCurrentLayout = ATTACHMENT_RTL_LAYOUTS_BY_COUNT[Math.min(count, - ATTACHMENT_RTL_LAYOUTS_BY_COUNT.length - 1)]; - } else { - mCurrentLayout = ATTACHMENT_LAYOUTS_BY_COUNT[Math.min(count, - ATTACHMENT_LAYOUTS_BY_COUNT.length - 1)]; - } - - // We must have a valid layout for the current configuration. - Assert.notNull(mCurrentLayout); - - mPlusNumber = count - mCurrentLayout.tiles.size(); - Assert.isTrue(mPlusNumber >= 0); - } - - private void buildViews(final Iterable attachments, - final ArrayList previousViews, final Rect transitionRect) { - final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - final int count = mCurrentLayout.tiles.size(); - int i = 0; - final Iterator iterator = attachments.iterator(); - while (iterator.hasNext() && i < count) { - final MessagePartData attachment = iterator.next(); - ViewWrapper attachmentWrapper = null; - // Try to recycle a previous view first - for (int j = 0; j < previousViews.size(); j++) { - final ViewWrapper previousView = previousViews.get(j); - if (previousView.attachment.equals(attachment) && - !(previousView.attachment instanceof PendingAttachmentData)) { - attachmentWrapper = previousView; - previousViews.remove(j); - break; - } - } - - if (attachmentWrapper == null) { - final View view = AttachmentPreviewFactory.createAttachmentPreview(layoutInflater, - attachment, this, AttachmentPreviewFactory.TYPE_MULTIPLE, - false /* startImageRequest */, mAttachmentClickListener); - - if (view == null) { - // createAttachmentPreview can return null if something goes wrong (e.g. - // attachment has unsupported contentType) - continue; - } - if (view instanceof AsyncImageView && mImageViewDelayLoader != null) { - AsyncImageView asyncImageView = (AsyncImageView) view; - asyncImageView.setDelayLoader(mImageViewDelayLoader); - } - addView(view); - attachmentWrapper = new ViewWrapper(view, attachment); - // Help animate from single to multi by copying over the prev location - if (count == 2 && i == 1 && transitionRect != null) { - attachmentWrapper.prevLeft = transitionRect.left; - attachmentWrapper.prevTop = transitionRect.top; - attachmentWrapper.prevWidth = transitionRect.width(); - attachmentWrapper.prevHeight = transitionRect.height(); - } - } - i++; - Assert.notNull(attachmentWrapper); - mPreviewViews.add(attachmentWrapper); - - // The first view will animate in using PopupTransitionAnimation, but the remaining - // views will slide from their previous position to their new position within the - // layout - if (i == 0) { - AttachmentPreview.tryAnimateViewIn(attachment, attachmentWrapper.view); - } - attachmentWrapper.needsSlideAnimation = i > 0; - } - - // Build the plus text view (e.g. "+2") for when there are more attachments than what - // this layout can display. - if (mPlusNumber > 0) { - mPlusTextView = (TextView) layoutInflater.inflate(R.layout.attachment_more_text_view, - null /* parent */); - mPlusTextView.setText(getResources().getString(R.string.attachment_more_items, - mPlusNumber)); - addView(mPlusTextView); - } - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - final int maxWidth = getResources().getDimensionPixelSize( - R.dimen.multiple_attachment_preview_width); - final int maxHeight = getResources().getDimensionPixelSize( - R.dimen.multiple_attachment_preview_height); - final int width = Math.min(MeasureSpec.getSize(widthMeasureSpec), maxWidth); - final int height = maxHeight; - final int cellWidth = width / GRID_WIDTH; - final int cellHeight = height / GRID_HEIGHT; - final int count = mPreviewViews.size(); - final int padding = getResources().getDimensionPixelOffset( - R.dimen.multiple_attachment_preview_padding); - for (int i = 0; i < count; i++) { - final View view = mPreviewViews.get(i).view; - final Tile imageTile = mCurrentLayout.tiles.get(i); - view.measure(imageTile.getWidthMeasureSpec(cellWidth, padding), - imageTile.getHeightMeasureSpec(cellHeight, padding)); - - // Now that we know the size, we can request an appropriately-sized image. - if (view instanceof AsyncImageView) { - final ImageRequestDescriptor imageRequest = - AttachmentPreviewFactory.getImageRequestDescriptorForAttachment( - mPreviewViews.get(i).attachment, - view.getMeasuredWidth(), - view.getMeasuredHeight()); - ((AsyncImageView) view).setImageResourceId(imageRequest); - } - - if (i == count - 1 && mPlusTextView != null) { - // The plus text view always covers the last attachment. - mPlusTextView.measure(imageTile.getWidthMeasureSpec(cellWidth, padding), - imageTile.getHeightMeasureSpec(cellHeight, padding)); - } - } - setMeasuredDimension(width, height); - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, - final int bottom) { - final int cellWidth = getMeasuredWidth() / GRID_WIDTH; - final int cellHeight = getMeasuredHeight() / GRID_HEIGHT; - final int padding = getResources().getDimensionPixelOffset( - R.dimen.multiple_attachment_preview_padding); - final int count = mPreviewViews.size(); - for (int i = 0; i < count; i++) { - final ViewWrapper viewWrapper = mPreviewViews.get(i); - final View view = viewWrapper.view; - final Tile imageTile = mCurrentLayout.tiles.get(i); - final int tileLeft = imageTile.startX * cellWidth; - final int tileTop = imageTile.startY * cellHeight; - view.layout(tileLeft + padding, tileTop + padding, - tileLeft + view.getMeasuredWidth(), - tileTop + view.getMeasuredHeight()); - if (viewWrapper.needsSlideAnimation) { - trySlideAttachmentView(viewWrapper); - viewWrapper.needsSlideAnimation = false; - } else { - viewWrapper.prevLeft = view.getLeft(); - viewWrapper.prevTop = view.getTop(); - viewWrapper.prevWidth = view.getWidth(); - viewWrapper.prevHeight = view.getHeight(); - } - - if (i == count - 1 && mPlusTextView != null) { - // The plus text view always covers the last attachment. - mPlusTextView.layout(tileLeft + padding, tileTop + padding, - tileLeft + mPlusTextView.getMeasuredWidth(), - tileTop + mPlusTextView.getMeasuredHeight()); - } - } - } - - private void trySlideAttachmentView(final ViewWrapper viewWrapper) { - if (!(viewWrapper.attachment instanceof MediaPickerMessagePartData)) { - return; - } - final View view = viewWrapper.view; - - - final int xOffset = viewWrapper.prevLeft - view.getLeft(); - final int yOffset = viewWrapper.prevTop - view.getTop(); - final float scaleX = viewWrapper.prevWidth / (float) view.getWidth(); - final float scaleY = viewWrapper.prevHeight / (float) view.getHeight(); - - if (xOffset == 0 && yOffset == 0 && scaleX == 1 && scaleY == 1) { - // Layout hasn't changed - return; - } - - final AnimationSet animationSet = new AnimationSet( - true /* shareInterpolator */); - animationSet.addAnimation(new TranslateAnimation(xOffset, 0, yOffset, 0)); - animationSet.addAnimation(new ScaleAnimation(scaleX, 1, scaleY, 1)); - animationSet.setDuration( - UiUtils.MEDIAPICKER_TRANSITION_DURATION); - animationSet.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR); - view.startAnimation(animationSet); - view.invalidate(); - viewWrapper.prevLeft = view.getLeft(); - viewWrapper.prevTop = view.getTop(); - viewWrapper.prevWidth = view.getWidth(); - viewWrapper.prevHeight = view.getHeight(); - } - - public View findViewForAttachment(final MessagePartData attachment) { - for (ViewWrapper wrapper : mPreviewViews) { - if (wrapper.attachment.equals(attachment) && - !(wrapper.attachment instanceof PendingAttachmentData)) { - return wrapper.view; - } - } - return null; - } - - private static class ViewWrapper { - final View view; - final MessagePartData attachment; - boolean needsSlideAnimation; - int prevLeft; - int prevTop; - int prevWidth; - int prevHeight; - - ViewWrapper(final View view, final MessagePartData attachment) { - this.view = view; - this.attachment = attachment; - } - } -} diff --git a/src/com/android/messaging/ui/OrientedBitmapDrawable.java b/src/com/android/messaging/ui/OrientedBitmapDrawable.java deleted file mode 100644 index 9242668..0000000 --- a/src/com/android/messaging/ui/OrientedBitmapDrawable.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; -import android.view.Gravity; - -import com.android.messaging.util.exif.ExifInterface; - -/** - * A drawable that draws a bitmap in a flipped or rotated orientation without having to adjust the - * bitmap - */ -public class OrientedBitmapDrawable extends BitmapDrawable { - private final ExifInterface.OrientationParams mOrientationParams; - private final Rect mDstRect; - private int mCenterX; - private int mCenterY; - private boolean mApplyGravity; - - public static BitmapDrawable create(final int orientation, Resources res, Bitmap bitmap) { - if (orientation <= ExifInterface.Orientation.TOP_LEFT) { - // No need to adjust the bitmap, so just use a regular BitmapDrawable - return new BitmapDrawable(res, bitmap); - } else { - // Create an oriented bitmap drawable - return new OrientedBitmapDrawable(orientation, res, bitmap); - } - } - - private OrientedBitmapDrawable(final int orientation, Resources res, Bitmap bitmap) { - super(res, bitmap); - mOrientationParams = ExifInterface.getOrientationParams(orientation); - mApplyGravity = true; - mDstRect = new Rect(); - } - - @Override - public int getIntrinsicWidth() { - if (mOrientationParams.invertDimensions) { - return super.getIntrinsicHeight(); - } - return super.getIntrinsicWidth(); - } - - @Override - public int getIntrinsicHeight() { - if (mOrientationParams.invertDimensions) { - return super.getIntrinsicWidth(); - } - return super.getIntrinsicHeight(); - } - - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - mApplyGravity = true; - } - - @Override - public void draw(Canvas canvas) { - if (mApplyGravity) { - Gravity.apply(getGravity(), getIntrinsicWidth(), getIntrinsicHeight(), getBounds(), - mDstRect); - mCenterX = mDstRect.centerX(); - mCenterY = mDstRect.centerY(); - if (mOrientationParams.invertDimensions) { - final Matrix matrix = new Matrix(); - matrix.setRotate(mOrientationParams.rotation, mCenterX, mCenterY); - final RectF rotatedRect = new RectF(mDstRect); - matrix.mapRect(rotatedRect); - mDstRect.set((int) rotatedRect.left, (int) rotatedRect.top, (int) rotatedRect.right, - (int) rotatedRect.bottom); - } - - mApplyGravity = false; - } - canvas.save(); - canvas.scale(mOrientationParams.scaleX, mOrientationParams.scaleY, mCenterX, mCenterY); - canvas.rotate(mOrientationParams.rotation, mCenterX, mCenterY); - canvas.drawBitmap(getBitmap(), (Rect) null, mDstRect, getPaint()); - canvas.restore(); - } -} diff --git a/src/com/android/messaging/ui/PagerViewHolder.java b/src/com/android/messaging/ui/PagerViewHolder.java deleted file mode 100644 index 2f33a0f..0000000 --- a/src/com/android/messaging/ui/PagerViewHolder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.view.View; -import android.view.ViewGroup; - -/** - * Holds reusable View(s) for a {@link FixedViewPagerAdapter} to display a page. By using - * reusable Views inside ViewPagers this allows us to get rid of nested fragments and the messy - * activity lifecycle problems they entail. - */ -public interface PagerViewHolder extends PersistentInstanceState { - /** Instructs the pager to clean up any view related resources - * @return the destroyed View so that the adapter may remove it from the container, or - * null if no View has been created. */ - View destroyView(); - - /** @return The view that presents the page view to the user */ - View getView(ViewGroup container); -} diff --git a/src/com/android/messaging/ui/PagingAwareViewPager.java b/src/com/android/messaging/ui/PagingAwareViewPager.java deleted file mode 100644 index cc7b2cd..0000000 --- a/src/com/android/messaging/ui/PagingAwareViewPager.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import com.android.messaging.util.UiUtils; - -/** - * A simple extension on the standard ViewPager which lets you turn paging on/off. - */ -public class PagingAwareViewPager extends ViewPager { - private boolean mPagingEnabled = true; - - public PagingAwareViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void setCurrentItem(int item, boolean smoothScroll) { - super.setCurrentItem(getRtlPosition(item), smoothScroll); - } - - @Override - public void setCurrentItem(int item) { - super.setCurrentItem(getRtlPosition(item)); - } - - @Override - public int getCurrentItem() { - int position = super.getCurrentItem(); - return getRtlPosition(position); - } - - /** - * Switches position in pager to be adjusted for if we are in RtL mode - * - * @param position - * @return position adjusted if in rtl mode - */ - protected int getRtlPosition(final int position) { - final PagerAdapter adapter = getAdapter(); - if (adapter != null && UiUtils.isRtlMode()) { - return adapter.getCount() - 1 - position; - } - return position; - } - - @Override - public boolean onTouchEvent(final MotionEvent event) { - if (!mPagingEnabled) { - return false; - } - return super.onTouchEvent(event); - } - - @Override - public boolean onInterceptTouchEvent(final MotionEvent event) { - if (!mPagingEnabled) { - return false; - } - return super.onInterceptTouchEvent(event); - } - - public void setPagingEnabled(final boolean enabled) { - this.mPagingEnabled = enabled; - } - - /** This prevents touch-less scrolling eg. while doing accessibility navigation. */ - @Override - public boolean canScrollHorizontally(int direction) { - if (mPagingEnabled) { - return super.canScrollHorizontally(direction); - } else { - return false; - } - } -} diff --git a/src/com/android/messaging/ui/PermissionCheckActivity.java b/src/com/android/messaging/ui/PermissionCheckActivity.java deleted file mode 100644 index e992a10..0000000 --- a/src/com/android/messaging/ui/PermissionCheckActivity.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.SystemClock; -import android.provider.Settings; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -/** - * Activity to check if the user has required permissions. If not, it will try to prompt the user - * to grant permissions. However, the OS may not actually prompt the user if the user had - * previously checked the "Never ask again" checkbox while denying the required permissions. - */ -public class PermissionCheckActivity extends Activity { - private static final int REQUIRED_PERMISSIONS_REQUEST_CODE = 1; - private static final long AUTOMATED_RESULT_THRESHOLD_MILLLIS = 250; - private static final String PACKAGE_URI_PREFIX = "package:"; - private long mRequestTimeMillis; - private TextView mNextView; - private TextView mSettingsView; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (redirectIfNeeded()) { - return; - } - - setContentView(R.layout.permission_check_activity); - UiUtils.setStatusBarColor(this, getColor(R.color.permission_check_activity_background)); - - findViewById(R.id.exit).setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - finish(); - } - }); - - mNextView = (TextView) findViewById(R.id.next); - mNextView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - tryRequestPermission(); - } - }); - - mSettingsView = (TextView) findViewById(R.id.settings); - mSettingsView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.parse(PACKAGE_URI_PREFIX + getPackageName())); - startActivity(intent); - } - }); - } - - @Override - public void onResume() { - super.onResume(); - - if (redirectIfNeeded()) { - return; - } - } - - private void tryRequestPermission() { - final String[] missingPermissions = OsUtil.getMissingRequiredPermissions(); - if (missingPermissions.length == 0) { - redirect(); - return; - } - - mRequestTimeMillis = SystemClock.elapsedRealtime(); - requestPermissions(missingPermissions, REQUIRED_PERMISSIONS_REQUEST_CODE); - } - - @Override - public void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { - if (requestCode == REQUIRED_PERMISSIONS_REQUEST_CODE) { - // We do not use grantResults as some of the granted permissions might have been - // revoked while the permissions dialog box was being shown for the missing permissions. - if (OsUtil.hasRequiredPermissions()) { - Factory.get().onRequiredPermissionsAcquired(); - redirect(); - } else { - final long currentTimeMillis = SystemClock.elapsedRealtime(); - // If the permission request completes very quickly, it must be because the system - // automatically denied. This can happen if the user had previously denied it - // and checked the "Never ask again" check box. - if ((currentTimeMillis - mRequestTimeMillis) < AUTOMATED_RESULT_THRESHOLD_MILLLIS) { - mNextView.setVisibility(View.GONE); - - mSettingsView.setVisibility(View.VISIBLE); - findViewById(R.id.enable_permission_procedure).setVisibility(View.VISIBLE); - } - } - } - } - - /** Returns true if the redirecting was performed */ - private boolean redirectIfNeeded() { - if (!OsUtil.hasRequiredPermissions()) { - return false; - } - - redirect(); - return true; - } - - private void redirect() { - UIIntents.get().launchConversationListActivity(this); - finish(); - } -} diff --git a/src/com/android/messaging/ui/PersistentInstanceState.java b/src/com/android/messaging/ui/PersistentInstanceState.java deleted file mode 100644 index 3cc1856..0000000 --- a/src/com/android/messaging/ui/PersistentInstanceState.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.os.Parcelable; - -/** - * Wraps around functionality to save, restore and reset a particular UI component's state. - */ -public interface PersistentInstanceState { - /** - * Saves necessary information about the current state of the instance to later restore - * the instance to its original state. - */ - Parcelable saveState(); - - /** - * Given a previously saved instance state, attempt to restore to its original view state. - */ - void restoreState(Parcelable restoredState); - - /** - * Called when any current/preserved state should be reset to zero state. - */ - void resetState(); -} diff --git a/src/com/android/messaging/ui/PersonItemView.java b/src/com/android/messaging/ui/PersonItemView.java deleted file mode 100644 index afd1a99..0000000 --- a/src/com/android/messaging/ui/PersonItemView.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.DetachableBinding; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.UiUtils; - -/** - * Shows a view for a "person" - could be a contact or a participant. This always shows a - * contact icon on the left, and the person's display name on the right. - * - * This view is always bound to an abstract PersonItemData class, so to use it for a specific - * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges - * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name). - */ -public class PersonItemView extends LinearLayout implements PersonItemDataListener, - OnLayoutChangeListener { - public interface PersonItemViewListener { - void onPersonClicked(PersonItemData data); - boolean onPersonLongClicked(PersonItemData data); - } - - protected final DetachableBinding mBinding; - private TextView mNameTextView; - private TextView mDetailsTextView; - private ContactIconView mContactIconView; - private View mDetailsContainer; - private PersonItemViewListener mListener; - private boolean mAvatarOnly; - - public PersonItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mBinding = BindingBase.createDetachableBinding(this); - LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true); - } - - @Override - protected void onFinishInflate() { - mNameTextView = (TextView) findViewById(R.id.name); - mDetailsTextView = (TextView) findViewById(R.id.details); - mContactIconView = (ContactIconView) findViewById(R.id.contact_icon); - mDetailsContainer = findViewById(R.id.details_container); - mNameTextView.addOnLayoutChangeListener(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mBinding.isBound()) { - mBinding.detach(); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mBinding.reAttachIfPossible(); - } - - /** - * Binds to a person item data which will provide us info to be displayed. - * @param personData the PersonItemData to be bound to. - */ - public void bind(final PersonItemData personData) { - if (mBinding.isBound()) { - if (mBinding.getData().equals(personData)) { - // Don't rebind if we are requesting the same data. - return; - } - mBinding.unbind(); - } - - if (personData != null) { - mBinding.bind(personData); - mBinding.getData().setListener(this); - - // Accessibility reason : in case phone numbers are mixed in the display name, - // we need to vocalize it for talkback. - final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber( - getResources(), getDisplayName()); - mNameTextView.setContentDescription(vocalizedDisplayName); - } - updateViewAppearance(); - } - - /** - * @return Display name, possibly comma-ellipsized. - */ - private String getDisplayName() { - final int width = mNameTextView.getMeasuredWidth(); - final String displayName = mBinding.getData().getDisplayName(); - if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) { - return displayName; - } - final String plusOneString = getContext().getString(R.string.plus_one); - final String plusNString = getContext().getString(R.string.plus_n); - return BidiFormatter.getInstance().unicodeWrap( - UiUtils.commaEllipsize( - displayName, - mNameTextView.getPaint(), - width, - plusOneString, - plusNString).toString(), - TextDirectionHeuristicsCompat.LTR); - } - - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, - final int bottom, final int oldLeft, final int oldTop, final int oldRight, - final int oldBottom) { - if (mBinding.isBound() && v == mNameTextView) { - setNameTextView(); - } - } - - /** - * When set to true, we display only the avatar of the person and hide everything else. - */ - public void setAvatarOnly(final boolean avatarOnly) { - mAvatarOnly = avatarOnly; - mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE); - } - - public boolean isAvatarOnly() { - return mAvatarOnly; - } - - public void setNameTextColor(final int color) { - mNameTextView.setTextColor(color); - } - - public void setDetailsTextColor(final int color) { - mDetailsTextView.setTextColor(color); - } - - public void setListener(final PersonItemViewListener listener) { - mListener = listener; - if (mListener == null) { - return; - } - setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - if (mListener != null && mBinding.isBound()) { - mListener.onPersonClicked(mBinding.getData()); - } - } - }); - final OnLongClickListener onLongClickListener = new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mListener != null && mBinding.isBound()) { - return mListener.onPersonLongClicked(mBinding.getData()); - } - return false; - } - }; - setOnLongClickListener(onLongClickListener); - mContactIconView.setOnLongClickListener(onLongClickListener); - } - - public void performClickOnAvatar() { - mContactIconView.performClick(); - } - - protected void updateViewAppearance() { - if (mBinding.isBound()) { - setNameTextView(); - - final String details = mBinding.getData().getDetails(); - if (TextUtils.isEmpty(details)) { - mDetailsTextView.setVisibility(GONE); - } else { - mDetailsTextView.setVisibility(VISIBLE); - mDetailsTextView.setText(details); - } - - mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(), - mBinding.getData().getContactId(), mBinding.getData().getLookupKey(), - mBinding.getData().getNormalizedDestination()); - } else { - mNameTextView.setText(""); - mContactIconView.setImageResourceUri(null); - } - } - - private void setNameTextView() { - final String displayName = getDisplayName(); - if (TextUtils.isEmpty(displayName)) { - mNameTextView.setVisibility(GONE); - } else { - mNameTextView.setVisibility(VISIBLE); - mNameTextView.setText(displayName); - } - } - - @Override - public void onPersonDataUpdated(final PersonItemData data) { - mBinding.ensureBound(data); - updateViewAppearance(); - } - - @Override - public void onPersonDataFailed(final PersonItemData data, final Exception exception) { - mBinding.ensureBound(data); - updateViewAppearance(); - } - - public Intent getClickIntent() { - return mBinding.getData().getClickIntent(); - } -} diff --git a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java b/src/com/android/messaging/ui/PlaceholderInsetDrawable.java deleted file mode 100644 index dda2e7b..0000000 --- a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; - -/** - * A "placeholder" drawable that has the same sizing properties as the real UI element it - * replaces. - * - * This is an InsetDrawable that takes a placeholder drawable (an animation list, or simply - * a color drawable) and place it in the center of the inset drawable that's sized to the - * requested source width and height of the image that it replaces. Unlike the base - * InsetDrawable, this implementation returns the true width and height of the real image - * that it's placeholding, instead of the intrinsic size of the contained drawable, so that - * when used in an ImageView, it may be positioned/scaled/cropped the same way the real - * image is. - */ -public class PlaceholderInsetDrawable extends InsetDrawable { - // The dimensions of the real image that this drawable is replacing. - private final int mSourceWidth; - private final int mSourceHeight; - - /** - * Given a source drawable, wraps it around in this placeholder drawable by placing the - * drawable at the center of the container if possible (or fill the container if the - * drawable doesn't have intrinsic size such as color drawable). - */ - public static PlaceholderInsetDrawable fromDrawable(final Drawable drawable, - final int sourceWidth, final int sourceHeight) { - final int drawableWidth = drawable.getIntrinsicWidth(); - final int drawableHeight = drawable.getIntrinsicHeight(); - final int insetHorizontal = drawableWidth < 0 || drawableWidth > sourceWidth ? - 0 : (sourceWidth - drawableWidth) / 2; - final int insetVertical = drawableHeight < 0 || drawableHeight > sourceHeight ? - 0 : (sourceHeight - drawableHeight) / 2; - return new PlaceholderInsetDrawable(drawable, insetHorizontal, insetVertical, - insetHorizontal, insetVertical, sourceWidth, sourceHeight); - } - - private PlaceholderInsetDrawable(final Drawable drawable, final int insetLeft, - final int insetTop, final int insetRight, final int insetBottom, - final int sourceWidth, final int sourceHeight) { - super(drawable, insetLeft, insetTop, insetRight, insetBottom); - mSourceWidth = sourceWidth; - mSourceHeight = sourceHeight; - } - - @Override - public int getIntrinsicWidth() { - return mSourceWidth; - } - - @Override - public int getIntrinsicHeight() { - return mSourceHeight; - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/PlainTextEditText.java b/src/com/android/messaging/ui/PlainTextEditText.java deleted file mode 100644 index 8d6e784..0000000 --- a/src/com/android/messaging/ui/PlainTextEditText.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.util.AttributeSet; -import android.widget.EditText; - -/** - * We want the EditText used in Conversations to convert text to plain text on paste. This - * conversion would happen anyway on send, so without this class it could appear to the user - * that we would send e.g. bold or italic formatting, but in the sent message it would just be - * plain text. - */ -public class PlainTextEditText extends EditText { - private static final char OBJECT_UNICODE = '\uFFFC'; - - public PlainTextEditText(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - // Intercept and modify the paste event. Let everything else through unchanged. - @Override - public boolean onTextContextMenuItem(final int id) { - if (id == android.R.id.paste) { - // We can use this to know where the text position was originally before we pasted - final int selectionStartPrePaste = getSelectionStart(); - - // Let the EditText's normal paste routine fire, then modify the content after. - // This is simpler than re-implementing the paste logic, which we'd have to do - // if we want to get the text from the clipboard ourselves and then modify it. - - final boolean result = super.onTextContextMenuItem(id); - CharSequence text = getText(); - int selectionStart = getSelectionStart(); - int selectionEnd = getSelectionEnd(); - - // There is an option in the Chrome mobile app to copy image; however, instead of the - // image in the form of the uri, Chrome gives us the html source for the image, which - // the platform paste code turns into the unicode object character. The below section - // of code looks for that edge case and replaces it with the url for the image. - final int startIndex = selectionStart - 1; - final int pasteStringLength = selectionStart - selectionStartPrePaste; - // Only going to handle the case where the pasted object is the image - if (pasteStringLength == 1 && text.charAt(startIndex) == OBJECT_UNICODE) { - final ClipboardManager clipboard = - (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - final ClipData clip = clipboard.getPrimaryClip(); - if (clip != null) { - ClipData.Item item = clip.getItemAt(0); - StringBuilder sb = new StringBuilder(text); - final String url = item.getText().toString(); - sb.replace(selectionStartPrePaste, selectionStart, url); - text = sb.toString(); - selectionStart = selectionStartPrePaste + url.length(); - selectionEnd = selectionStart; - } - } - - // This removes the formatting due to the conversion to string. - setText(text.toString(), BufferType.EDITABLE); - - // Restore the cursor selection state. - setSelection(selectionStart, selectionEnd); - return result; - } else { - return super.onTextContextMenuItem(id); - } - } -} diff --git a/src/com/android/messaging/ui/PlaybackStateView.java b/src/com/android/messaging/ui/PlaybackStateView.java deleted file mode 100644 index 8d9aac7..0000000 --- a/src/com/android/messaging/ui/PlaybackStateView.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -/** - * An interface for a UI element ("View") that reflects the playback state of a piece of media - * content. It needs to support the ability to take common playback commands (play, pause, stop, - * restart) and reflect the state in UI (through timer or progress bar etc.) - */ -public interface PlaybackStateView { - /** - * Restart the playback. - */ - void restart(); - - /** - * Reset ("stop") the playback to the starting position. - */ - void reset(); - - /** - * Resume the playback, or start it if it hasn't been started yet. - */ - void resume(); - - /** - * Pause the playback. - */ - void pause(); -} diff --git a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java b/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java deleted file mode 100644 index c731164..0000000 --- a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Intent; -import android.os.Bundle; -import android.telephony.TelephonyManager; - -import com.android.messaging.datamodel.NoConfirmationSmsSendService; -import com.android.messaging.util.LogUtil; - -public class RemoteInputEntrypointActivity extends BaseBugleActivity { - private static final String TAG = LogUtil.BUGLE_TAG; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - if (intent == null) { - LogUtil.w(TAG, "No intent attached"); - setResult(RESULT_CANCELED); - finish(); - return; - } - - // Perform some action depending on the intent - String action = intent.getAction(); - if (Intent.ACTION_SENDTO.equals(action)) { - // Build and send the intent - final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class); - sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE); - sendIntent.putExtras(intent); - // Wear apparently passes all of its extras via the clip data. Must pass it along. - sendIntent.setClipData(intent.getClipData()); - startService(sendIntent); - setResult(RESULT_OK); - } else { - LogUtil.w(TAG, "Unrecognized intent action: " + action); - setResult(RESULT_CANCELED); - } - // This activity should never stick around after processing the intent - finish(); - } -} diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java b/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java deleted file mode 100644 index 6b3e84b..0000000 --- a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.FragmentTransaction; -import android.os.Bundle; - -/** - * Activity to contain the dialog of warning sms storage low. - */ -public class SmsStorageLowWarningActivity extends BaseBugleFragmentActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - SmsStorageLowWarningFragment fragment = - SmsStorageLowWarningFragment.newInstance(); - ft.add(fragment, null/*tag*/); - ft.commit(); - } -} diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java b/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java deleted file mode 100644 index 3ebfdcf..0000000 --- a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.action.HandleLowStorageAction; -import com.android.messaging.sms.SmsReleaseStorage; -import com.android.messaging.sms.SmsReleaseStorage.Duration; -import com.android.messaging.sms.SmsStorageStatusManager; -import com.android.messaging.util.Assert; -import com.google.common.collect.Lists; - -import java.util.List; - -/** - * Dialog to show the sms storage low warning - */ -public class SmsStorageLowWarningFragment extends Fragment { - private SmsStorageLowWarningFragment() { - } - - public static SmsStorageLowWarningFragment newInstance() { - return new SmsStorageLowWarningFragment(); - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - final ChooseActionDialogFragment dialog = ChooseActionDialogFragment.newInstance(); - dialog.setTargetFragment(this, 0/*requestCode*/); - dialog.show(ft, null/*tag*/); - } - - /** - * Perform confirm action for a specific action - * - * @param actionIndex - */ - private void confirm(final int actionIndex) { - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - final ConfirmationDialog dialog = ConfirmationDialog.newInstance(actionIndex); - dialog.setTargetFragment(this, 0/*requestCode*/); - dialog.show(ft, null/*tag*/); - } - - /** - * The dialog is cancelled at any step - */ - private void cancel() { - getActivity().finish(); - } - - /** - * The dialog to show for user to choose what delete actions to take when storage is low - */ - private static class ChooseActionDialogFragment extends DialogFragment { - public static ChooseActionDialogFragment newInstance() { - return new ChooseActionDialogFragment(); - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - final LayoutInflater inflater = getActivity().getLayoutInflater(); - final View dialogLayout = inflater.inflate( - R.layout.sms_storage_low_warning_dialog, null); - final ListView actionListView = (ListView) dialogLayout.findViewById( - R.id.free_storage_action_list); - final List actions = loadFreeStorageActions(getActivity().getResources()); - final ActionListAdapter listAdapter = new ActionListAdapter(getActivity(), actions); - actionListView.setAdapter(listAdapter); - - builder.setTitle(R.string.sms_storage_low_title) - .setView(dialogLayout) - .setNegativeButton(R.string.ignore, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - - final Dialog dialog = builder.create(); - dialog.setCanceledOnTouchOutside(false); - return dialog; - } - - @Override - public void onCancel(final DialogInterface dialog) { - ((SmsStorageLowWarningFragment) getTargetFragment()).cancel(); - } - - private class ActionListAdapter extends ArrayAdapter { - public ActionListAdapter(final Context context, final List actions) { - super(context, R.layout.sms_free_storage_action_item_view, actions); - } - - @Override - public View getView(final int position, final View view, final ViewGroup parent) { - TextView actionItemView; - if (view == null || !(view instanceof TextView)) { - final LayoutInflater inflater = LayoutInflater.from(getContext()); - actionItemView = (TextView) inflater.inflate( - R.layout.sms_free_storage_action_item_view, parent, false); - } else { - actionItemView = (TextView) view; - } - - final String action = getItem(position); - actionItemView.setText(action); - actionItemView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - dismiss(); - ((SmsStorageLowWarningFragment) getTargetFragment()).confirm(position); - } - }); - return actionItemView; - } - } - } - - private static final String KEY_ACTION_INDEX = "action_index"; - - /** - * The dialog to confirm user's delete action - */ - private static class ConfirmationDialog extends DialogFragment { - private Duration mDuration; - private String mDurationString; - - public static ConfirmationDialog newInstance(final int actionIndex) { - final ConfirmationDialog dialog = new ConfirmationDialog(); - final Bundle args = new Bundle(); - args.putInt(KEY_ACTION_INDEX, actionIndex); - dialog.setArguments(args); - return dialog; - } - - @Override - public void onCancel(final DialogInterface dialog) { - ((SmsStorageLowWarningFragment) getTargetFragment()).cancel(); - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - mDuration = SmsReleaseStorage.parseMessageRetainingDuration(); - mDurationString = SmsReleaseStorage.getMessageRetainingDurationString(mDuration); - - final int actionIndex = getArguments().getInt(KEY_ACTION_INDEX); - if (actionIndex < 0 || actionIndex > 1) { - return null; - } - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.sms_storage_low_title) - .setMessage(getConfirmDialogMessage(actionIndex)) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - dismiss(); - ((SmsStorageLowWarningFragment) getTargetFragment()).cancel(); - } - }) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - dismiss(); - handleAction(actionIndex); - getActivity().finish(); - SmsStorageStatusManager.cancelStorageLowNotification(); - } - }); - return builder.create(); - } - - private void handleAction(final int actionIndex) { - final long durationInMillis = - SmsReleaseStorage.durationToTimeInMillis(mDuration); - switch (actionIndex) { - case 0: - HandleLowStorageAction.handleDeleteMediaMessages(durationInMillis); - break; - - case 1: - HandleLowStorageAction.handleDeleteOldMessages(durationInMillis); - break; - - default: - Assert.fail("Unsupported action"); - break; - } - } - - /** - * Get the confirm dialog text for a specific delete action - * @param index The action index - * @return - */ - private String getConfirmDialogMessage(final int index) { - switch (index) { - case 0: - return getString(R.string.delete_all_media_confirmation, mDurationString); - case 1: - return getString(R.string.delete_oldest_messages_confirmation, mDurationString); - case 2: - return getString(R.string.auto_delete_oldest_messages_confirmation, - mDurationString); - } - throw new IllegalArgumentException( - "SmsStorageLowWarningFragment: invalid action index " + index); - } - } - - /** - * Load the text of delete message actions - * - * @param resources - * @return - */ - private static List loadFreeStorageActions(final Resources resources) { - final Duration duration = SmsReleaseStorage.parseMessageRetainingDuration(); - final String durationString = SmsReleaseStorage.getMessageRetainingDurationString(duration); - final List actions = Lists.newArrayList(); - actions.add(resources.getString(R.string.delete_all_media)); - actions.add(resources.getString(R.string.delete_oldest_messages, durationString)); - - // TODO: Auto-purging is disabled for Bugle V1. - // actions.add(resources.getString(R.string.auto_delete_oldest_messages, durationString)); - return actions; - } -} diff --git a/src/com/android/messaging/ui/SnackBar.java b/src/com/android/messaging/ui/SnackBar.java deleted file mode 100644 index a278040..0000000 --- a/src/com/android/messaging/ui/SnackBar.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup.MarginLayoutParams; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.util.Assert; - -import java.util.ArrayList; -import java.util.List; - -public class SnackBar { - public static final int LONG_DURATION_IN_MS = 5000; - public static final int SHORT_DURATION_IN_MS = 1000; - public static final int MAX_DURATION_IN_MS = 10000; - - public interface SnackBarListener { - void onActionClick(); - } - - /** - * Defines an action to be performed when the user clicks on the action button on the snack bar - */ - public static class Action { - private final Runnable mActionRunnable; - private final String mActionLabel; - - public final static int SNACK_BAR_UNDO = 0; - public final static int SNACK_BAR_RETRY = 1; - - private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) { - mActionRunnable = actionRunnable; - mActionLabel = actionLabel; - } - - Runnable getActionRunnable() { - return mActionRunnable; - } - - String getActionLabel() { - return mActionLabel; - } - - public static Action createUndoAction(final Runnable undoRunnable) { - return createCustomAction(undoRunnable, Factory.get().getApplicationContext() - .getString(R.string.snack_bar_undo)); - } - - public static Action createRetryAction(final Runnable retryRunnable) { - return createCustomAction(retryRunnable, Factory.get().getApplicationContext() - .getString(R.string.snack_bar_retry)); - } - - - public static Action createCustomAction(final Runnable runnable, final String actionLabel) { - return new Action(runnable, actionLabel); - } - } - - /** - * Defines the placement of the snack bar (e.g. anchored view, anchor gravity). - */ - public static class Placement { - private final View mAnchorView; - private final boolean mAnchorAbove; - - private Placement(@NonNull final View anchorView, final boolean anchorAbove) { - Assert.notNull(anchorView); - mAnchorView = anchorView; - mAnchorAbove = anchorAbove; - } - - public View getAnchorView() { - return mAnchorView; - } - - public boolean getAnchorAbove() { - return mAnchorAbove; - } - - /** - * Anchor the snack bar above the given {@code anchorView}. - */ - public static Placement above(final View anchorView) { - return new Placement(anchorView, true); - } - - /** - * Anchor the snack bar below the given {@code anchorView}. - */ - public static Placement below(final View anchorView) { - return new Placement(anchorView, false); - } - } - - public static class Builder { - private static final List NO_INTERACTIONS = - new ArrayList(); - - private final Context mContext; - private final SnackBarManager mSnackBarManager; - - private String mSnackBarMessage; - private int mDuration = LONG_DURATION_IN_MS; - private List mInteractions = NO_INTERACTIONS; - private Action mAction; - private Placement mPlacement; - // The parent view is only used to get a window token and doesn't affect the layout - private View mParentView; - - public Builder(final SnackBarManager snackBarManager, final View parentView) { - Assert.notNull(snackBarManager); - Assert.notNull(parentView); - mSnackBarManager = snackBarManager; - mContext = parentView.getContext(); - mParentView = parentView; - } - - public Builder setText(final String snackBarMessage) { - Assert.isTrue(!TextUtils.isEmpty(snackBarMessage)); - mSnackBarMessage = snackBarMessage; - return this; - } - - public Builder setAction(final Action action) { - mAction = action; - return this; - } - - /** - * Sets the duration to show this toast for in milliseconds. - */ - public Builder setDuration(final int duration) { - Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS); - mDuration = duration; - return this; - } - - /** - * Sets the components that this toast's animation will interact with. These components may - * be animated to make room for the toast. - */ - public Builder withInteractions(final List interactions) { - mInteractions = interactions; - return this; - } - - /** - * Place the snack bar with the given placement requirement. - */ - public Builder withPlacement(final Placement placement) { - Assert.isNull(mPlacement); - mPlacement = placement; - return this; - } - - public SnackBar build() { - return new SnackBar(this); - } - - public void show() { - mSnackBarManager.show(build()); - } - } - - private final View mRootView; - private final Context mContext; - private final View mSnackBarView; - private final String mText; - private final int mDuration; - private final List mInteractions; - private final Action mAction; - private final Placement mPlacement; - private final TextView mActionTextView; - private final TextView mMessageView; - private final FrameLayout mMessageWrapper; - private final View mParentView; - - private SnackBarListener mListener; - - private SnackBar(final Builder builder) { - mContext = builder.mContext; - mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar, - null /* WindowManager will show this in show() below */); - mSnackBarView = mRootView.findViewById(R.id.snack_bar); - mText = builder.mSnackBarMessage; - mDuration = builder.mDuration; - mAction = builder.mAction; - mPlacement = builder.mPlacement; - mParentView = builder.mParentView; - if (builder.mInteractions == null) { - mInteractions = new ArrayList(); - } else { - mInteractions = builder.mInteractions; - } - - mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action); - mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message); - mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper); - - setUpButton(); - setUpTextLines(); - } - - public Context getContext() { - return mContext; - } - - public View getRootView() { - return mRootView; - } - - public View getParentView() { - return mParentView; - } - - public View getSnackBarView() { - return mSnackBarView; - } - - public String getMessageText() { - return mText; - } - - public String getActionLabel() { - if (mAction == null) { - return null; - } - return mAction.getActionLabel(); - } - - public int getDuration() { - return mDuration; - } - - public Placement getPlacement() { - return mPlacement; - } - - public List getInteractions() { - return mInteractions; - } - - public void setEnabled(final boolean enabled) { - mActionTextView.setClickable(enabled); - } - - public void setListener(final SnackBarListener snackBarListener) { - mListener = snackBarListener; - } - - private void setUpButton() { - if (mAction == null || mAction.getActionRunnable() == null) { - mActionTextView.setVisibility(View.GONE); - // In the XML layout we add left/right padding to the button to add space between - // the message text and the button and on the right side we add padding to put space - // between the button and the edge of the snack bar. This is so the button can use the - // padding area as part of it's click target. Since we have no button, we need to put - // some margin on the right side. While the left margin is already set on the wrapper, - // we're setting it again to not have to make a special case for RTL. - final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams(); - final int leftRightMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.snack_bar_left_right_margin); - lp.leftMargin = leftRightMargin; - lp.rightMargin = leftRightMargin; - mMessageWrapper.setLayoutParams(lp); - } else { - mActionTextView.setVisibility(View.VISIBLE); - mActionTextView.setText(mAction.getActionLabel()); - mActionTextView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mAction.getActionRunnable().run(); - if (mListener != null) { - mListener.onActionClick(); - } - } - }); - } - } - - private void setUpTextLines() { - if (mText == null) { - mMessageView.setVisibility(View.GONE); - } else { - mMessageView.setVisibility(View.VISIBLE); - mMessageView.setText(mText); - } - } -} diff --git a/src/com/android/messaging/ui/SnackBarInteraction.java b/src/com/android/messaging/ui/SnackBarInteraction.java deleted file mode 100644 index f723caa..0000000 --- a/src/com/android/messaging/ui/SnackBarInteraction.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.view.Gravity; -import android.view.View; -import android.view.ViewPropertyAnimator; - -import com.google.common.base.Preconditions; - -/** - * An interface that defines how a component can be animated with an {@link SnackBar}. - */ -public interface SnackBarInteraction { - /** - * Returns the animator that will be run in reaction to the given SnackBar being shown. - * - * Implementations may return null here if it determines that the given SnackBar does not need - * to animate this component. - */ - ViewPropertyAnimator animateOnSnackBarShow(SnackBar snackBar); - - /** - * Returns the animator that will be run in reaction to the given SnackBar being dismissed. - * - * Implementations may return null here if it determines that the given SnackBar does not need - * to animate this component. - */ - ViewPropertyAnimator animateOnSnackBarDismiss(SnackBar snackBar); - - /** - * A basic implementation of {@link SnackBarInteraction} that assumes that the - * {@link SnackBar} is always shown with {@link Gravity#BOTTOM} and that the provided View will - * always need to be translated up to make room for the SnackBar. - */ - public static class BasicSnackBarInteraction implements SnackBarInteraction { - private final View mView; - - public BasicSnackBarInteraction(final View view) { - mView = Preconditions.checkNotNull(view); - } - - @Override - public ViewPropertyAnimator animateOnSnackBarShow(final SnackBar snackBar) { - final View rootView = snackBar.getRootView(); - return mView.animate().translationY(-rootView.getMeasuredHeight()); - } - - @Override - public ViewPropertyAnimator animateOnSnackBarDismiss(final SnackBar snackBar) { - return mView.animate().translationY(0); - } - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/SnackBarManager.java b/src/com/android/messaging/ui/SnackBarManager.java deleted file mode 100644 index e107999..0000000 --- a/src/com/android/messaging/ui/SnackBarManager.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.view.WindowManager; -import android.widget.PopupWindow; -import android.widget.PopupWindow.OnDismissListener; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.ui.SnackBar.Placement; -import com.android.messaging.ui.SnackBar.SnackBarListener; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.TextUtil; -import com.android.messaging.util.UiUtils; -import com.google.common.base.Joiner; - -import java.util.List; - -public class SnackBarManager { - - private static SnackBarManager sInstance; - - public static SnackBarManager get() { - if (sInstance == null) { - synchronized (SnackBarManager.class) { - if (sInstance == null) { - sInstance = new SnackBarManager(); - } - } - } - return sInstance; - } - - private final Runnable mDismissRunnable = new Runnable() { - @Override - public void run() { - dismiss(); - } - }; - - private final OnTouchListener mDismissOnTouchListener = new OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent event) { - // Dismiss the {@link SnackBar} but don't consume the event. - dismiss(); - return false; - } - }; - - private final SnackBarListener mDismissOnUserTapListener = new SnackBarListener() { - @Override - public void onActionClick() { - dismiss(); - } - }; - - private final int mTranslationDurationMs; - private final Handler mHideHandler; - - private SnackBar mCurrentSnackBar; - private SnackBar mLatestSnackBar; - private SnackBar mNextSnackBar; - private boolean mIsCurrentlyDismissing; - private PopupWindow mPopupWindow; - - private SnackBarManager() { - mTranslationDurationMs = Factory.get().getApplicationContext().getResources().getInteger( - R.integer.snackbar_translation_duration_ms); - mHideHandler = new Handler(); - } - - public SnackBar getLatestSnackBar() { - return mLatestSnackBar; - } - - public SnackBar.Builder newBuilder(final View parentView) { - return new SnackBar.Builder(this, parentView); - } - - /** - * The given snackBar is not guaranteed to be shown. If the previous snackBar is animating away, - * and another snackBar is requested to show after this one, this snackBar will be skipped. - */ - public void show(final SnackBar snackBar) { - Assert.notNull(snackBar); - - if (mCurrentSnackBar != null) { - LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar, but currentSnackBar was not null."); - - // Dismiss the current snack bar. That will cause the next snack bar to be shown on - // completion. - mNextSnackBar = snackBar; - mLatestSnackBar = snackBar; - dismiss(); - return; - } - - mCurrentSnackBar = snackBar; - mLatestSnackBar = snackBar; - - // We want to know when either button was tapped so we can dismiss. - snackBar.setListener(mDismissOnUserTapListener); - - // Cancel previous dismisses & set dismiss for the delay time. - mHideHandler.removeCallbacks(mDismissRunnable); - mHideHandler.postDelayed(mDismissRunnable, snackBar.getDuration()); - - snackBar.setEnabled(false); - - // For some reason, the addView function does not respect layoutParams. - // We need to explicitly set it first here. - final View rootView = snackBar.getRootView(); - - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar: " + snackBar); - } - // Measure the snack bar root view so we know how much to translate by. - measureSnackBar(snackBar); - mPopupWindow = new PopupWindow(snackBar.getContext()); - mPopupWindow.setWidth(LayoutParams.MATCH_PARENT); - mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT); - mPopupWindow.setBackgroundDrawable(null); - mPopupWindow.setContentView(rootView); - final Placement placement = snackBar.getPlacement(); - if (placement == null) { - mPopupWindow.showAtLocation( - snackBar.getParentView(), Gravity.BOTTOM | Gravity.START, - 0, getScreenBottomOffset(snackBar)); - } else { - final View anchorView = placement.getAnchorView(); - - // You'd expect PopupWindow.showAsDropDown to ensure the popup moves with the anchor - // view, which it does for scrolling, but not layout changes, so we have to manually - // update while the snackbar is showing - final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mPopupWindow.update(anchorView, 0, getRelativeOffset(snackBar), - anchorView.getWidth(), LayoutParams.WRAP_CONTENT); - } - }; - anchorView.getViewTreeObserver().addOnGlobalLayoutListener(listener); - mPopupWindow.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss() { - anchorView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); - } - }); - mPopupWindow.showAsDropDown(anchorView, 0, getRelativeOffset(snackBar)); - } - - - // Animate the toast bar into view. - placeSnackBarOffScreen(snackBar); - animateSnackBarOnScreen(snackBar).withEndAction(new Runnable() { - @Override - public void run() { - mCurrentSnackBar.setEnabled(true); - makeCurrentSnackBarDismissibleOnTouch(); - // Fire an accessibility event as needed - String snackBarText = snackBar.getMessageText(); - if (!TextUtils.isEmpty(snackBarText) && - TextUtils.getTrimmedLength(snackBarText) > 0) { - snackBarText = snackBarText.trim(); - final String snackBarActionText = snackBar.getActionLabel(); - if (!TextUtil.isAllWhitespace(snackBarActionText)) { - snackBarText = Joiner.on(", ").join(snackBarText, snackBarActionText); - } - AccessibilityUtil.announceForAccessibilityCompat(snackBar.getSnackBarView(), - null /*accessibilityManager*/, snackBarText); - } - } - }); - - // Animate any interaction views out of the way. - animateInteractionsOnShow(snackBar); - } - - /** - * Dismisses the current toast that is showing. If there is a toast waiting to be shown, that - * toast will be shown when the current one has been dismissed. - */ - public void dismiss() { - mHideHandler.removeCallbacks(mDismissRunnable); - - if (mCurrentSnackBar == null || mIsCurrentlyDismissing) { - return; - } - - final SnackBar snackBar = mCurrentSnackBar; - - LogUtil.d(LogUtil.BUGLE_TAG, "Dismissing snack bar."); - mIsCurrentlyDismissing = true; - - snackBar.setEnabled(false); - - // Animate the toast bar down. - final View rootView = snackBar.getRootView(); - animateSnackBarOffScreen(snackBar).withEndAction(new Runnable() { - @Override - public void run() { - rootView.setVisibility(View.GONE); - try { - mPopupWindow.dismiss(); - } catch (IllegalArgumentException e) { - // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity - // has already ended while we were animating - } - - mCurrentSnackBar = null; - mIsCurrentlyDismissing = false; - - // Show the next toast if one is waiting. - if (mNextSnackBar != null) { - final SnackBar localNextSnackBar = mNextSnackBar; - mNextSnackBar = null; - show(localNextSnackBar); - } - } - }); - - // Animate any interaction views back. - animateInteractionsOnDismiss(snackBar); - } - - private void makeCurrentSnackBarDismissibleOnTouch() { - // Set touching on the entire view, the {@link SnackBar} itself, as - // well as the button's dismiss the toast. - mCurrentSnackBar.getRootView().setOnTouchListener(mDismissOnTouchListener); - mCurrentSnackBar.getSnackBarView().setOnTouchListener(mDismissOnTouchListener); - } - - private void measureSnackBar(final SnackBar snackBar) { - final View rootView = snackBar.getRootView(); - final Point displaySize = new Point(); - getWindowManager(snackBar.getContext()).getDefaultDisplay().getSize(displaySize); - final int widthSpec = ViewGroup.getChildMeasureSpec( - MeasureSpec.makeMeasureSpec(displaySize.x, MeasureSpec.EXACTLY), - 0, LayoutParams.MATCH_PARENT); - final int heightSpec = ViewGroup.getChildMeasureSpec( - MeasureSpec.makeMeasureSpec(displaySize.y, MeasureSpec.EXACTLY), - 0, LayoutParams.WRAP_CONTENT); - rootView.measure(widthSpec, heightSpec); - } - - private void placeSnackBarOffScreen(final SnackBar snackBar) { - final View rootView = snackBar.getRootView(); - final View snackBarView = snackBar.getSnackBarView(); - snackBarView.setTranslationY(rootView.getMeasuredHeight()); - } - - private ViewPropertyAnimator animateSnackBarOnScreen(final SnackBar snackBar) { - final View snackBarView = snackBar.getSnackBarView(); - return normalizeAnimator(snackBarView.animate()).translationX(0).translationY(0); - } - - private ViewPropertyAnimator animateSnackBarOffScreen(final SnackBar snackBar) { - final View rootView = snackBar.getRootView(); - final View snackBarView = snackBar.getSnackBarView(); - return normalizeAnimator(snackBarView.animate()).translationY(rootView.getHeight()); - } - - private void animateInteractionsOnShow(final SnackBar snackBar) { - final List interactions = snackBar.getInteractions(); - for (final SnackBarInteraction interaction : interactions) { - if (interaction != null) { - final ViewPropertyAnimator animator = interaction.animateOnSnackBarShow(snackBar); - if (animator != null) { - normalizeAnimator(animator); - } - } - } - } - - private void animateInteractionsOnDismiss(final SnackBar snackBar) { - final List interactions = snackBar.getInteractions(); - for (final SnackBarInteraction interaction : interactions) { - if (interaction != null) { - final ViewPropertyAnimator animator = - interaction.animateOnSnackBarDismiss(snackBar); - if (animator != null) { - normalizeAnimator(animator); - } - } - } - } - - private ViewPropertyAnimator normalizeAnimator(final ViewPropertyAnimator animator) { - return animator - .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR) - .setDuration(mTranslationDurationMs); - } - - private WindowManager getWindowManager(final Context context) { - return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - } - - /** - * Get the offset from the bottom of the screen where the snack bar should be placed. - */ - private int getScreenBottomOffset(final SnackBar snackBar) { - final WindowManager windowManager = getWindowManager(snackBar.getContext()); - final DisplayMetrics displayMetrics = new DisplayMetrics(); - if (OsUtil.isAtLeastL()) { - windowManager.getDefaultDisplay().getRealMetrics(displayMetrics); - } else { - windowManager.getDefaultDisplay().getMetrics(displayMetrics); - } - final int screenHeight = displayMetrics.heightPixels; - - if (OsUtil.isAtLeastL()) { - // In L, the navigation bar is included in the space for the popup window, so we have to - // offset by the size of the navigation bar - final Rect displayRect = new Rect(); - snackBar.getParentView().getRootView().getWindowVisibleDisplayFrame(displayRect); - return screenHeight - displayRect.bottom; - } - - return 0; - } - - private int getRelativeOffset(final SnackBar snackBar) { - final Placement placement = snackBar.getPlacement(); - Assert.notNull(placement); - final View anchorView = placement.getAnchorView(); - if (placement.getAnchorAbove()) { - return -snackBar.getRootView().getMeasuredHeight() - anchorView.getHeight(); - } else { - // Use the default dropdown positioning - return 0; - } - } -} diff --git a/src/com/android/messaging/ui/TestActivity.java b/src/com/android/messaging/ui/TestActivity.java deleted file mode 100644 index 1693660..0000000 --- a/src/com/android/messaging/ui/TestActivity.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Fragment; -import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.view.View; -import android.view.WindowManager; -import android.widget.FrameLayout; - -import com.android.messaging.R; -import com.android.messaging.util.LogUtil; -import com.google.common.annotations.VisibleForTesting; - -/** - * An empty activity that can be used to host a fragment or view for unit testing purposes. Lives in - * app code vs test code due to requirement of ActivityInstrumentationTestCase2. - */ -public class TestActivity extends FragmentActivity { - private FragmentEventListener mFragmentEventListener; - - public interface FragmentEventListener { - public void onAttachFragment(Fragment fragment); - } - - @Override - protected void onCreate(final Bundle bundle) { - super.onCreate(bundle); - - if (bundle != null) { - // The test case may have configured the fragment, and recreating the activity will - // lose that configuration. The real activity is the only activity that would know - // how to reapply that configuration. - throw new IllegalStateException("TestActivity cannot get recreated"); - } - - // There is a race condition, but this often makes it possible for tests to run with the - // key guard up - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - - setContentView(R.layout.test_activity); - } - - @VisibleForTesting - public void setFragment(final Fragment fragment) { - LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setFragment"); - getFragmentManager() - .beginTransaction() - .replace(R.id.test_content, fragment) - .commit(); - getFragmentManager().executePendingTransactions(); - } - - @VisibleForTesting - public void setView(final View view) { - LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setView"); - ((FrameLayout) findViewById(R.id.test_content)).addView(view); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - if (mFragmentEventListener != null) { - mFragmentEventListener.onAttachFragment(fragment); - } - } - - public void setFragmentEventListener(final FragmentEventListener fragmentEventListener) { - mFragmentEventListener = fragmentEventListener; - } -} diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java deleted file mode 100644 index e5f8a52..0000000 --- a/src/com/android/messaging/ui/UIIntents.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Activity; -import android.app.Fragment; -import android.app.PendingIntent; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.graphics.Point; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.util.ConversationIdSet; - -/** - * A central repository of Intents used to start activities. - */ -public abstract class UIIntents { - public static UIIntents get() { - return Factory.get().getUIIntents(); - } - - // Intent extras - public static final String UI_INTENT_EXTRA_CONVERSATION_ID = "conversation_id"; - - // Sending draft data (from share intent / message forwarding) to the ConversationActivity. - public static final String UI_INTENT_EXTRA_DRAFT_DATA = "draft_data"; - - // The request code for picking image from the Document picker. - public static final int REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER = 1400; - - // Indicates what type of notification this applies to (See BugleNotifications: - // UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL) - public static final String UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE = "notifications_update"; - - // Pass a set of conversation id's. - public static final String UI_INTENT_EXTRA_CONVERSATION_ID_SET = "conversation_id_set"; - - // Sending class zero message to its activity - public static final String UI_INTENT_EXTRA_MESSAGE_VALUES = "message_values"; - - // For the widget to go to the ConversationList from the Conversation. - public static final String UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST = "goto_conv_list"; - - // Indicates whether a conversation is launched with custom transition. - public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition"; - - public static final String ACTION_RESET_NOTIFICATIONS = - "com.android.messaging.reset_notifications"; - - // Sending VCard uri to VCard detail activity - public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri"; - - public static final String CMAS_COMPONENT = "com.android.cellbroadcastreceiver"; - - // Intent action for local broadcast receiver for conversation self id change. - public static final String CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION = - "conversation_self_id_change"; - - // Conversation self id - public static final String UI_INTENT_EXTRA_CONVERSATION_SELF_ID = "conversation_self_id"; - - // For opening an APN editor on a particular row in the apn database. - public static final String UI_INTENT_EXTRA_APN_ROW_ID = "apn_row_id"; - - // Subscription id - public static final String UI_INTENT_EXTRA_SUB_ID = "sub_id"; - - // Per-Subscription setting activity title - public static final String UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE = - "per_sub_setting_title"; - - // Is application settings launched as the top level settings activity? - public static final String UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS = "top_level_settings"; - - // Sending attachment uri from widget - public static final String UI_INTENT_EXTRA_ATTACHMENT_URI = "attachment_uri"; - - // Sending attachment content type from widget - public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type"; - - public static final String ACTION_WIDGET_CONVERSATION = - "com.android.messaging.widget_conversation:"; - - public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms"; - - public static final String UI_INTENT_EXTRA_SELF_ID = "self_id"; - - // Message position to scroll to. - public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position"; - - /** - * Launch the permission check activity - */ - public abstract void launchPermissionCheckActivity(final Context context); - - public abstract void launchConversationListActivity(final Context context); - - /** - * Launch an activity to show a conversation. This method by default provides no additional - * activity options. - */ - public void launchConversationActivity(final Context context, - final String conversationId, final MessageData draft) { - launchConversationActivity(context, conversationId, draft, null, - false /* withCustomTransition */); - } - - /** - * Launch an activity to show a conversation. - */ - public abstract void launchConversationActivity(final Context context, - final String conversationId, final MessageData draft, final Bundle activityOptions, - final boolean withCustomTransition); - - - /** - * Launch an activity to show conversation with conversation list in back stack. - */ - public abstract void launchConversationActivityWithParentStack(Context context, - String conversationId, String smsBody); - - /** - * Launch an activity to show a conversation as a new task. - */ - public abstract void launchConversationActivityNewTask(final Context context, - final String conversationId); - - /** - * Launch an activity to start a new conversation - */ - public abstract void launchCreateNewConversationActivity(final Context context, - final MessageData draft); - - /** - * Launch debug activity to set MMS config options. - */ - public abstract void launchDebugMmsConfigActivity(final Context context); - - /** - * Launch an activity to change settings. - */ - public abstract void launchSettingsActivity(final Context context); - - /** - * Launch an activity to add a contact with a given destination. - */ - public abstract void launchAddContactActivity(final Context context, final String destination); - - /** - * Launch an activity to show the document picker to pick an image. - * @param fragment the requesting fragment - */ - public abstract void launchDocumentImagePicker(final Fragment fragment); - - /** - * Launch an activity to show people & options for a given conversation. - */ - public abstract void launchPeopleAndOptionsActivity(final Activity context, - final String conversationId); - - /** - * Launch an external activity to handle a phone call - * @param phoneNumber the phone number to call - * @param clickPosition is the location tapped to start this launch for transition use - */ - public abstract void launchPhoneCallActivity(final Context context, final String phoneNumber, - final Point clickPosition); - - /** - * Launch an activity to show archived conversations. - */ - public abstract void launchArchivedConversationsActivity(final Context context); - - /** - * Launch an activity to show blocked participants. - */ - public abstract void launchBlockedParticipantsActivity(final Context context); - - /** - * Launch an activity to show a class zero message - */ - public abstract void launchClassZeroActivity(Context context, ContentValues messageValues); - - /** - * Launch an activity to let the user forward a message - */ - public abstract void launchForwardMessageActivity(Context context, MessageData message); - - /** - * Launch an activity to show details for a VCard - */ - public abstract void launchVCardDetailActivity(Context context, Uri vcardUri); - - /** - * Launch an external activity that handles the intent to add VCard to contacts - */ - public abstract void launchSaveVCardToContactsActivity(Context context, Uri vcardUri); - - /** - * Launch an activity to let the user select & unselect the list of attachments to send. - */ - public abstract void launchAttachmentChooserActivity(final Activity activity, - final String conversationId, final int requestCode); - - /** - * Launch full screen video viewer. - */ - public abstract void launchFullScreenVideoViewer(Context context, Uri videoUri); - - /** - * Launch full screen photo viewer. - */ - public abstract void launchFullScreenPhotoViewer(Activity activity, Uri initialPhoto, - Rect initialPhotoBounds, Uri photosUri); - - /** - * Launch an activity to show general app settings - * @param topLevel indicates whether the app settings is launched as the top-level settings - * activity (instead of SettingsActivity which shows a collapsed view of the app - * settings + one settings item per subscription). This is true when there's only one - * active SIM in the system so we can show this activity directly. - */ - public abstract void launchApplicationSettingsActivity(Context context, boolean topLevel); - - /** - * Launch an activity to show per-subscription settings - */ - public abstract void launchPerSubscriptionSettingsActivity(Context context, int subId, - String settingTitle); - - /** - * Get a ACTION_VIEW intent - * @param url display the data in the url to users - */ - public abstract Intent getViewUrlIntent(final String url); - - /** - * Get an intent to launch the ringtone picker - * @param title the title to show in the ringtone picker - * @param existingUri the currently set uri - * @param defaultUri the default uri if none is currently set - * @param toneType type of ringtone to pick, maybe any of RingtoneManager.TYPE_* - */ - public abstract Intent getRingtonePickerIntent(final String title, final Uri existingUri, - final Uri defaultUri, final int toneType); - - /** - * Get an intent to launch the wireless alert viewer. - */ - public abstract Intent getWirelessAlertsIntent(); - - /** - * Get an intent to launch the dialog for changing the default SMS App. - */ - public abstract Intent getChangeDefaultSmsAppIntent(final Activity activity); - - /** - * Broadcast conversation self id change so it may be reflected in the message compose UI. - */ - public abstract void broadcastConversationSelfIdChange(final Context context, - final String conversationId, final String conversationSelfId); - - /** - * Get a PendingIntent for starting conversation list from notifications. - */ - public abstract PendingIntent getPendingIntentForConversationListActivity( - final Context context); - - /** - * Get a PendingIntent for starting conversation list from widget. - */ - public abstract PendingIntent getWidgetPendingIntentForConversationListActivity( - final Context context); - - /** - * Get a PendingIntent for showing a conversation from notifications. - */ - public abstract PendingIntent getPendingIntentForConversationActivity(final Context context, - final String conversationId, final MessageData draft); - - /** - * Get an Intent for showing a conversation from the widget. - */ - public abstract Intent getIntentForConversationActivity(final Context context, - final String conversationId, final MessageData draft); - - /** - * Get a PendingIntent for sending a message to a conversation, without opening the Bugle UI. - * - *

This is intended to be used by the Android Wear companion app when sending transcribed - * voice replies. - */ - public abstract PendingIntent getPendingIntentForSendingMessageToConversation( - final Context context, final String conversationId, final String selfId, - final boolean requiresMms, final int requestCode); - - /** - * Get a PendingIntent for clearing notifications. - * - *

This is intended to be used by notifications. - */ - public abstract PendingIntent getPendingIntentForClearingNotifications(final Context context, - final int updateTargets, final ConversationIdSet conversationIdSet, - final int requestCode); - - /** - * Get a PendingIntent for showing low storage notifications. - */ - public abstract PendingIntent getPendingIntentForLowStorageNotifications(final Context context); - - /** - * Get a PendingIntent for showing a new message to a secondary user. - */ - public abstract PendingIntent getPendingIntentForSecondaryUserNewMessageNotification( - final Context context); - - /** - * Get an intent for showing the APN editor. - */ - public abstract Intent getApnEditorIntent(final Context context, final String rowId, int subId); - - /** - * Get an intent for showing the APN settings. - */ - public abstract Intent getApnSettingsIntent(final Context context, final int subId); - - /** - * Get an intent for showing advanced settings. - */ - public abstract Intent getAdvancedSettingsIntent(final Context context); - - /** - * Get an intent for the LaunchConversationActivity. - */ - public abstract Intent getLaunchConversationActivityIntent(final Context context); - - /** - * Tell MediaScanner to re-scan the specified volume. - */ - public abstract void kickMediaScanner(final Context context, final String volume); - - /** - * Launch to browser for a url. - */ - public abstract void launchBrowserForUrl(final Context context, final String url); - - /** - * Get a PendingIntent for the widget conversation template. - */ - public abstract PendingIntent getWidgetPendingIntentForConversationActivity( - final Context context, final String conversationId, final int requestCode); - - /** - * Get a PendingIntent for the conversation widget configuration activity template. - */ - public abstract PendingIntent getWidgetPendingIntentForConfigurationActivity( - final Context context, final int appWidgetId); - -} diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java deleted file mode 100644 index b7db719..0000000 --- a/src/com/android/messaging/ui/UIIntentsImpl.java +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Activity; -import android.app.Fragment; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.content.ActivityNotFoundException; -import android.content.ClipData; -import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.graphics.Point; -import android.graphics.Rect; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Intents; -import android.provider.MediaStore; -import android.provider.Telephony; -import android.support.annotation.Nullable; -import android.support.v4.app.TaskStackBuilder; -import android.support.v4.content.LocalBroadcastManager; -import android.text.TextUtils; - -import com.android.ex.photo.Intents.PhotoViewIntentBuilder; -import com.android.messaging.R; -import com.android.messaging.datamodel.ConversationImagePartsView; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.receiver.NotificationReceiver; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.ui.appsettings.ApnEditorActivity; -import com.android.messaging.ui.appsettings.ApnSettingsActivity; -import com.android.messaging.ui.appsettings.ApplicationSettingsActivity; -import com.android.messaging.ui.appsettings.PerSubscriptionSettingsActivity; -import com.android.messaging.ui.appsettings.SettingsActivity; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity; -import com.android.messaging.ui.conversation.ConversationActivity; -import com.android.messaging.ui.conversation.LaunchConversationActivity; -import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity; -import com.android.messaging.ui.conversationlist.ConversationListActivity; -import com.android.messaging.ui.conversationlist.ForwardMessageActivity; -import com.android.messaging.ui.conversationsettings.PeopleAndOptionsActivity; -import com.android.messaging.ui.debug.DebugMmsConfigActivity; -import com.android.messaging.ui.photoviewer.BuglePhotoViewActivity; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ConversationIdSet; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; - -/** - * A central repository of Intents used to start activities. - */ -public class UIIntentsImpl extends UIIntents { - private static final String CELL_BROADCAST_LIST_ACTIVITY = - "com.android.cellbroadcastreceiver.CellBroadcastListActivity"; - private static final String CALL_TARGET_CLICK_KEY = "touchPoint"; - private static final String CALL_TARGET_CLICK_EXTRA_KEY = - "android.telecom.extra.OUTGOING_CALL_EXTRAS"; - private static final String MEDIA_SCANNER_CLASS = - "com.android.providers.media.MediaScannerService"; - private static final String MEDIA_SCANNER_PACKAGE = "com.android.providers.media"; - private static final String MEDIA_SCANNER_SCAN_ACTION = "android.media.IMediaScannerService"; - - /** - * Get an intent which takes you to a conversation - */ - private Intent getConversationActivityIntent(final Context context, - final String conversationId, final MessageData draft, - final boolean withCustomTransition) { - final Intent intent = new Intent(context, ConversationActivity.class); - - // Always try to reuse the same ConversationActivity in the current task so that we don't - // have two conversation activities in the back stack. - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - // Otherwise we're starting a new conversation - if (conversationId != null) { - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - } - if (draft != null) { - intent.putExtra(UI_INTENT_EXTRA_DRAFT_DATA, draft); - - // If draft attachments came from an external content provider via a share intent, we - // need to propagate the URI permissions through to ConversationActivity. This requires - // putting the URIs into the ClipData (setData also works, but accepts only one URI). - ClipData clipData = null; - for (final MessagePartData partData : draft.getParts()) { - if (partData.isAttachment()) { - final Uri uri = partData.getContentUri(); - if (clipData == null) { - clipData = ClipData.newRawUri("Attachments", uri); - } else { - clipData.addItem(new ClipData.Item(uri)); - } - } - } - if (clipData != null) { - intent.setClipData(clipData); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } - if (withCustomTransition) { - intent.putExtra(UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION, true); - } - - if (!(context instanceof Activity)) { - // If the caller supplies an application context, and not an activity context, we must - // include this flag - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - return intent; - } - - @Override - public void launchPermissionCheckActivity(final Context context) { - final Intent intent = new Intent(context, PermissionCheckActivity.class); - context.startActivity(intent); - } - - /** - * Get an intent which takes you to the conversation list - */ - private Intent getConversationListActivityIntent(final Context context) { - return new Intent(context, ConversationListActivity.class); - } - - @Override - public void launchConversationListActivity(final Context context) { - final Intent intent = getConversationListActivityIntent(context); - context.startActivity(intent); - } - - /** - * Get an intent which shows the low storage warning activity. - */ - private Intent getSmsStorageLowWarningActivityIntent(final Context context) { - return new Intent(context, SmsStorageLowWarningActivity.class); - } - - @Override - public void launchConversationActivity(final Context context, - final String conversationId, final MessageData draft, final Bundle activityOptions, - final boolean withCustomTransition) { - Assert.isTrue(!withCustomTransition || activityOptions != null); - final Intent intent = getConversationActivityIntent(context, conversationId, draft, - withCustomTransition); - context.startActivity(intent, activityOptions); - } - - @Override - public void launchConversationActivityNewTask( - final Context context, final String conversationId) { - final Intent intent = getConversationActivityIntent(context, conversationId, null, - false /* withCustomTransition */); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } - - @Override - public void launchConversationActivityWithParentStack(final Context context, - final String conversationId, final String smsBody) { - final MessageData messageData = TextUtils.isEmpty(smsBody) - ? null - : MessageData.createDraftSmsMessage(conversationId, null, smsBody); - TaskStackBuilder.create(context) - .addNextIntentWithParentStack( - getConversationActivityIntent(context, conversationId, messageData, - false /* withCustomTransition */)) - .startActivities(); - } - - @Override - public void launchCreateNewConversationActivity(final Context context, - final MessageData draft) { - final Intent intent = getConversationActivityIntent(context, null, draft, - false /* withCustomTransition */); - context.startActivity(intent); - } - - @Override - public void launchDebugMmsConfigActivity(final Context context) { - context.startActivity(new Intent(context, DebugMmsConfigActivity.class)); - } - - @Override - public void launchAddContactActivity(final Context context, final String destination) { - final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); - final String destinationType = MmsSmsUtils.isEmailAddress(destination) ? - Intents.Insert.EMAIL : Intents.Insert.PHONE; - intent.setType(Contacts.CONTENT_ITEM_TYPE); - intent.putExtra(destinationType, destination); - startExternalActivity(context, intent); - } - - @Override - public void launchSettingsActivity(final Context context) { - final Intent intent = new Intent(context, SettingsActivity.class); - context.startActivity(intent); - } - - @Override - public void launchArchivedConversationsActivity(final Context context) { - final Intent intent = new Intent(context, ArchivedConversationListActivity.class); - context.startActivity(intent); - } - - @Override - public void launchBlockedParticipantsActivity(final Context context) { - final Intent intent = new Intent(context, BlockedParticipantsActivity.class); - context.startActivity(intent); - } - - @Override - public void launchDocumentImagePicker(final Fragment fragment) { - final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_IMAGE_TYPES); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(ContentType.IMAGE_UNSPECIFIED); - - fragment.startActivityForResult(intent, REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER); - } - - @Override - public void launchPeopleAndOptionsActivity(final Activity activity, - final String conversationId) { - final Intent intent = new Intent(activity, PeopleAndOptionsActivity.class); - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - activity.startActivityForResult(intent, 0); - } - - @Override - public void launchPhoneCallActivity(final Context context, final String phoneNumber, - final Point clickPosition) { - final Intent intent = new Intent(Intent.ACTION_CALL, - Uri.parse(UriUtil.SCHEME_TEL + phoneNumber)); - final Bundle extras = new Bundle(); - extras.putParcelable(CALL_TARGET_CLICK_KEY, clickPosition); - intent.putExtra(CALL_TARGET_CLICK_EXTRA_KEY, extras); - startExternalActivity(context, intent); - } - - @Override - public void launchClassZeroActivity(final Context context, final ContentValues messageValues) { - final Intent classZeroIntent = new Intent(context, ClassZeroActivity.class) - .putExtra(UI_INTENT_EXTRA_MESSAGE_VALUES, messageValues) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - context.startActivity(classZeroIntent); - } - - @Override - public void launchForwardMessageActivity(final Context context, final MessageData message) { - final Intent forwardMessageIntent = new Intent(context, ForwardMessageActivity.class) - .putExtra(UI_INTENT_EXTRA_DRAFT_DATA, message); - context.startActivity(forwardMessageIntent); - } - - @Override - public void launchVCardDetailActivity(final Context context, final Uri vcardUri) { - final Intent vcardDetailIntent = new Intent(context, VCardDetailActivity.class) - .putExtra(UI_INTENT_EXTRA_VCARD_URI, vcardUri); - context.startActivity(vcardDetailIntent); - } - - @Override - public void launchSaveVCardToContactsActivity(final Context context, final Uri vcardUri) { - Assert.isTrue(MediaScratchFileProvider.isMediaScratchSpaceUri(vcardUri)); - final Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setDataAndType(vcardUri, ContentType.TEXT_VCARD.toLowerCase()); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startExternalActivity(context, intent); - } - - @Override - public void launchAttachmentChooserActivity(final Activity activity, - final String conversationId, final int requestCode) { - final Intent intent = new Intent(activity, AttachmentChooserActivity.class); - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - activity.startActivityForResult(intent, requestCode); - } - - @Override - public void launchFullScreenVideoViewer(final Context context, final Uri videoUri) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - // So we don't see "surrounding" images in Gallery - intent.putExtra("SingleItemOnly", true); - intent.setDataAndType(videoUri, ContentType.VIDEO_UNSPECIFIED); - startExternalActivity(context, intent); - } - - @Override - public void launchFullScreenPhotoViewer(final Activity activity, final Uri initialPhoto, - final Rect initialPhotoBounds, final Uri photosUri) { - final PhotoViewIntentBuilder builder = - com.android.ex.photo.Intents.newPhotoViewIntentBuilder( - activity, BuglePhotoViewActivity.class); - builder.setPhotosUri(photosUri.toString()); - builder.setInitialPhotoUri(initialPhoto.toString()); - builder.setProjection(ConversationImagePartsView.PhotoViewQuery.PROJECTION); - - // Set the location of the imageView so that the photoviewer can animate from that location - // to full screen. - builder.setScaleAnimation(initialPhotoBounds.left, initialPhotoBounds.top, - initialPhotoBounds.width(), initialPhotoBounds.height()); - - builder.setDisplayThumbsFullScreen(false); - builder.setMaxInitialScale(8); - activity.startActivity(builder.build()); - activity.overridePendingTransition(0, 0); - } - - @Override - public void launchApplicationSettingsActivity(final Context context, final boolean topLevel) { - final Intent intent = new Intent(context, ApplicationSettingsActivity.class); - intent.putExtra(UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, topLevel); - context.startActivity(intent); - } - - @Override - public void launchPerSubscriptionSettingsActivity(final Context context, final int subId, - final String settingTitle) { - final Intent intent = getPerSubscriptionSettingsIntent(context, subId, settingTitle); - context.startActivity(intent); - } - - @Override - public Intent getViewUrlIntent(final String url) { - final Uri uri = Uri.parse(url); - return new Intent(Intent.ACTION_VIEW, uri); - } - - @Override - public void broadcastConversationSelfIdChange(final Context context, - final String conversationId, final String conversationSelfId) { - final Intent intent = new Intent(CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION); - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_SELF_ID, conversationSelfId); - LocalBroadcastManager.getInstance(context).sendBroadcast(intent); - } - - @Override - public PendingIntent getPendingIntentForConversationListActivity(final Context context) { - final Intent intent = getConversationListActivityIntent(context); - return getPendingIntentWithParentStack(context, intent, 0); - } - - @Override - public PendingIntent getPendingIntentForConversationActivity(final Context context, - final String conversationId, final MessageData draft) { - final Intent intent = getConversationActivityIntent(context, conversationId, draft, - false /* withCustomTransition */); - // Ensure that the platform doesn't reuse PendingIntents across conversations - intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId)); - return getPendingIntentWithParentStack(context, intent, 0); - } - - @Override - public Intent getIntentForConversationActivity(final Context context, - final String conversationId, final MessageData draft) { - final Intent intent = getConversationActivityIntent(context, conversationId, draft, - false /* withCustomTransition */); - return intent; - } - - @Override - public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context, - final String conversationId, final String selfId, final boolean requiresMms, - final int requestCode) { - final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class); - intent.setAction(Intent.ACTION_SENDTO); - // Ensure that the platform doesn't reuse PendingIntents across conversations - intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId)); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return getPendingIntentWithParentStack(context, intent, requestCode); - } - - @Override - public PendingIntent getPendingIntentForClearingNotifications(final Context context, - final int updateTargets, final ConversationIdSet conversationIdSet, - final int requestCode) { - final Intent intent = new Intent(context, NotificationReceiver.class); - intent.setAction(ACTION_RESET_NOTIFICATIONS); - intent.putExtra(UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, updateTargets); - if (conversationIdSet != null) { - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID_SET, - conversationIdSet.getDelimitedString()); - } - return PendingIntent.getBroadcast(context, - requestCode, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** - * Gets a PendingIntent associated with an Intent to start an Activity. All notifications - * that starts an Activity must use this method to get a PendingIntent, which achieves two - * goals: - * 1. The target activities will be created, with any existing ones destroyed. This ensures - * we don't end up with multiple instances of ConversationListActivity, for example. - * 2. The target activity, when launched, will have its backstack correctly constructed so - * back navigation will work correctly. - */ - private static PendingIntent getPendingIntentWithParentStack(final Context context, - final Intent intent, final int requestCode) { - final TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); - // Adds the back stack for the Intent (plus the Intent itself) - stackBuilder.addNextIntentWithParentStack(intent); - final PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT); - return resultPendingIntent; - } - - @Override - public Intent getRingtonePickerIntent(final String title, final Uri existingUri, - final Uri defaultUri, final int toneType) { - return new Intent(RingtoneManager.ACTION_RINGTONE_PICKER) - .putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, toneType) - .putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, title) - .putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingUri) - .putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultUri); - } - - @Override - public PendingIntent getPendingIntentForLowStorageNotifications(final Context context) { - final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); - final Intent conversationListIntent = getConversationListActivityIntent(context); - taskStackBuilder.addNextIntent(conversationListIntent); - taskStackBuilder.addNextIntentWithParentStack( - getSmsStorageLowWarningActivityIntent(context)); - - return taskStackBuilder.getPendingIntent( - 0, PendingIntent.FLAG_UPDATE_CURRENT); - } - - @Override - public PendingIntent getPendingIntentForSecondaryUserNewMessageNotification( - final Context context) { - return getPendingIntentForConversationListActivity(context); - } - - @Override - public Intent getWirelessAlertsIntent() { - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setComponent(new ComponentName(CMAS_COMPONENT, CELL_BROADCAST_LIST_ACTIVITY)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return intent; - } - - @Override - public Intent getApnEditorIntent(final Context context, final String rowId, final int subId) { - final Intent intent = new Intent(context, ApnEditorActivity.class); - intent.putExtra(UI_INTENT_EXTRA_APN_ROW_ID, rowId); - intent.putExtra(UI_INTENT_EXTRA_SUB_ID, subId); - return intent; - } - - @Override - public Intent getApnSettingsIntent(final Context context, final int subId) { - final Intent intent = new Intent(context, ApnSettingsActivity.class) - .putExtra(UI_INTENT_EXTRA_SUB_ID, subId); - return intent; - } - - @Override - public Intent getAdvancedSettingsIntent(final Context context) { - return getPerSubscriptionSettingsIntent(context, ParticipantData.DEFAULT_SELF_SUB_ID, null); - } - - @Override - public Intent getChangeDefaultSmsAppIntent(final Activity activity) { - final Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT); - intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, activity.getPackageName()); - return intent; - } - - @Override - public void launchBrowserForUrl(final Context context, final String url) { - final Intent intent = getViewUrlIntent(url); - startExternalActivity(context, intent); - } - - /** - * Provides a safe way to handle external activities which may not exist. - */ - private void startExternalActivity(final Context context, final Intent intent) { - try { - context.startActivity(intent); - } catch (final ActivityNotFoundException ex) { - LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex); - UiUtils.showToastAtBottom(R.string.activity_not_found_message); - } - } - - private Intent getPerSubscriptionSettingsIntent(final Context context, final int subId, - @Nullable final String settingTitle) { - return new Intent(context, PerSubscriptionSettingsActivity.class) - .putExtra(UI_INTENT_EXTRA_SUB_ID, subId) - .putExtra(UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE, settingTitle); - } - - @Override - public Intent getLaunchConversationActivityIntent(final Context context) { - final Intent intent = new Intent(context, LaunchConversationActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY); - return intent; - } - - @Override - public void kickMediaScanner(final Context context, final String volume) { - final Intent intent = new Intent(MEDIA_SCANNER_SCAN_ACTION) - .putExtra(MediaStore.MEDIA_SCANNER_VOLUME, volume) - .setClassName(MEDIA_SCANNER_PACKAGE, MEDIA_SCANNER_CLASS); - context.startService(intent); - } - - @Override - public PendingIntent getWidgetPendingIntentForConversationActivity(final Context context, - final String conversationId, final int requestCode) { - final Intent intent = getConversationActivityIntent(context, null, null, - false /* withCustomTransition */); - if (conversationId != null) { - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - - // Set the action to something unique to this conversation so if someone calls this - // function again on a different conversation, they'll get a new PendingIntent instead - // of the old one. - intent.setAction(ACTION_WIDGET_CONVERSATION + conversationId); - } - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return getPendingIntentWithParentStack(context, intent, requestCode); - } - - @Override - public PendingIntent getWidgetPendingIntentForConversationListActivity( - final Context context) { - final Intent intent = getConversationListActivityIntent(context); - return getPendingIntentWithParentStack(context, intent, 0); - } - - @Override - public PendingIntent getWidgetPendingIntentForConfigurationActivity(final Context context, - final int appWidgetId) { - final Intent configureIntent = new Intent(context, WidgetPickConversationActivity.class); - configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - configureIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); - configureIntent.setData(Uri.parse(configureIntent.toUri(Intent.URI_INTENT_SCHEME))); - configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY); - return getPendingIntentWithParentStack(context, configureIntent, 0); - } -} diff --git a/src/com/android/messaging/ui/VCardDetailActivity.java b/src/com/android/messaging/ui/VCardDetailActivity.java deleted file mode 100644 index fecdc34..0000000 --- a/src/com/android/messaging/ui/VCardDetailActivity.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Fragment; -import android.net.Uri; -import android.os.Bundle; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; - -/** - * An activity that hosts VCardDetailFragment that shows the content of a VCard that contains one - * or more contacts. - */ -public class VCardDetailActivity extends BugleActionBarActivity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.vcard_detail_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - Assert.isTrue(fragment instanceof VCardDetailFragment); - final Uri vCardUri = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_VCARD_URI); - Assert.notNull(vCardUri); - final VCardDetailFragment vCardDetailFragment = (VCardDetailFragment) fragment; - vCardDetailFragment.setVCardUri(vCardUri); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // Treat the home press as back press so that when we go back to - // ConversationActivity, it doesn't lose its original intent (conversation id etc.) - onBackPressed(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/src/com/android/messaging/ui/VCardDetailAdapter.java b/src/com/android/messaging/ui/VCardDetailAdapter.java deleted file mode 100644 index cfdd836..0000000 --- a/src/com/android/messaging/ui/VCardDetailAdapter.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseExpandableListAdapter; - -import com.android.messaging.R; -import com.android.messaging.datamodel.media.VCardResourceEntry; -import com.android.messaging.datamodel.media.VCardResourceEntry.VCardResourceEntryDestinationItem; - -import java.util.List; - -/** - * Displays a list of expandable contact cards shown in the VCardDetailActivity. - */ -public class VCardDetailAdapter extends BaseExpandableListAdapter { - private final List mVCards; - private final LayoutInflater mInflater; - - public VCardDetailAdapter(final Context context, final List vCards) { - mVCards = vCards; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public Object getChild(final int groupPosition, final int childPosition) { - return mVCards.get(groupPosition).getContactInfo().get(childPosition); - } - - @Override - public long getChildId(final int groupPosition, final int childPosition) { - return childPosition; - } - - @Override - public View getChildView(final int groupPosition, final int childPosition, - final boolean isLastChild, final View convertView, final ViewGroup parent) { - PersonItemView v; - if (convertView == null) { - v = instantiateView(parent); - } else { - v = (PersonItemView) convertView; - } - - final VCardResourceEntryDestinationItem item = (VCardResourceEntryDestinationItem) - getChild(groupPosition, childPosition); - - v.bind(item.getDisplayItem()); - return v; - } - - @Override - public int getChildrenCount(final int groupPosition) { - return mVCards.get(groupPosition).getContactInfo().size(); - } - - @Override - public Object getGroup(final int groupPosition) { - return mVCards.get(groupPosition); - } - - @Override - public int getGroupCount() { - return mVCards.size(); - } - - @Override - public long getGroupId(final int groupPosition) { - return groupPosition; - } - - @Override - public View getGroupView(final int groupPosition, final boolean isExpanded, - final View convertView, final ViewGroup parent) { - PersonItemView v; - if (convertView == null) { - v = instantiateView(parent); - } else { - v = (PersonItemView) convertView; - } - - final VCardResourceEntry item = (VCardResourceEntry) getGroup(groupPosition); - v.bind(item.getDisplayItem()); - return v; - } - - @Override - public boolean isChildSelectable(final int groupPosition, final int childPosition) { - return true; - } - - @Override - public boolean hasStableIds() { - return true; - } - - private PersonItemView instantiateView(final ViewGroup parent) { - final PersonItemView v = (PersonItemView) mInflater.inflate(R.layout.people_list_item_view, - parent, false); - v.setClickable(false); - return v; - } -} diff --git a/src/com/android/messaging/ui/VCardDetailFragment.java b/src/com/android/messaging/ui/VCardDetailFragment.java deleted file mode 100644 index 1b2b88d..0000000 --- a/src/com/android/messaging/ui/VCardDetailFragment.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Fragment; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup; -import android.widget.ExpandableListAdapter; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.OnChildClickListener; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.datamodel.data.VCardContactItemData; -import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; - -/** - * A fragment that shows the content of a VCard that contains one or more contacts. - */ -public class VCardDetailFragment extends Fragment implements PersonItemDataListener { - private final Binding mBinding = - BindingBase.createBinding(this); - private ExpandableListView mListView; - private VCardDetailAdapter mAdapter; - private Uri mVCardUri; - - /** - * We need to persist the VCard in the scratch directory before letting the user view it. - * We save this Uri locally, so that if the user cancels the action and re-perform the add - * to contacts action we don't have to persist it again. - */ - private Uri mScratchSpaceUri; - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - Assert.notNull(mVCardUri); - final View view = inflater.inflate(R.layout.vcard_detail_fragment, container, false); - mListView = (ExpandableListView) view.findViewById(R.id.list); - mListView.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, - final int bottom, final int oldLeft, final int oldTop, final int oldRight, - final int oldBottom) { - mListView.setIndicatorBounds(mListView.getWidth() - getResources() - .getDimensionPixelSize(R.dimen.vcard_detail_group_indicator_width), - mListView.getWidth()); - } - }); - mListView.setOnChildClickListener(new OnChildClickListener() { - @Override - public boolean onChildClick(ExpandableListView expandableListView, View clickedView, - int groupPosition, int childPosition, long childId) { - if (!(clickedView instanceof PersonItemView)) { - return false; - } - final Intent intent = ((PersonItemView) clickedView).getClickIntent(); - if (intent != null) { - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - return false; - } - return true; - } - return false; - } - }); - mBinding.bind(DataModel.get().createVCardContactItemData(getActivity(), mVCardUri)); - mBinding.getData().setListener(this); - return view; - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mBinding.isBound()) { - mBinding.unbind(); - } - mListView.setAdapter((ExpandableListAdapter) null); - } - - private boolean shouldShowAddToContactsItem() { - return mBinding.isBound() && mBinding.getData().hasValidVCard(); - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.vcard_detail_fragment_menu, menu); - final MenuItem addToContactsItem = menu.findItem(R.id.action_add_contact); - addToContactsItem.setVisible(shouldShowAddToContactsItem()); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_add_contact: - mBinding.ensureBound(); - final Uri vCardUri = mBinding.getData().getVCardUri(); - - // We have to do things in the background in case we need to copy the vcard data. - new SafeAsyncTask() { - @Override - protected Uri doInBackgroundTimed(final Void... params) { - // We can't delete the persisted vCard file because we don't know when to - // delete it, since the app that uses it (contacts, dialer) may start or - // shut down at any point. Therefore, we rely on the system to clean up - // the cache directory for us. - return mScratchSpaceUri != null ? mScratchSpaceUri : - UriUtil.persistContentToScratchSpace(vCardUri); - } - - @Override - protected void onPostExecute(final Uri result) { - if (result != null) { - mScratchSpaceUri = result; - if (getActivity() != null) { - MediaScratchFileProvider.addUriToDisplayNameEntry( - result, mBinding.getData().getDisplayName()); - UIIntents.get().launchSaveVCardToContactsActivity(getActivity(), - result); - } - } - } - }.executeOnThreadPool(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - public void setVCardUri(final Uri vCardUri) { - Assert.isTrue(!mBinding.isBound()); - mVCardUri = vCardUri; - } - - @Override - public void onPersonDataUpdated(final PersonItemData data) { - Assert.isTrue(data instanceof VCardContactItemData); - mBinding.ensureBound(); - final VCardContactItemData vCardData = (VCardContactItemData) data; - Assert.isTrue(vCardData.hasValidVCard()); - mAdapter = new VCardDetailAdapter(getActivity(), vCardData.getVCardResource().getVCards()); - mListView.setAdapter(mAdapter); - - // Expand the contact card if there's only one contact. - if (mAdapter.getGroupCount() == 1) { - mListView.expandGroup(0); - } - getActivity().invalidateOptionsMenu(); - } - - @Override - public void onPersonDataFailed(final PersonItemData data, final Exception exception) { - mBinding.ensureBound(); - UiUtils.showToastAtBottom(R.string.failed_loading_vcard); - getActivity().finish(); - } -} diff --git a/src/com/android/messaging/ui/VideoThumbnailView.java b/src/com/android/messaging/ui/VideoThumbnailView.java deleted file mode 100644 index 966336e..0000000 --- a/src/com/android/messaging/ui/VideoThumbnailView.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.media.MediaPlayer; -import android.net.Uri; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView.ScaleType; -import android.widget.VideoView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor; -import com.android.messaging.datamodel.media.VideoThumbnailRequest; -import com.android.messaging.util.Assert; - -/** - * View that encapsulates a video preview (either as a thumbnail image, or video player), and the - * a play button to overlay it. Ensures that the video preview maintains the aspect ratio of the - * original video while trying to respect minimum width/height and constraining to the available - * bounds - */ -public class VideoThumbnailView extends FrameLayout { - /** - * When in this mode the VideoThumbnailView is a lightweight AsyncImageView with an ImageButton - * to play the video. Clicking play will launch a full screen player - */ - private static final int MODE_IMAGE_THUMBNAIL = 0; - - /** - * When in this mode the VideoThumbnailVideo will include a VideoView, and the play button will - * play the video inline. When in this mode, the loop and playOnLoad attributes can be applied - * to auto-play or loop the video. - */ - private static final int MODE_PLAYABLE_VIDEO = 1; - - private final int mMode; - private final boolean mPlayOnLoad; - private final boolean mAllowCrop; - private final VideoView mVideoView; - private final ImageButton mPlayButton; - private final AsyncImageView mThumbnailImage; - private int mVideoWidth; - private int mVideoHeight; - private Uri mVideoSource; - private boolean mAnimating; - private boolean mVideoLoaded; - - public VideoThumbnailView(final Context context, final AttributeSet attrs) { - super(context, attrs); - final TypedArray typedAttributes = - context.obtainStyledAttributes(attrs, R.styleable.VideoThumbnailView); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.video_thumbnail_view, this, true); - - mPlayOnLoad = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_playOnLoad, false); - final boolean loop = - typedAttributes.getBoolean(R.styleable.VideoThumbnailView_loop, false); - mMode = typedAttributes.getInt(R.styleable.VideoThumbnailView_mode, MODE_IMAGE_THUMBNAIL); - mAllowCrop = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_allowCrop, false); - - mVideoWidth = ImageRequest.UNSPECIFIED_SIZE; - mVideoHeight = ImageRequest.UNSPECIFIED_SIZE; - - if (mMode == MODE_PLAYABLE_VIDEO) { - mVideoView = new VideoView(context); - // Video view tries to request focus on start which pulls focus from the user's intended - // focus when we add this control. Remove focusability to prevent this. The play - // button can still be focused - mVideoView.setFocusable(false); - mVideoView.setFocusableInTouchMode(false); - mVideoView.clearFocus(); - addView(mVideoView, 0, new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(final MediaPlayer mediaPlayer) { - mVideoLoaded = true; - mVideoWidth = mediaPlayer.getVideoWidth(); - mVideoHeight = mediaPlayer.getVideoHeight(); - mediaPlayer.setLooping(loop); - trySwitchToVideo(); - } - }); - mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(final MediaPlayer mediaPlayer) { - mPlayButton.setVisibility(View.VISIBLE); - } - }); - mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(final MediaPlayer mediaPlayer, final int i, final int i2) { - return true; - } - }); - } else { - mVideoView = null; - } - - mPlayButton = (ImageButton) findViewById(R.id.video_thumbnail_play_button); - if (loop) { - mPlayButton.setVisibility(View.GONE); - } else { - mPlayButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - if (mVideoSource == null) { - return; - } - - if (mMode == MODE_PLAYABLE_VIDEO) { - mVideoView.seekTo(0); - start(); - } else { - UIIntents.get().launchFullScreenVideoViewer(getContext(), mVideoSource); - } - } - }); - mPlayButton.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(final View view) { - // Button prevents long click from propagating up, do it manually - VideoThumbnailView.this.performLongClick(); - return true; - } - }); - } - - mThumbnailImage = (AsyncImageView) findViewById(R.id.video_thumbnail_image); - if (mAllowCrop) { - mThumbnailImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT; - mThumbnailImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; - mThumbnailImage.setScaleType(ScaleType.CENTER_CROP); - } else { - // This is the default setting in the layout, so No-op. - } - final int maxHeight = typedAttributes.getDimensionPixelSize( - R.styleable.VideoThumbnailView_android_maxHeight, ImageRequest.UNSPECIFIED_SIZE); - if (maxHeight != ImageRequest.UNSPECIFIED_SIZE) { - mThumbnailImage.setMaxHeight(maxHeight); - mThumbnailImage.setAdjustViewBounds(true); - } - - typedAttributes.recycle(); - } - - @Override - protected void onAnimationStart() { - super.onAnimationStart(); - mAnimating = true; - } - - @Override - protected void onAnimationEnd() { - super.onAnimationEnd(); - mAnimating = false; - trySwitchToVideo(); - } - - private void trySwitchToVideo() { - if (mAnimating) { - // Don't start video or hide image until after animation completes - return; - } - - if (!mVideoLoaded) { - // Video hasn't loaded, nothing more to do - return; - } - - if (mPlayOnLoad) { - start(); - } else { - mVideoView.seekTo(0); - } - } - - private boolean hasVideoSize() { - return mVideoWidth != ImageRequest.UNSPECIFIED_SIZE && - mVideoHeight != ImageRequest.UNSPECIFIED_SIZE; - } - - public void start() { - Assert.equals(MODE_PLAYABLE_VIDEO, mMode); - mPlayButton.setVisibility(View.GONE); - mThumbnailImage.setVisibility(View.GONE); - mVideoView.start(); - } - - // TODO: The check could be added to MessagePartData itself so that all users of MessagePartData - // get the right behavior, instead of requiring all the users to do similar checks. - private static boolean shouldUseGenericVideoIcon(final boolean incomingMessage) { - return incomingMessage && !VideoThumbnailRequest.shouldShowIncomingVideoThumbnails(); - } - - public void setSource(final MessagePartData part, final boolean incomingMessage) { - if (part == null) { - clearSource(); - } else { - mVideoSource = part.getContentUri(); - if (shouldUseGenericVideoIcon(incomingMessage)) { - mThumbnailImage.setImageResource(R.drawable.generic_video_icon); - mVideoWidth = ImageRequest.UNSPECIFIED_SIZE; - mVideoHeight = ImageRequest.UNSPECIFIED_SIZE; - } else { - mThumbnailImage.setImageResourceId( - new MessagePartVideoThumbnailRequestDescriptor(part)); - if (mVideoView != null) { - mVideoView.setVideoURI(mVideoSource); - } - mVideoWidth = part.getWidth(); - mVideoHeight = part.getHeight(); - } - } - } - - public void setSource(final Uri videoSource, final boolean incomingMessage) { - if (videoSource == null) { - clearSource(); - } else { - mVideoSource = videoSource; - if (shouldUseGenericVideoIcon(incomingMessage)) { - mThumbnailImage.setImageResource(R.drawable.generic_video_icon); - mVideoWidth = ImageRequest.UNSPECIFIED_SIZE; - mVideoHeight = ImageRequest.UNSPECIFIED_SIZE; - } else { - mThumbnailImage.setImageResourceId( - new MessagePartVideoThumbnailRequestDescriptor(videoSource)); - if (mVideoView != null) { - mVideoView.setVideoURI(videoSource); - } - } - } - } - - private void clearSource() { - mVideoSource = null; - mThumbnailImage.setImageResourceId(null); - mVideoWidth = ImageRequest.UNSPECIFIED_SIZE; - mVideoHeight = ImageRequest.UNSPECIFIED_SIZE; - if (mVideoView != null) { - mVideoView.setVideoURI(null); - } - } - - @Override - public void setMinimumWidth(final int minWidth) { - super.setMinimumWidth(minWidth); - if (mVideoView != null) { - mVideoView.setMinimumWidth(minWidth); - } - } - - @Override - public void setMinimumHeight(final int minHeight) { - super.setMinimumHeight(minHeight); - if (mVideoView != null) { - mVideoView.setMinimumHeight(minHeight); - } - } - - public void setColorFilter(int color) { - mThumbnailImage.setColorFilter(color); - mPlayButton.setColorFilter(color); - } - - public void clearColorFilter() { - mThumbnailImage.clearColorFilter(); - mPlayButton.clearColorFilter(); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - if (mAllowCrop) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - int desiredWidth = 1; - int desiredHeight = 1; - if (mVideoView != null) { - mVideoView.measure(widthMeasureSpec, heightMeasureSpec); - } - mThumbnailImage.measure(widthMeasureSpec, heightMeasureSpec); - if (hasVideoSize()) { - desiredWidth = mVideoWidth; - desiredHeight = mVideoHeight; - } else { - desiredWidth = mThumbnailImage.getMeasuredWidth(); - desiredHeight = mThumbnailImage.getMeasuredHeight(); - } - - final int minimumWidth = getMinimumWidth(); - final int minimumHeight = getMinimumHeight(); - - // Constrain the scale to fit within the supplied size - final float maxScale = Math.max( - MeasureSpec.getSize(widthMeasureSpec) / (float) desiredWidth, - MeasureSpec.getSize(heightMeasureSpec) / (float) desiredHeight); - - // Scale up to reach minimum width/height - final float widthScale = Math.max(1, minimumWidth / (float) desiredWidth); - final float heightScale = Math.max(1, minimumHeight / (float) desiredHeight); - final float scale = Math.min(maxScale, Math.max(widthScale, heightScale)); - desiredWidth = (int) (desiredWidth * scale); - desiredHeight = (int) (desiredHeight * scale); - - setMeasuredDimension(desiredWidth, desiredHeight); - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, - final int bottom) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - child.layout(0, 0, right - left, bottom - top); - } - } -} diff --git a/src/com/android/messaging/ui/ViewPagerTabStrip.java b/src/com/android/messaging/ui/ViewPagerTabStrip.java deleted file mode 100644 index e088296..0000000 --- a/src/com/android/messaging/ui/ViewPagerTabStrip.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import com.android.messaging.R; -import com.android.messaging.util.OsUtil; - -public class ViewPagerTabStrip extends LinearLayout { - private int mSelectedUnderlineThickness; - private final Paint mSelectedUnderlinePaint; - - private int mIndexForSelection; - private float mSelectionOffset; - - public ViewPagerTabStrip(Context context) { - this(context, null); - } - - public ViewPagerTabStrip(Context context, AttributeSet attrs) { - super(context, attrs); - - final Resources res = context.getResources(); - - mSelectedUnderlineThickness = - res.getDimensionPixelSize(R.dimen.pager_tab_underline_selected); - int underlineColor = res.getColor(R.color.contact_picker_tab_underline); - int backgroundColor = res.getColor(R.color.action_bar_background_color); - - mSelectedUnderlinePaint = new Paint(); - mSelectedUnderlinePaint.setColor(underlineColor); - - setBackgroundColor(backgroundColor); - setWillNotDraw(false); - } - - /** - * Notifies this view that view pager has been scrolled. We save the tab index - * and selection offset for interpolating the position and width of selection - * underline. - */ - void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mIndexForSelection = position; - mSelectionOffset = positionOffset; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - int childCount = getChildCount(); - - // Thick colored underline below the current selection - if (childCount > 0) { - View selectedTitle = getChildAt(mIndexForSelection); - int selectedLeft = selectedTitle.getLeft(); - int selectedRight = selectedTitle.getRight(); - final boolean isRtl = isRtl(); - final boolean hasNextTab = isRtl ? mIndexForSelection > 0 - : (mIndexForSelection < (getChildCount() - 1)); - if ((mSelectionOffset > 0.0f) && hasNextTab) { - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mIndexForSelection + (isRtl ? -1 : 1)); - int nextLeft = nextTitle.getLeft(); - int nextRight = nextTitle.getRight(); - - selectedLeft = (int) (mSelectionOffset * nextLeft + - (1.0f - mSelectionOffset) * selectedLeft); - selectedRight = (int) (mSelectionOffset * nextRight + - (1.0f - mSelectionOffset) * selectedRight); - } - - int height = getHeight(); - canvas.drawRect(selectedLeft, height - mSelectedUnderlineThickness, - selectedRight, height, mSelectedUnderlinePaint); - } - } - - private boolean isRtl() { - return OsUtil.isAtLeastJB_MR2() ? getLayoutDirection() == View.LAYOUT_DIRECTION_RTL : false; - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/ViewPagerTabs.java b/src/com/android/messaging/ui/ViewPagerTabs.java deleted file mode 100644 index f31a071..0000000 --- a/src/com/android/messaging/ui/ViewPagerTabs.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Outline; -import android.graphics.drawable.ColorDrawable; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.FrameLayout; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.util.OsUtil; - -/** - * Lightweight implementation of ViewPager tabs. This looks similar to traditional actionBar tabs, - * but allows for the view containing the tabs to be placed anywhere on screen. Text-related - * attributes can also be assigned in XML - these will get propogated to the child TextViews - * automatically. - * - * Note: this file is taken from the AOSP /packages/apps/ContactsCommon/src/com/android/contacts/ - * common/list/ViewPagerTabs.java. Some platform specific API calls (e.g. ViewOutlineProvider which - * assumes L and above) have been modified to support down to Api Level 16. - */ -public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnPageChangeListener { - - ViewPager mPager; - private ViewPagerTabStrip mTabStrip; - - /** - * Linearlayout that will contain the TextViews serving as tabs. This is the only child - * of the parent HorizontalScrollView. - */ - final int mTextStyle; - final ColorStateList mTextColor; - final int mTextSize; - final boolean mTextAllCaps; - int mPrevSelected = -1; - int mSidePadding; - - private static final int TAB_SIDE_PADDING_IN_DPS = 10; - - // TODO: This should use in the future - private static final int[] ATTRS = new int[] { - android.R.attr.textSize, - android.R.attr.textStyle, - android.R.attr.textColor, - android.R.attr.textAllCaps - }; - - /** - * Shows a toast with the tab description when long-clicked. - */ - private class OnTabLongClickListener implements OnLongClickListener { - final String mTabDescription; - - public OnTabLongClickListener(String tabDescription) { - mTabDescription = tabDescription; - } - - @Override - public boolean onLongClick(View v) { - final int[] screenPos = new int[2]; - getLocationOnScreen(screenPos); - - final Context context = getContext(); - final int width = getWidth(); - final int height = getHeight(); - final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; - - Toast toast = Toast.makeText(context, mTabDescription, Toast.LENGTH_SHORT); - - // Show the toast under the tab - toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, - (screenPos[0] + width / 2) - screenWidth / 2, screenPos[1] + height); - - toast.show(); - return true; - } - } - - public ViewPagerTabs(Context context) { - this(context, null); - } - - public ViewPagerTabs(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setFillViewport(true); - - mSidePadding = (int) (getResources().getDisplayMetrics().density * TAB_SIDE_PADDING_IN_DPS); - - final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); - mTextSize = a.getDimensionPixelSize(0, 0); - mTextStyle = a.getInt(1, 0); - mTextColor = a.getColorStateList(2); - mTextAllCaps = a.getBoolean(3, false); - - mTabStrip = new ViewPagerTabStrip(context); - addView(mTabStrip, - new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); - a.recycle(); - - // enable shadow casting from view bounds - if (OsUtil.isAtLeastL()) { - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRect(0, 0, view.getWidth(), view.getHeight()); - } - }); - } - } - - public void setViewPager(ViewPager viewPager) { - mPager = viewPager; - addTabs(mPager.getAdapter()); - } - - private void addTabs(PagerAdapter adapter) { - mTabStrip.removeAllViews(); - - final int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - addTab(adapter.getPageTitle(i), i); - } - } - - private void addTab(CharSequence tabTitle, final int position) { - final TextView textView = new TextView(getContext()); - textView.setText(tabTitle); - textView.setBackgroundResource(R.drawable.contact_picker_tab_background_selector); - textView.setGravity(Gravity.CENTER); - textView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mPager.setCurrentItem(getRtlPosition(position)); - } - }); - - // Assign various text appearance related attributes to child views. - if (mTextStyle > 0) { - textView.setTypeface(textView.getTypeface(), mTextStyle); - } - if (mTextSize > 0) { - textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); - } - if (mTextColor != null) { - textView.setTextColor(mTextColor); - } - textView.setAllCaps(mTextAllCaps); - textView.setPadding(mSidePadding, 0, mSidePadding, 0); - mTabStrip.addView(textView, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT, 1)); - // Default to the first child being selected - if (position == 0) { - mPrevSelected = 0; - textView.setSelected(true); - } - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - position = getRtlPosition(position); - int tabStripChildCount = mTabStrip.getChildCount(); - if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { - return; - } - - mTabStrip.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - - @Override - public void onPageSelected(int position) { - position = getRtlPosition(position); - int tabStripChildCount = mTabStrip.getChildCount(); - if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { - return; - } - - if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) { - mTabStrip.getChildAt(mPrevSelected).setSelected(false); - } - final View selectedChild = mTabStrip.getChildAt(position); - selectedChild.setSelected(true); - - // Update scroll position - final int scrollPos = selectedChild.getLeft() - (getWidth() - selectedChild.getWidth()) / 2; - smoothScrollTo(scrollPos, 0); - mPrevSelected = position; - } - - @Override - public void onPageScrollStateChanged(int state) { - } - - private int getRtlPosition(int position) { - if (OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources() - .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - return mTabStrip.getChildCount() - 1 - position; - } - return position; - } - - public int getSelectedItemPosition() { - return mPrevSelected; - } -} diff --git a/src/com/android/messaging/ui/WidgetPickConversationActivity.java b/src/com/android/messaging/ui/WidgetPickConversationActivity.java deleted file mode 100644 index 60e1318..0000000 --- a/src/com/android/messaging/ui/WidgetPickConversationActivity.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Fragment; -import android.appwidget.AppWidgetManager; -import android.content.Intent; -import android.os.Bundle; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.ui.conversationlist.ShareIntentFragment; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.widget.WidgetConversationProvider; - -public class WidgetPickConversationActivity extends BaseBugleActivity implements - ShareIntentFragment.HostInterface { - - private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - // Set the result to CANCELED. This will cause the widget host to cancel - // out of the widget placement if they press the back button. - setResult(RESULT_CANCELED); - - // Find the widget id from the intent. - final Intent intent = getIntent(); - final Bundle extras = intent.getExtras(); - if (extras != null) { - mAppWidgetId = extras.getInt( - AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - } - - // If they gave us an intent without the widget id, just bail. - if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { - finish(); - } - - final ShareIntentFragment convPicker = new ShareIntentFragment(); - final Bundle bundle = new Bundle(); - bundle.putBoolean(ShareIntentFragment.HIDE_NEW_CONVERSATION_BUTTON_KEY, true); - convPicker.setArguments(bundle); - convPicker.show(getFragmentManager(), "ShareIntentFragment"); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - final Intent intent = getIntent(); - final String action = intent.getAction(); - if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) { - // Unsupported action. - Assert.fail("Unsupported action type: " + action); - } - } - - @Override - public void onConversationClick(final ConversationListItemData conversationListItemData) { - saveConversationidPref(mAppWidgetId, conversationListItemData.getConversationId()); - - // Push widget update to surface with newly set prefix - WidgetConversationProvider.rebuildWidget(this, mAppWidgetId); - - // Make sure we pass back the original appWidgetId - Intent resultValue = new Intent(); - resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); - setResult(RESULT_OK, resultValue); - finish(); - } - - @Override - public void onCreateConversationClick() { - // We should never get here because we're hiding the new conversation button in the - // ShareIntentFragment by setting HIDE_NEW_CONVERSATION_BUTTON_KEY in the arguments. - finish(); - } - - // Write the ConversationId to the SharedPreferences object for this widget - static void saveConversationidPref(int appWidgetId, String conversationId) { - final BuglePrefs prefs = Factory.get().getWidgetPrefs(); - prefs.putString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, conversationId); - } - - // Read the ConversationId from the SharedPreferences object for this widget. - public static String getConversationIdPref(int appWidgetId) { - final BuglePrefs prefs = Factory.get().getWidgetPrefs(); - return prefs.getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, null); - } - - // Delete the ConversationId preference from the SharedPreferences object for this widget. - public static void deleteConversationIdPref(int appWidgetId) { - final BuglePrefs prefs = Factory.get().getWidgetPrefs(); - prefs.remove(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId); - } - -} diff --git a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java deleted file mode 100644 index 21529c6..0000000 --- a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.animation; - -import android.animation.TypeEvaluator; -import android.app.Activity; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Rect; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.Transformation; -import android.widget.PopupWindow; - -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.ThreadUtil; -import com.android.messaging.util.UiUtils; - -/** - * Animates viewToAnimate from startRect to the place where it is in the layout, viewToAnimate - * should be in its final destination location before startAfterLayoutComplete is called. - * viewToAnimate will be drawn scaled and offset in a popupWindow. - * This class handles the case where the viewToAnimate moves during the animation - */ -public class PopupTransitionAnimation extends Animation { - /** The view we're animating */ - private final View mViewToAnimate; - - /** The rect to start the slide in animation from */ - private final Rect mStartRect; - - /** The rect of the currently animated view */ - private Rect mCurrentRect; - - /** The rect that we're animating to. This can change during the animation */ - private final Rect mDestRect; - - /** The bounds of the popup in window coordinates. Does not include notification bar */ - private final Rect mPopupRect; - - /** The bounds of the action bar in window coordinates. We clip the popup to below this */ - private final Rect mActionBarRect; - - /** Interpolates between the start and end rect for every animation tick */ - private final TypeEvaluator mRectEvaluator; - - /** The popup window that holds contains the animating view */ - private PopupWindow mPopupWindow; - - /** The layout root for the popup which is where the animated view is rendered */ - private View mPopupRoot; - - /** The action bar's view */ - private final View mActionBarView; - - private Runnable mOnStartCallback; - private Runnable mOnStopCallback; - - public PopupTransitionAnimation(final Rect startRect, final View viewToAnimate) { - mViewToAnimate = viewToAnimate; - mStartRect = startRect; - mCurrentRect = new Rect(mStartRect); - mDestRect = new Rect(); - mPopupRect = new Rect(); - mActionBarRect = new Rect(); - final Activity activity = (Activity) viewToAnimate.getRootView().getContext(); - mActionBarView = activity.getWindow().getDecorView().findViewById( - android.support.v7.appcompat.R.id.action_bar); - mRectEvaluator = RectEvaluatorCompat.create(); - setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION); - setInterpolator(UiUtils.DEFAULT_INTERPOLATOR); - setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(final Animation animation) { - if (mOnStartCallback != null) { - mOnStartCallback.run(); - } - mEvents.append("oAS,"); - } - - @Override - public void onAnimationEnd(final Animation animation) { - if (mOnStopCallback != null) { - mOnStopCallback.run(); - } - dismiss(); - mEvents.append("oAE,"); - } - - @Override - public void onAnimationRepeat(final Animation animation) { - } - }); - } - - private final StringBuilder mEvents = new StringBuilder(); - private final Runnable mCleanupRunnable = new Runnable() { - @Override - public void run() { - LogUtil.w(LogUtil.BUGLE_TAG, "PopupTransitionAnimation: " + mEvents); - } - }; - - /** - * Ensures the animation is ready before starting the animation. - * viewToAnimate must first be layed out so we know where we will animate to - */ - public void startAfterLayoutComplete() { - // We want layout to occur, and then we immediately animate it in, so hide it initially to - // reduce jank on the first frame - mViewToAnimate.setVisibility(View.INVISIBLE); - mViewToAnimate.setAlpha(0); - - final Runnable startAnimation = new Runnable() { - boolean mRunComplete = false; - boolean mFirstTry = true; - - @Override - public void run() { - if (mRunComplete) { - return; - } - - mViewToAnimate.getGlobalVisibleRect(mDestRect); - // In Android views which are visible but haven't computed their size yet have a - // size of 1x1 because anything with a size of 0x0 is considered hidden. We can't - // start the animation until after the size is greater than 1x1 - if (mDestRect.width() <= 1 || mDestRect.height() <= 1) { - // Layout hasn't occurred yet - if (!mFirstTry) { - // Give up if this is not the first try, since layout change still doesn't - // yield a size for the view. This is likely because the media picker is - // full screen so there's no space left for the animated view. We give up - // on animation, but need to make sure the view that was initially - // hidden is re-shown. - mViewToAnimate.setAlpha(1); - mViewToAnimate.setVisibility(View.VISIBLE); - } else { - mFirstTry = false; - UiUtils.doOnceAfterLayoutChange(mViewToAnimate, this); - } - return; - } - - mRunComplete = true; - mViewToAnimate.startAnimation(PopupTransitionAnimation.this); - mViewToAnimate.invalidate(); - // http://b/20856505: The PopupWindow sometimes does not get dismissed. - ThreadUtil.getMainThreadHandler().postDelayed(mCleanupRunnable, getDuration() * 2); - } - }; - - startAnimation.run(); - } - - public PopupTransitionAnimation setOnStartCallback(final Runnable onStart) { - mOnStartCallback = onStart; - return this; - } - - public PopupTransitionAnimation setOnStopCallback(final Runnable onStop) { - mOnStopCallback = onStop; - return this; - } - - @Override - protected void applyTransformation(final float interpolatedTime, final Transformation t) { - if (mPopupWindow == null) { - initPopupWindow(); - } - // Update mDestRect as it may have moved during the animation - mPopupRect.set(UiUtils.getMeasuredBoundsOnScreen(mPopupRoot)); - mActionBarRect.set(UiUtils.getMeasuredBoundsOnScreen(mActionBarView)); - computeDestRect(); - - // Update currentRect to the new animated coordinates, and request mPopupRoot to redraw - // itself at the new coordinates - mCurrentRect = mRectEvaluator.evaluate(interpolatedTime, mStartRect, mDestRect); - mPopupRoot.invalidate(); - - if (interpolatedTime >= 0.98) { - mEvents.append("aT").append(interpolatedTime).append(','); - } - if (interpolatedTime == 1) { - dismiss(); - } - } - - private void dismiss() { - mEvents.append("d,"); - mViewToAnimate.setAlpha(1); - mViewToAnimate.setVisibility(View.VISIBLE); - // Delay dismissing the popup window to let mViewToAnimate draw under it and reduce the - // flash - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - try { - mPopupWindow.dismiss(); - } catch (IllegalArgumentException e) { - // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity - // has already ended while we were animating - } - ThreadUtil.getMainThreadHandler().removeCallbacks(mCleanupRunnable); - } - }); - } - - @Override - public boolean willChangeBounds() { - return false; - } - - /** - * Computes mDestRect (the position in window space of the placeholder view that we should - * animate to). Some frames during the animation fail to compute getGlobalVisibleRect, so use - * the last known values in that case - */ - private void computeDestRect() { - final int prevTop = mDestRect.top; - final int prevLeft = mDestRect.left; - final int prevRight = mDestRect.right; - final int prevBottom = mDestRect.bottom; - - if (!getViewScreenMeasureRect(mViewToAnimate, mDestRect)) { - mDestRect.top = prevTop; - mDestRect.left = prevLeft; - mDestRect.bottom = prevBottom; - mDestRect.right = prevRight; - } - } - - /** - * Sets up the PopupWindow that the view will animate in. Animating the size and position of a - * popup can be choppy, so instead we make the popup fill the entire space of the screen, and - * animate the position of viewToAnimate within the popup using a Transformation - */ - private void initPopupWindow() { - mPopupRoot = new View(mViewToAnimate.getContext()) { - @Override - protected void onDraw(final Canvas canvas) { - canvas.save(); - canvas.clipRect(getLeft(), mActionBarRect.bottom - mPopupRect.top, getRight(), - getBottom()); - canvas.drawColor(Color.TRANSPARENT); - final float previousAlpha = mViewToAnimate.getAlpha(); - mViewToAnimate.setAlpha(1); - // The view's global position includes the notification bar height, but - // the popup window may or may not cover the notification bar (depending on screen - // rotation, IME status etc.), so we need to compensate for this difference by - // offseting vertically. - canvas.translate(mCurrentRect.left, mCurrentRect.top - mPopupRect.top); - - final float viewWidth = mViewToAnimate.getWidth(); - final float viewHeight = mViewToAnimate.getHeight(); - if (viewWidth > 0 && viewHeight > 0) { - canvas.scale(mCurrentRect.width() / viewWidth, - mCurrentRect.height() / viewHeight); - } - canvas.clipRect(0, 0, mCurrentRect.width(), mCurrentRect.height()); - if (!mPopupRect.isEmpty()) { - // HACK: Layout is unstable until mPopupRect is non-empty. - mViewToAnimate.draw(canvas); - } - mViewToAnimate.setAlpha(previousAlpha); - canvas.restore(); - } - }; - mPopupWindow = new PopupWindow(mViewToAnimate.getContext()); - mPopupWindow.setBackgroundDrawable(null); - mPopupWindow.setContentView(mPopupRoot); - mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); - mPopupWindow.setHeight(ViewGroup.LayoutParams.MATCH_PARENT); - mPopupWindow.setTouchable(false); - // We must pass a non-zero value for the y offset, or else the system resets the status bar - // color to black (M only) during the animation. The actual position of the window (and - // the animated view inside it) are still correct, regardless of what we pass for the y - // parameter (e.g. 1 and 100 both work). Not entirely sure why this works. - mPopupWindow.showAtLocation(mViewToAnimate, Gravity.TOP, 0, 1); - } - - private static boolean getViewScreenMeasureRect(final View view, final Rect outRect) { - outRect.set(UiUtils.getMeasuredBoundsOnScreen(view)); - return !outRect.isEmpty(); - } -} diff --git a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java b/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java deleted file mode 100644 index e3c60fc..0000000 --- a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.animation; - -import android.animation.RectEvaluator; -import android.animation.TypeEvaluator; -import android.graphics.Rect; - -import com.android.messaging.util.OsUtil; - -/** - * This evaluator can be used to perform type interpolation between Rect values. - * It's backward compatible to Api Level 11. - */ -public class RectEvaluatorCompat implements TypeEvaluator { - public static TypeEvaluator create() { - if (OsUtil.isAtLeastJB_MR2()) { - return new RectEvaluator(); - } else { - return new RectEvaluatorCompat(); - } - } - - @Override - public Rect evaluate(float fraction, Rect startValue, Rect endValue) { - int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction); - int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction); - int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction); - int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction); - return new Rect(left, top, right, bottom); - } -} diff --git a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java deleted file mode 100644 index 6abfdf9..0000000 --- a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.animation; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.support.v4.view.ViewCompat; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroupOverlay; -import android.view.ViewOverlay; -import android.widget.FrameLayout; - -import com.android.messaging.R; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -/** - *

- * Shows a vertical "explode" animation for any view inside a view group (e.g. views inside a - * ListView). During the animation, a snapshot is taken for the view to the animated and - * presented in a popup window or view overlay on top of the original view group. The background - * of the view (a highlight) vertically expands (explodes) during the animation. - *

- *

- * The exact implementation of the animation depends on platform API level. For JB_MR2 and later, - * the implementation utilizes ViewOverlay to perform highly performant overlay animations; for - * older API levels, the implementation falls back to using a full screen popup window to stage - * the animation. - *

- *

- * To start this animation, call {@link #startAnimationForView(ViewGroup, View, View, boolean, int)} - *

- */ -public class ViewGroupItemVerticalExplodeAnimation { - /** - * Starts a vertical explode animation for a given view situated in a given container. - * - * @param container the container of the view which determines the explode animation's final - * size - * @param viewToAnimate the view to be animated. The view will be highlighted by the explode - * highlight, which expands from the size of the view to the size of the container. - * @param animationStagingView the view that stages the animation. Since viewToAnimate may be - * removed from the view tree during the animation, we need a view that'll be alive - * for the duration of the animation so that the animation won't get cancelled. - * @param snapshotView whether a snapshot of the view to animate is needed. - */ - public static void startAnimationForView(final ViewGroup container, final View viewToAnimate, - final View animationStagingView, final boolean snapshotView, final int duration) { - if (OsUtil.isAtLeastJB_MR2() && (viewToAnimate.getContext() instanceof Activity)) { - new ViewExplodeAnimationJellyBeanMR2(viewToAnimate, container, snapshotView, duration) - .startAnimation(); - } else { - // Pre JB_MR2, this animation can cause rendering failures which causes the framework - // to fall back to software rendering where camera preview isn't supported (b/18264647) - // just skip the animation to avoid this case. - } - } - - /** - * Implementation class for API level >= 18. - */ - @TargetApi(18) - private static class ViewExplodeAnimationJellyBeanMR2 { - private final View mViewToAnimate; - private final ViewGroup mContainer; - private final View mSnapshot; - private final Bitmap mViewBitmap; - private final int mDuration; - - public ViewExplodeAnimationJellyBeanMR2(final View viewToAnimate, final ViewGroup container, - final boolean snapshotView, final int duration) { - mViewToAnimate = viewToAnimate; - mContainer = container; - mDuration = duration; - if (snapshotView) { - mViewBitmap = snapshotView(viewToAnimate); - mSnapshot = new View(viewToAnimate.getContext()); - } else { - mSnapshot = null; - mViewBitmap = null; - } - } - - public void startAnimation() { - final Context context = mViewToAnimate.getContext(); - final Resources resources = context.getResources(); - final View decorView = ((Activity) context).getWindow().getDecorView(); - final ViewOverlay viewOverlay = decorView.getOverlay(); - if (viewOverlay instanceof ViewGroupOverlay) { - final ViewGroupOverlay overlay = (ViewGroupOverlay) viewOverlay; - - // Add a shadow layer to the overlay. - final FrameLayout shadowContainerLayer = new FrameLayout(context); - final Drawable oldBackground = mViewToAnimate.getBackground(); - final Rect containerRect = UiUtils.getMeasuredBoundsOnScreen(mContainer); - final Rect decorRect = UiUtils.getMeasuredBoundsOnScreen(decorView); - // Position the container rect relative to the decor rect since the decor rect - // defines whether the view overlay will be positioned. - containerRect.offset(-decorRect.left, -decorRect.top); - shadowContainerLayer.setLeft(containerRect.left); - shadowContainerLayer.setTop(containerRect.top); - shadowContainerLayer.setBottom(containerRect.bottom); - shadowContainerLayer.setRight(containerRect.right); - shadowContainerLayer.setBackgroundColor(resources.getColor( - R.color.open_conversation_animation_background_shadow)); - // Per design request, temporarily clear out the background of the item content - // to not show any ripple effects during animation. - if (!(oldBackground instanceof ColorDrawable)) { - mViewToAnimate.setBackground(null); - } - overlay.add(shadowContainerLayer); - - // Add a expand layer and position it with in the shadow background, so it can - // be properly clipped to the container bounds during the animation. - final View expandLayer = new View(context); - final int elevation = resources.getDimensionPixelSize( - R.dimen.explode_animation_highlight_elevation); - final Rect viewRect = UiUtils.getMeasuredBoundsOnScreen(mViewToAnimate); - // Frame viewRect from screen space to containerRect space. - viewRect.offset(-containerRect.left - decorRect.left, - -containerRect.top - decorRect.top); - // Since the expand layer expands at the same rate above and below, we need to - // compute the expand scale using the bigger of the top/bottom distances. - final int expandLayerHalfHeight = viewRect.height() / 2; - final int topDist = viewRect.top; - final int bottomDist = containerRect.height() - viewRect.bottom; - final float scale = expandLayerHalfHeight == 0 ? 1 : - ((float) Math.max(topDist, bottomDist) + expandLayerHalfHeight) / - expandLayerHalfHeight; - // Position the expand layer initially to exactly match the animated item. - shadowContainerLayer.addView(expandLayer); - expandLayer.setLeft(viewRect.left); - expandLayer.setTop(viewRect.top); - expandLayer.setBottom(viewRect.bottom); - expandLayer.setRight(viewRect.right); - expandLayer.setBackgroundColor(resources.getColor( - R.color.conversation_background)); - ViewCompat.setElevation(expandLayer, elevation); - - // Conditionally stage the snapshot in the overlay. - if (mSnapshot != null) { - shadowContainerLayer.addView(mSnapshot); - mSnapshot.setLeft(viewRect.left); - mSnapshot.setTop(viewRect.top); - mSnapshot.setBottom(viewRect.bottom); - mSnapshot.setRight(viewRect.right); - mSnapshot.setBackground(new BitmapDrawable(resources, mViewBitmap)); - ViewCompat.setElevation(mSnapshot, elevation); - } - - // Apply a scale animation to scale to full screen. - expandLayer.animate().scaleY(scale) - .setDuration(mDuration) - .setInterpolator(UiUtils.EASE_IN_INTERPOLATOR) - .withEndAction(new Runnable() { - @Override - public void run() { - // Clean up the views added to overlay on animation finish. - overlay.remove(shadowContainerLayer); - mViewToAnimate.setBackground(oldBackground); - if (mViewBitmap != null) { - mViewBitmap.recycle(); - } - } - }); - } - } - } - - /** - * Take a snapshot of the given review, return a Bitmap object that's owned by the caller. - */ - static Bitmap snapshotView(final View view) { - // Save the content of the view into a bitmap. - final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(), - view.getHeight(), Bitmap.Config.ARGB_8888); - // Strip the view of its background when taking a snapshot so that things like touch - // feedback don't get accidentally snapshotted. - final Drawable viewBackground = view.getBackground(); - ImageUtils.setBackgroundDrawableOnView(view, null); - view.draw(new Canvas(viewBitmap)); - ImageUtils.setBackgroundDrawableOnView(view, viewBackground); - return viewBitmap; - } -} diff --git a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java b/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java deleted file mode 100644 index b7cb7ae..0000000 --- a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.ContentValues; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.provider.Telephony; -import android.support.v4.app.NavUtils; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.ApnDatabase; -import com.android.messaging.sms.BugleApnSettingsLoader; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.PhoneUtils; - -public class ApnEditorActivity extends BugleActionBarActivity { - private static final int ERROR_DIALOG_ID = 0; - private static final String ERROR_MESSAGE_KEY = "error_msg"; - private ApnEditorFragment mApnEditorFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Display the fragment as the main content. - mApnEditorFragment = new ApnEditorFragment(); - mApnEditorFragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID, - ParticipantData.DEFAULT_SELF_SUB_ID)); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, mApnEditorFragment) - .commit(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected Dialog onCreateDialog(int id, Bundle args) { - - if (id == ERROR_DIALOG_ID) { - String msg = args.getString(ERROR_MESSAGE_KEY); - - return new AlertDialog.Builder(this) - .setPositiveButton(android.R.string.ok, null) - .setMessage(msg) - .create(); - } - - return super.onCreateDialog(id); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: { - if (mApnEditorFragment.validateAndSave(false)) { - finish(); - } - return true; - } - } - return super.onKeyDown(keyCode, event); - } - - @Override - protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { - super.onPrepareDialog(id, dialog); - - if (id == ERROR_DIALOG_ID) { - final String msg = args.getString(ERROR_MESSAGE_KEY); - - if (msg != null) { - ((AlertDialog) dialog).setMessage(msg); - } - } - } - - public static class ApnEditorFragment extends PreferenceFragment implements - SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String SAVED_POS = "pos"; - - private static final int MENU_DELETE = Menu.FIRST; - private static final int MENU_SAVE = Menu.FIRST + 1; - private static final int MENU_CANCEL = Menu.FIRST + 2; - - private EditTextPreference mMmsProxy; - private EditTextPreference mMmsPort; - private EditTextPreference mName; - private EditTextPreference mMmsc; - private EditTextPreference mMcc; - private EditTextPreference mMnc; - private static String sNotSet; - - private String mCurMnc; - private String mCurMcc; - - private Cursor mCursor; - private boolean mNewApn; - private boolean mFirstTime; - private String mCurrentId; - - private int mSubId; - - /** - * Standard projection for the interesting columns of a normal note. - */ - private static final String[] sProjection = new String[] { - Telephony.Carriers._ID, // 0 - Telephony.Carriers.NAME, // 1 - Telephony.Carriers.MMSC, // 2 - Telephony.Carriers.MCC, // 3 - Telephony.Carriers.MNC, // 4 - Telephony.Carriers.NUMERIC, // 5 - Telephony.Carriers.MMSPROXY, // 6 - Telephony.Carriers.MMSPORT, // 7 - Telephony.Carriers.TYPE, // 8 - }; - - private static final int ID_INDEX = 0; - private static final int NAME_INDEX = 1; - private static final int MMSC_INDEX = 2; - private static final int MCC_INDEX = 3; - private static final int MNC_INDEX = 4; - private static final int NUMERIC_INDEX = 5; - private static final int MMSPROXY_INDEX = 6; - private static final int MMSPORT_INDEX = 7; - private static final int TYPE_INDEX = 8; - - private SQLiteDatabase mDatabase; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - addPreferencesFromResource(R.xml.apn_editor); - - setHasOptionsMenu(true); - - sNotSet = getResources().getString(R.string.apn_not_set); - mName = (EditTextPreference) findPreference("apn_name"); - mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy"); - mMmsPort = (EditTextPreference) findPreference("apn_mms_port"); - mMmsc = (EditTextPreference) findPreference("apn_mmsc"); - mMcc = (EditTextPreference) findPreference("apn_mcc"); - mMnc = (EditTextPreference) findPreference("apn_mnc"); - - final Intent intent = getActivity().getIntent(); - - mFirstTime = savedInstanceState == null; - mCurrentId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_APN_ROW_ID); - mNewApn = mCurrentId == null; - - mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase(); - - if (mNewApn) { - fillUi(); - } else { - // Do initial query not on the UI thread - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - if (mCurrentId != null) { - String selection = Telephony.Carriers._ID + " =?"; - String[] selectionArgs = new String[]{ mCurrentId }; - mCursor = mDatabase.query(ApnDatabase.APN_TABLE, sProjection, selection, - selectionArgs, null, null, null, null); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - if (mCursor == null) { - getActivity().finish(); - return; - } - mCursor.moveToFirst(); - - fillUi(); - } - }.execute((Void) null); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mCursor != null) { - mCursor.close(); - mCursor = null; - } - } - - @Override - public void onResume() { - super.onResume(); - getPreferenceScreen().getSharedPreferences() - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() { - getPreferenceScreen().getSharedPreferences() - .unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); - } - - public void setSubId(final int subId) { - mSubId = subId; - } - - private void fillUi() { - if (mNewApn) { - mMcc.setText(null); - mMnc.setText(null); - String numeric = PhoneUtils.get(mSubId).getSimOperatorNumeric(); - // MCC is first 3 chars and then in 2 - 3 chars of MNC - if (numeric != null && numeric.length() > 4) { - // Country code - String mcc = numeric.substring(0, 3); - // Network code - String mnc = numeric.substring(3); - // Auto populate MNC and MCC for new entries, based on what SIM reports - mMcc.setText(mcc); - mMnc.setText(mnc); - mCurMnc = mnc; - mCurMcc = mcc; - } - mName.setText(null); - mMmsProxy.setText(null); - mMmsPort.setText(null); - mMmsc.setText(null); - } else if (mFirstTime) { - mFirstTime = false; - // Fill in all the values from the db in both text editor and summary - mName.setText(mCursor.getString(NAME_INDEX)); - mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX)); - mMmsPort.setText(mCursor.getString(MMSPORT_INDEX)); - mMmsc.setText(mCursor.getString(MMSC_INDEX)); - mMcc.setText(mCursor.getString(MCC_INDEX)); - mMnc.setText(mCursor.getString(MNC_INDEX)); - } - - mName.setSummary(checkNull(mName.getText())); - mMmsProxy.setSummary(checkNull(mMmsProxy.getText())); - mMmsPort.setSummary(checkNull(mMmsPort.getText())); - mMmsc.setSummary(checkNull(mMmsc.getText())); - mMcc.setSummary(checkNull(mMcc.getText())); - mMnc.setSummary(checkNull(mMnc.getText())); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - // If it's a new APN, then cancel will delete the new entry in onPause - if (!mNewApn) { - menu.add(0, MENU_DELETE, 0, R.string.menu_delete_apn) - .setIcon(R.drawable.ic_delete_small_dark); - } - menu.add(0, MENU_SAVE, 0, R.string.menu_save_apn) - .setIcon(android.R.drawable.ic_menu_save); - menu.add(0, MENU_CANCEL, 0, R.string.menu_discard_apn_change) - .setIcon(android.R.drawable.ic_menu_close_clear_cancel); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_DELETE: - deleteApn(); - return true; - - case MENU_SAVE: - if (validateAndSave(false)) { - getActivity().finish(); - } - return true; - - case MENU_CANCEL: - getActivity().finish(); - return true; - - case android.R.id.home: - getActivity().onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onSaveInstanceState(Bundle icicle) { - super.onSaveInstanceState(icicle); - if (validateAndSave(true) && mCursor != null) { - icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX)); - } - } - - /** - * Check the key fields' validity and save if valid. - * @param force save even if the fields are not valid, if the app is - * being suspended - * @return true if the data was saved - */ - private boolean validateAndSave(boolean force) { - final String name = checkNotSet(mName.getText()); - final String mcc = checkNotSet(mMcc.getText()); - final String mnc = checkNotSet(mMnc.getText()); - - if (getErrorMsg() != null && !force) { - final Bundle bundle = new Bundle(); - bundle.putString(ERROR_MESSAGE_KEY, getErrorMsg()); - getActivity().showDialog(ERROR_DIALOG_ID, bundle); - return false; - } - - // Make database changes not on the UI thread - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - ContentValues values = new ContentValues(); - - // Add a dummy name "Untitled", if the user exits the screen without adding a - // name but entered other information worth keeping. - values.put(Telephony.Carriers.NAME, name.length() < 1 ? - getResources().getString(R.string.untitled_apn) : name); - values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText())); - values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText())); - values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText())); - - values.put(Telephony.Carriers.TYPE, BugleApnSettingsLoader.APN_TYPE_MMS); - - values.put(Telephony.Carriers.MCC, mcc); - values.put(Telephony.Carriers.MNC, mnc); - - values.put(Telephony.Carriers.NUMERIC, mcc + mnc); - - if (mCurMnc != null && mCurMcc != null) { - if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) { - values.put(Telephony.Carriers.CURRENT, 1); - } - } - - if (mNewApn) { - mDatabase.insert(ApnDatabase.APN_TABLE, null, values); - } else { - // update the APN - String selection = Telephony.Carriers._ID + " =?"; - String[] selectionArgs = new String[]{ mCurrentId }; - int updated = mDatabase.update(ApnDatabase.APN_TABLE, values, - selection, selectionArgs); - } - return null; - } - }.execute((Void) null); - - return true; - } - - private void deleteApn() { - // Make database changes not on the UI thread - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - // delete the APN - String where = Telephony.Carriers._ID + " =?"; - String[] whereArgs = new String[]{ mCurrentId }; - - mDatabase.delete(ApnDatabase.APN_TABLE, where, whereArgs); - return null; - } - }.execute((Void) null); - - getActivity().finish(); - } - - private String checkNull(String value) { - if (value == null || value.length() == 0) { - return sNotSet; - } else { - return value; - } - } - - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - Preference pref = findPreference(key); - if (pref != null) { - pref.setSummary(checkNull(sharedPreferences.getString(key, ""))); - } - } - - private String getErrorMsg() { - String errorMsg = null; - - String name = checkNotSet(mName.getText()); - String mcc = checkNotSet(mMcc.getText()); - String mnc = checkNotSet(mMnc.getText()); - - if (name.length() < 1) { - errorMsg = getString(R.string.error_apn_name_empty); - } else if (mcc.length() != 3) { - errorMsg = getString(R.string.error_mcc_not3); - } else if ((mnc.length() & 0xFFFE) != 2) { - errorMsg = getString(R.string.error_mnc_not23); - } - - return errorMsg; - } - - private String checkNotSet(String value) { - if (value == null || value.equals(sNotSet)) { - return ""; - } else { - return value; - } - } - } -} diff --git a/src/com/android/messaging/ui/appsettings/ApnPreference.java b/src/com/android/messaging/ui/appsettings/ApnPreference.java deleted file mode 100644 index 74c6a08..0000000 --- a/src/com/android/messaging/ui/appsettings/ApnPreference.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.content.Context; -import android.preference.Preference; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.RadioButton; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.ui.UIIntents; - -/** - * ApnPreference implements a pref, typically used as a list item, that has a title/summary on - * the left and a radio button on the right. - * - */ -public class ApnPreference extends Preference implements - CompoundButton.OnCheckedChangeListener, OnClickListener { - static final String TAG = "ApnPreference"; - - public ApnPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public ApnPreference(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.apnPreferenceStyle); - } - - public ApnPreference(Context context) { - this(context, null); - } - - private static String mSelectedKey = null; - private static CompoundButton mCurrentChecked = null; - private boolean mProtectFromCheckedChange = false; - private boolean mSelectable = true; - private int mSubId = ParticipantData.DEFAULT_SELF_SUB_ID; - - @Override - public View getView(View convertView, ViewGroup parent) { - View view = super.getView(convertView, parent); - - View widget = view.findViewById(R.id.apn_radiobutton); - if ((widget != null) && widget instanceof RadioButton) { - RadioButton rb = (RadioButton) widget; - if (mSelectable) { - rb.setOnCheckedChangeListener(this); - - boolean isChecked = getKey().equals(mSelectedKey); - if (isChecked) { - mCurrentChecked = rb; - mSelectedKey = getKey(); - } - - mProtectFromCheckedChange = true; - rb.setChecked(isChecked); - mProtectFromCheckedChange = false; - } else { - rb.setVisibility(View.GONE); - } - setApnRadioButtonContentDescription(rb); - } - - View textLayout = view.findViewById(R.id.text_layout); - if ((textLayout != null) && textLayout instanceof RelativeLayout) { - textLayout.setOnClickListener(this); - } - - return view; - } - - public void setApnRadioButtonContentDescription(final CompoundButton buttonView) { - final View widget = (View) buttonView.getParent(); - final TextView tv = (TextView) widget.findViewById(android.R.id.title); - final String apnTitle = tv.getText().toString(); - buttonView.setContentDescription(apnTitle); - } - - public boolean isChecked() { - return getKey().equals(mSelectedKey); - } - - public void setChecked() { - mSelectedKey = getKey(); - } - - public void setSubId(final int subId) { - mSubId = subId; - } - - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Log.i(TAG, "ID: " + getKey() + " :" + isChecked); - if (mProtectFromCheckedChange) { - return; - } - - if (isChecked) { - if (mCurrentChecked != null) { - mCurrentChecked.setChecked(false); - } - mCurrentChecked = buttonView; - mSelectedKey = getKey(); - callChangeListener(mSelectedKey); - } else { - mCurrentChecked = null; - mSelectedKey = null; - } - setApnRadioButtonContentDescription(buttonView); - } - - public void onClick(android.view.View v) { - if ((v != null) && (R.id.text_layout == v.getId())) { - Context context = getContext(); - if (context != null) { - context.startActivity( - UIIntents.get().getApnEditorIntent(context, getKey(), mSubId)); - } - } - } - - public void setSelectable(boolean selectable) { - mSelectable = selectable; - } - - public boolean getSelectable() { - return mSelectable; - } -} diff --git a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java deleted file mode 100644 index 28dfc2a..0000000 --- a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.app.Activity; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.UserManager; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; -import android.provider.Telephony; -import android.support.v4.app.NavUtils; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.ApnDatabase; -import com.android.messaging.sms.BugleApnSettingsLoader; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -public class ApnSettingsActivity extends BugleActionBarActivity { - private static final int DIALOG_RESTORE_DEFAULTAPN = 1001; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Display the fragment as the main content. - final ApnSettingsFragment fragment = new ApnSettingsFragment(); - fragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID, - ParticipantData.DEFAULT_SELF_SUB_ID)); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, fragment) - .commit(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected Dialog onCreateDialog(int id) { - if (id == DIALOG_RESTORE_DEFAULTAPN) { - ProgressDialog dialog = new ProgressDialog(this); - dialog.setMessage(getResources().getString(R.string.restore_default_apn)); - dialog.setCancelable(false); - return dialog; - } - return null; - } - - public static class ApnSettingsFragment extends PreferenceFragment implements - Preference.OnPreferenceChangeListener { - public static final String EXTRA_POSITION = "position"; - - public static final String APN_ID = "apn_id"; - - private static final String[] APN_PROJECTION = { - Telephony.Carriers._ID, // 0 - Telephony.Carriers.NAME, // 1 - Telephony.Carriers.APN, // 2 - Telephony.Carriers.TYPE // 3 - }; - private static final int ID_INDEX = 0; - private static final int NAME_INDEX = 1; - private static final int APN_INDEX = 2; - private static final int TYPES_INDEX = 3; - - private static final int MENU_NEW = Menu.FIRST; - private static final int MENU_RESTORE = Menu.FIRST + 1; - - private static final int EVENT_RESTORE_DEFAULTAPN_START = 1; - private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2; - - private static boolean mRestoreDefaultApnMode; - - private RestoreApnUiHandler mRestoreApnUiHandler; - private RestoreApnProcessHandler mRestoreApnProcessHandler; - private HandlerThread mRestoreDefaultApnThread; - - private String mSelectedKey; - - private static final ContentValues sCurrentNullMap; - private static final ContentValues sCurrentSetMap; - - private UserManager mUm; - - private boolean mUnavailable; - private int mSubId; - - static { - sCurrentNullMap = new ContentValues(1); - sCurrentNullMap.putNull(Telephony.Carriers.CURRENT); - - sCurrentSetMap = new ContentValues(1); - sCurrentSetMap.put(Telephony.Carriers.CURRENT, "2"); // 2 for user-selected APN, - // 1 for Bugle-selected APN - } - - private SQLiteDatabase mDatabase; - - public void setSubId(final int subId) { - mSubId = subId; - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase(); - - if (OsUtil.isAtLeastL()) { - mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); - if (!mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) { - setHasOptionsMenu(true); - } - } else { - setHasOptionsMenu(true); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final ListView lv = (ListView) getView().findViewById(android.R.id.list); - TextView empty = (TextView) getView().findViewById(android.R.id.empty); - if (empty != null) { - empty.setText(R.string.apn_settings_not_available); - lv.setEmptyView(empty); - } - - if (OsUtil.isAtLeastL() && - mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) { - mUnavailable = true; - setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity())); - return; - } - - addPreferencesFromResource(R.xml.apn_settings); - - lv.setItemsCanFocus(true); - } - - @Override - public void onResume() { - super.onResume(); - - if (mUnavailable) { - return; - } - - if (!mRestoreDefaultApnMode) { - fillList(); - } - } - - @Override - public void onPause() { - super.onPause(); - - if (mUnavailable) { - return; - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (mRestoreDefaultApnThread != null) { - mRestoreDefaultApnThread.quit(); - } - } - - private void fillList() { - final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.get(mSubId).getMccMnc()); - - new AsyncTask() { - @Override - protected Cursor doInBackground(Void... params) { - String selection = Telephony.Carriers.NUMERIC + " =?"; - String[] selectionArgs = new String[]{ mccMnc }; - final Cursor cursor = mDatabase.query(ApnDatabase.APN_TABLE, APN_PROJECTION, - selection, selectionArgs, null, null, null, null); - return cursor; - } - - @Override - protected void onPostExecute(Cursor cursor) { - if (cursor != null) { - try { - PreferenceGroup apnList = (PreferenceGroup) - findPreference(getString(R.string.apn_list_pref_key)); - apnList.removeAll(); - - mSelectedKey = BugleApnSettingsLoader.getFirstTryApn(mDatabase, mccMnc); - while (cursor.moveToNext()) { - String name = cursor.getString(NAME_INDEX); - String apn = cursor.getString(APN_INDEX); - String key = cursor.getString(ID_INDEX); - String type = cursor.getString(TYPES_INDEX); - - if (BugleApnSettingsLoader.isValidApnType(type, - BugleApnSettingsLoader.APN_TYPE_MMS)) { - ApnPreference pref = new ApnPreference(getActivity()); - pref.setKey(key); - pref.setTitle(name); - pref.setSummary(apn); - pref.setPersistent(false); - pref.setOnPreferenceChangeListener(ApnSettingsFragment.this); - pref.setSelectable(true); - - // Turn on the radio button for the currently selected APN. If - // there is no selected APN, don't select an APN. - if ((mSelectedKey != null && mSelectedKey.equals(key))) { - pref.setChecked(); - } - apnList.addPreference(pref); - } - } - } finally { - cursor.close(); - } - } - } - }.execute((Void) null); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (!mUnavailable) { - menu.add(0, MENU_NEW, 0, - getResources().getString(R.string.menu_new_apn)) - .setIcon(R.drawable.ic_add_gray) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(0, MENU_RESTORE, 0, - getResources().getString(R.string.menu_restore_default_apn)) - .setIcon(android.R.drawable.ic_menu_upload); - } - - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_NEW: - addNewApn(); - return true; - - case MENU_RESTORE: - restoreDefaultApn(); - return true; - } - return super.onOptionsItemSelected(item); - } - - private void addNewApn() { - startActivity(UIIntents.get().getApnEditorIntent(getActivity(), null, mSubId)); - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, - Preference preference) { - startActivity( - UIIntents.get().getApnEditorIntent(getActivity(), preference.getKey(), mSubId)); - return true; - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (newValue instanceof String) { - setSelectedApnKey((String) newValue); - } - - return true; - } - - // current=2 means user selected APN - private static final String UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?"; - private static final String[] UPDATE_SELECTION_ARGS = new String[] { "2" }; - private void setSelectedApnKey(final String key) { - mSelectedKey = key; - - // Make database changes not on the UI thread - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - // null out the previous "current=2" APN - mDatabase.update(ApnDatabase.APN_TABLE, sCurrentNullMap, - UPDATE_SELECTION, UPDATE_SELECTION_ARGS); - - // set the new "current" APN (2) - String selection = Telephony.Carriers._ID + " =?"; - String[] selectionArgs = new String[]{ key }; - - mDatabase.update(ApnDatabase.APN_TABLE, sCurrentSetMap, - selection, selectionArgs); - return null; - } - }.execute((Void) null); - } - - private boolean restoreDefaultApn() { - getActivity().showDialog(DIALOG_RESTORE_DEFAULTAPN); - mRestoreDefaultApnMode = true; - - if (mRestoreApnUiHandler == null) { - mRestoreApnUiHandler = new RestoreApnUiHandler(); - } - - if (mRestoreApnProcessHandler == null || - mRestoreDefaultApnThread == null) { - mRestoreDefaultApnThread = new HandlerThread( - "Restore default APN Handler: Process Thread"); - mRestoreDefaultApnThread.start(); - mRestoreApnProcessHandler = new RestoreApnProcessHandler( - mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler); - } - - mRestoreApnProcessHandler.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START); - return true; - } - - private class RestoreApnUiHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_RESTORE_DEFAULTAPN_COMPLETE: - fillList(); - getPreferenceScreen().setEnabled(true); - mRestoreDefaultApnMode = false; - final Activity activity = getActivity(); - activity.dismissDialog(DIALOG_RESTORE_DEFAULTAPN); - Toast.makeText(activity, getResources().getString( - R.string.restore_default_apn_completed), Toast.LENGTH_LONG) - .show(); - break; - } - } - } - - private class RestoreApnProcessHandler extends Handler { - private Handler mCachedRestoreApnUiHandler; - - public RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) { - super(looper); - this.mCachedRestoreApnUiHandler = restoreApnUiHandler; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_RESTORE_DEFAULTAPN_START: - ApnDatabase.forceBuildAndLoadApnTables(); - mCachedRestoreApnUiHandler.sendEmptyMessage( - EVENT_RESTORE_DEFAULTAPN_COMPLETE); - break; - } - } - } - } -} diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java deleted file mode 100644 index 906009f..0000000 --- a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.app.FragmentTransaction; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.preference.RingtonePreference; -import android.preference.TwoStatePreference; -import android.provider.Settings; -import android.support.v4.app.NavUtils; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.LicenseActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -public class ApplicationSettingsActivity extends BugleActionBarActivity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final boolean topLevel = getIntent().getBooleanExtra( - UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false); - if (topLevel) { - getSupportActionBar().setTitle(getString(R.string.settings_activity_title)); - } - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(android.R.id.content, new ApplicationSettingsFragment()); - ft.commit(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (super.onCreateOptionsMenu(menu)) { - return true; - } - getMenuInflater().inflate(R.menu.settings_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - case R.id.action_license: - final Intent intent = new Intent(this, LicenseActivity.class); - startActivity(intent); - return true; - } - return super.onOptionsItemSelected(item); - } - - public static class ApplicationSettingsFragment extends PreferenceFragment implements - OnSharedPreferenceChangeListener { - - private String mNotificationsEnabledPreferenceKey; - private TwoStatePreference mNotificationsEnabledPreference; - private String mRingtonePreferenceKey; - private RingtonePreference mRingtonePreference; - private Preference mVibratePreference; - private String mSmsDisabledPrefKey; - private Preference mSmsDisabledPreference; - private String mSmsEnabledPrefKey; - private Preference mSmsEnabledPreference; - private boolean mIsSmsPreferenceClicked; - - public ApplicationSettingsFragment() { - // Required empty constructor - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getPreferenceManager().setSharedPreferencesName(BuglePrefs.SHARED_PREFERENCES_NAME); - addPreferencesFromResource(R.xml.preferences_application); - - mNotificationsEnabledPreferenceKey = - getString(R.string.notifications_enabled_pref_key); - mNotificationsEnabledPreference = (TwoStatePreference) findPreference( - mNotificationsEnabledPreferenceKey); - mRingtonePreferenceKey = getString(R.string.notification_sound_pref_key); - mRingtonePreference = (RingtonePreference) findPreference(mRingtonePreferenceKey); - mVibratePreference = findPreference( - getString(R.string.notification_vibration_pref_key)); - mSmsDisabledPrefKey = getString(R.string.sms_disabled_pref_key); - mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey); - mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key); - mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey); - mIsSmsPreferenceClicked = false; - - final SharedPreferences prefs = getPreferenceScreen().getSharedPreferences(); - updateSoundSummary(prefs); - - if (!DebugUtils.isDebugEnabled()) { - final Preference debugCategory = findPreference(getString( - R.string.debug_pref_key)); - getPreferenceScreen().removePreference(debugCategory); - } - - final PreferenceScreen advancedScreen = (PreferenceScreen) findPreference( - getString(R.string.advanced_pref_key)); - final boolean topLevel = getActivity().getIntent().getBooleanExtra( - UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false); - if (topLevel) { - advancedScreen.setIntent(UIIntents.get() - .getAdvancedSettingsIntent(getPreferenceScreen().getContext())); - } else { - // Hide the Advanced settings screen if this is not top-level; these are shown at - // the parent SettingsActivity. - getPreferenceScreen().removePreference(advancedScreen); - } - } - - @Override - public boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen, - Preference preference) { - if (preference.getKey() == mSmsDisabledPrefKey || - preference.getKey() == mSmsEnabledPrefKey) { - mIsSmsPreferenceClicked = true; - } - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - - private void updateSoundSummary(final SharedPreferences sharedPreferences) { - // The silent ringtone just returns an empty string - String ringtoneName = mRingtonePreference.getContext().getString( - R.string.silent_ringtone); - - String ringtoneString = sharedPreferences.getString(mRingtonePreferenceKey, null); - - // Bootstrap the default setting in the preferences so that we have a valid selection - // in the dialog the first time that the user opens it. - if (ringtoneString == null) { - ringtoneString = Settings.System.DEFAULT_NOTIFICATION_URI.toString(); - final SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(mRingtonePreferenceKey, ringtoneString); - editor.apply(); - } - - if (!TextUtils.isEmpty(ringtoneString)) { - final Uri ringtoneUri = Uri.parse(ringtoneString); - final Ringtone tone = RingtoneManager.getRingtone(mRingtonePreference.getContext(), - ringtoneUri); - - if (tone != null) { - ringtoneName = tone.getTitle(mRingtonePreference.getContext()); - } - } - - mRingtonePreference.setSummary(ringtoneName); - } - - private void updateSmsEnabledPreferences() { - if (!OsUtil.isAtLeastKLP()) { - getPreferenceScreen().removePreference(mSmsDisabledPreference); - getPreferenceScreen().removePreference(mSmsEnabledPreference); - } else { - final String defaultSmsAppLabel = getString(R.string.default_sms_app, - PhoneUtils.getDefault().getDefaultSmsAppLabel()); - boolean isSmsEnabledBeforeState; - boolean isSmsEnabledCurrentState; - if (PhoneUtils.getDefault().isDefaultSmsApp()) { - if (getPreferenceScreen().findPreference(mSmsEnabledPrefKey) == null) { - getPreferenceScreen().addPreference(mSmsEnabledPreference); - isSmsEnabledBeforeState = false; - } else { - isSmsEnabledBeforeState = true; - } - isSmsEnabledCurrentState = true; - getPreferenceScreen().removePreference(mSmsDisabledPreference); - mSmsEnabledPreference.setSummary(defaultSmsAppLabel); - } else { - if (getPreferenceScreen().findPreference(mSmsDisabledPrefKey) == null) { - getPreferenceScreen().addPreference(mSmsDisabledPreference); - isSmsEnabledBeforeState = true; - } else { - isSmsEnabledBeforeState = false; - } - isSmsEnabledCurrentState = false; - getPreferenceScreen().removePreference(mSmsEnabledPreference); - mSmsDisabledPreference.setSummary(defaultSmsAppLabel); - } - updateNotificationsPreferences(); - } - mIsSmsPreferenceClicked = false; - } - - private void updateNotificationsPreferences() { - final boolean canNotify = !OsUtil.isAtLeastKLP() - || PhoneUtils.getDefault().isDefaultSmsApp(); - mNotificationsEnabledPreference.setEnabled(canNotify); - } - - @Override - public void onStart() { - super.onStart(); - // We do this on start rather than on resume because the sound picker is in a - // separate activity. - getPreferenceScreen().getSharedPreferences() - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onResume() { - super.onResume(); - updateSmsEnabledPreferences(); - updateNotificationsPreferences(); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, - final String key) { - if (key.equals(mNotificationsEnabledPreferenceKey)) { - updateNotificationsPreferences(); - } else if (key.equals(mRingtonePreferenceKey)) { - updateSoundSummary(sharedPreferences); - } - } - - @Override - public void onStop() { - super.onStop(); - getPreferenceScreen().getSharedPreferences() - .unregisterOnSharedPreferenceChangeListener(this); - } - } -} diff --git a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java b/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java deleted file mode 100644 index 739d2dc..0000000 --- a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.appsettings; - -import android.app.AlertDialog; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.RadioButton; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BuglePrefs; - -/** - * Displays an on/off switch for group MMS setting for a given subscription. - */ -public class GroupMmsSettingDialog { - private final Context mContext; - private final int mSubId; - private AlertDialog mDialog; - - /** - * Shows a new group MMS setting dialog. - */ - public static void showDialog(final Context context, final int subId) { - new GroupMmsSettingDialog(context, subId).show(); - } - - private GroupMmsSettingDialog(final Context context, final int subId) { - mContext = context; - mSubId = subId; - } - - private void show() { - Assert.isNull(mDialog); - mDialog = new AlertDialog.Builder(mContext) - .setView(createView()) - .setTitle(R.string.group_mms_pref_title) - .setNegativeButton(android.R.string.cancel, null) - .show(); - } - - private void changeGroupMmsSettings(final boolean enable) { - Assert.notNull(mDialog); - BuglePrefs.getSubscriptionPrefs(mSubId).putBoolean( - mContext.getString(R.string.group_mms_pref_key), enable); - mDialog.dismiss(); - } - - private View createView() { - final LayoutInflater inflater = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final View rootView = inflater.inflate(R.layout.group_mms_setting_dialog, null, false); - final RadioButton disableButton = (RadioButton) - rootView.findViewById(R.id.disable_group_mms_button); - final RadioButton enableButton = (RadioButton) - rootView.findViewById(R.id.enable_group_mms_button); - disableButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - changeGroupMmsSettings(false); - } - }); - enableButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - changeGroupMmsSettings(true); - } - }); - final boolean mmsEnabled = BuglePrefs.getSubscriptionPrefs(mSubId).getBoolean( - mContext.getString(R.string.group_mms_pref_key), - mContext.getResources().getBoolean(R.bool.group_mms_pref_default)); - enableButton.setChecked(mmsEnabled); - disableButton.setChecked(!mmsEnabled); - return rootView; - } -} diff --git a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java b/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java deleted file mode 100644 index e02823f..0000000 --- a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.app.FragmentTransaction; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.support.v4.app.NavUtils; -import android.text.TextUtils; -import android.view.MenuItem; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.ParticipantRefresh; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.ApnDatabase; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.PhoneUtils; - -public class PerSubscriptionSettingsActivity extends BugleActionBarActivity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final String title = getIntent().getStringExtra( - UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE); - if (!TextUtils.isEmpty(title)) { - getSupportActionBar().setTitle(title); - } else { - // This will fall back to the default title, i.e. "Messaging settings," so No-op. - } - - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - final PerSubscriptionSettingsFragment fragment = new PerSubscriptionSettingsFragment(); - ft.replace(android.R.id.content, fragment); - ft.commit(); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - public static class PerSubscriptionSettingsFragment extends PreferenceFragment - implements OnSharedPreferenceChangeListener { - private PhoneNumberPreference mPhoneNumberPreference; - private Preference mGroupMmsPreference; - private String mGroupMmsPrefKey; - private String mPhoneNumberKey; - private int mSubId; - - public PerSubscriptionSettingsFragment() { - // Required empty constructor - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Get sub id from launch intent - final Intent intent = getActivity().getIntent(); - Assert.notNull(intent); - mSubId = (intent != null) ? intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID, - ParticipantData.DEFAULT_SELF_SUB_ID) : ParticipantData.DEFAULT_SELF_SUB_ID; - - final BuglePrefs subPrefs = Factory.get().getSubscriptionPrefs(mSubId); - getPreferenceManager().setSharedPreferencesName(subPrefs.getSharedPreferencesName()); - addPreferencesFromResource(R.xml.preferences_per_subscription); - - mPhoneNumberKey = getString(R.string.mms_phone_number_pref_key); - mPhoneNumberPreference = (PhoneNumberPreference) findPreference(mPhoneNumberKey); - final PreferenceCategory advancedCategory = (PreferenceCategory) - findPreference(getString(R.string.advanced_category_pref_key)); - final PreferenceCategory mmsCategory = (PreferenceCategory) - findPreference(getString(R.string.mms_messaging_category_pref_key)); - - mPhoneNumberPreference.setDefaultPhoneNumber( - PhoneUtils.get(mSubId).getCanonicalForSelf(false/*allowOverride*/), mSubId); - - mGroupMmsPrefKey = getString(R.string.group_mms_pref_key); - mGroupMmsPreference = findPreference(mGroupMmsPrefKey); - if (!MmsConfig.get(mSubId).getGroupMmsEnabled()) { - // Always show group messaging setting even if the SIM has no number - // If broadcast sms is selected, the SIM number is not needed - // If group mms is selected, the phone number dialog will popup when message - // is being sent, making sure we will have a self number for group mms. - mmsCategory.removePreference(mGroupMmsPreference); - } else { - mGroupMmsPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference pref) { - GroupMmsSettingDialog.showDialog(getActivity(), mSubId); - return true; - } - }); - updateGroupMmsPrefSummary(); - } - - if (!MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled()) { - final Preference deliveryReportsPref = findPreference( - getString(R.string.delivery_reports_pref_key)); - mmsCategory.removePreference(deliveryReportsPref); - } - final Preference wirelessAlertPref = findPreference(getString( - R.string.wireless_alerts_key)); - if (!isCellBroadcastAppLinkEnabled()) { - advancedCategory.removePreference(wirelessAlertPref); - } else { - wirelessAlertPref.setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(final Preference preference) { - try { - startActivity(UIIntents.get().getWirelessAlertsIntent()); - } catch (final ActivityNotFoundException e) { - // Handle so we shouldn't crash if the wireless alerts - // implementation is broken. - LogUtil.e(LogUtil.BUGLE_TAG, - "Failed to launch wireless alerts activity", e); - } - return true; - } - }); - } - - // Access Point Names (APNs) - final Preference apnsPref = findPreference(getString(R.string.sms_apns_key)); - - if (MmsUtils.useSystemApnTable() && !ApnDatabase.doesDatabaseExist()) { - // Don't remove the ability to edit the local APN prefs if this device lets us - // access the system APN, but we can't find the MCC/MNC in the APN table and we - // created the local APN table in case the MCC/MNC was in there. In other words, - // if the local APN table exists, let the user edit it. - advancedCategory.removePreference(apnsPref); - } else { - final PreferenceScreen apnsScreen = (PreferenceScreen) findPreference( - getString(R.string.sms_apns_key)); - apnsScreen.setIntent(UIIntents.get() - .getApnSettingsIntent(getPreferenceScreen().getContext(), mSubId)); - } - - // We want to disable preferences if we are not the default app, but we do all of the - // above first so that the user sees the correct information on the screen - if (!PhoneUtils.getDefault().isDefaultSmsApp()) { - mGroupMmsPreference.setEnabled(false); - final Preference autoRetrieveMmsPreference = - findPreference(getString(R.string.auto_retrieve_mms_pref_key)); - autoRetrieveMmsPreference.setEnabled(false); - final Preference deliveryReportsPreference = - findPreference(getString(R.string.delivery_reports_pref_key)); - deliveryReportsPreference.setEnabled(false); - } - } - - private boolean isCellBroadcastAppLinkEnabled() { - if (!MmsConfig.get(mSubId).getShowCellBroadcast()) { - return false; - } - try { - final PackageManager pm = getActivity().getPackageManager(); - return pm.getApplicationEnabledSetting(UIIntents.CMAS_COMPONENT) - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - } catch (final IllegalArgumentException ignored) { - // CMAS app not installed. - } - return false; - } - - private void updateGroupMmsPrefSummary() { - final boolean groupMmsEnabled = getPreferenceScreen().getSharedPreferences().getBoolean( - mGroupMmsPrefKey, getResources().getBoolean(R.bool.group_mms_pref_default)); - mGroupMmsPreference.setSummary(groupMmsEnabled ? - R.string.enable_group_mms : R.string.disable_group_mms); - } - - @Override - public void onResume() { - super.onResume(); - getPreferenceScreen().getSharedPreferences() - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, - final String key) { - if (key.equals(mGroupMmsPrefKey)) { - updateGroupMmsPrefSummary(); - } else if (key.equals(mPhoneNumberKey)) { - // Save the changed phone number in preferences specific to the sub id - final String newPhoneNumber = mPhoneNumberPreference.getText(); - final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId); - if (TextUtils.isEmpty(newPhoneNumber)) { - subPrefs.remove(mPhoneNumberKey); - } else { - subPrefs.putString(getString(R.string.mms_phone_number_pref_key), - newPhoneNumber); - } - // Update the self participants so the new phone number will be reflected - // everywhere in the UI. - ParticipantRefresh.refreshSelfParticipants(); - } - } - - @Override - public void onPause() { - super.onPause(); - getPreferenceScreen().getSharedPreferences() - .unregisterOnSharedPreferenceChangeListener(this); - } - } -} diff --git a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java b/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java deleted file mode 100644 index 0c9c018..0000000 --- a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.content.Context; -import android.preference.EditTextPreference; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.text.InputType; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.util.PhoneUtils; - -/** - * Preference that displays a phone number and allows editing via a dialog. - *

- * A default number can be assigned, which is shown in the preference view and - * used to populate the dialog editor when the preference value is not set. If - * the user sets the preference to a number equivalent to the default, the - * underlying preference is cleared. - */ -public class PhoneNumberPreference extends EditTextPreference { - - private String mDefaultPhoneNumber; - private int mSubId; - - public PhoneNumberPreference(final Context context, final AttributeSet attrs) { - super(context, attrs); - mDefaultPhoneNumber = ""; - } - - public void setDefaultPhoneNumber(final String phoneNumber, final int subscriptionId) { - mDefaultPhoneNumber = phoneNumber; - mSubId = subscriptionId; - } - - @Override - protected void onBindView(final View view) { - // Show the preference value if it's set, or the default number if not. - // If we don't have a default, fall back to a static string (e.g. Unknown). - String value = getText(); - if (TextUtils.isEmpty(value)) { - value = mDefaultPhoneNumber; - } - final String displayValue = (!TextUtils.isEmpty(value)) - ? PhoneUtils.get(mSubId).formatForDisplay(value) - : getContext().getString(R.string.unknown_phone_number_pref_display_value); - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - final String phoneNumber = bidiFormatter.unicodeWrap - (displayValue, TextDirectionHeuristicsCompat.LTR); - // Set the value as the summary and let the superclass populate the views - setSummary(phoneNumber); - super.onBindView(view); - } - - @Override - protected void onBindDialogView(final View view) { - super.onBindDialogView(view); - - final String value = getText(); - - // If the preference is empty, populate the EditText with the default number instead. - if (TextUtils.isEmpty(value) && !TextUtils.isEmpty(mDefaultPhoneNumber)) { - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - final String phoneNumber = bidiFormatter.unicodeWrap - (PhoneUtils.get(mSubId).getCanonicalBySystemLocale(mDefaultPhoneNumber), - TextDirectionHeuristicsCompat.LTR); - getEditText().setText(phoneNumber); - } - getEditText().setInputType(InputType.TYPE_CLASS_PHONE); - } - - @Override - protected void onDialogClosed(final boolean positiveResult) { - if (positiveResult && mDefaultPhoneNumber != null) { - final String value = getEditText().getText().toString(); - final PhoneUtils phoneUtils = PhoneUtils.get(mSubId); - final String phoneNumber = phoneUtils.getCanonicalBySystemLocale(value); - final String defaultPhoneNumber = phoneUtils.getCanonicalBySystemLocale( - mDefaultPhoneNumber); - - // If the new value is the default, clear the preference. - if (phoneNumber.equals(defaultPhoneNumber)) { - setText(""); - return; - } - } - super.onDialogClosed(positiveResult); - } - - @Override - public void setText(final String text) { - super.setText(text); - - // EditTextPreference doesn't show the value on the preference view, but we do. - // We thus need to force a rebind of the view when a new value is set. - notifyChanged(); - } -} diff --git a/src/com/android/messaging/ui/appsettings/SettingsActivity.java b/src/com/android/messaging/ui/appsettings/SettingsActivity.java deleted file mode 100644 index 75266d8..0000000 --- a/src/com/android/messaging/ui/appsettings/SettingsActivity.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.appsettings; - -import android.app.Fragment; -import android.content.Context; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.SettingsData; -import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; -import com.android.messaging.datamodel.data.SettingsData.SettingsItem; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.PhoneUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Shows the "master" settings activity that contains two parts, one for application-wide settings - * (dubbed "General settings"), and one or more for per-subscription settings (dubbed "Messaging - * settings" for single-SIM, and the actual SIM name for multi-SIM). Clicking on either item - * (e.g. "General settings") will open the detail settings activity (ApplicationSettingsActivity - * in this case). - */ -public class SettingsActivity extends BugleActionBarActivity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Directly open the detailed settings page as the top-level settings activity if this is - // not a multi-SIM device. - if (PhoneUtils.getDefault().getActiveSubscriptionCount() <= 1) { - UIIntents.get().launchApplicationSettingsActivity(this, true /* topLevel */); - finish(); - } else { - getFragmentManager().beginTransaction() - .replace(android.R.id.content, new SettingsFragment()) - .commit(); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - public static class SettingsFragment extends Fragment implements SettingsDataListener { - private ListView mListView; - private SettingsListAdapter mAdapter; - private final Binding mBinding = BindingBase.createBinding(this); - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mBinding.bind(DataModel.get().createSettingsData(getActivity(), this)); - mBinding.getData().init(getLoaderManager(), mBinding); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.settings_fragment, container, false); - mListView = (ListView) view.findViewById(android.R.id.list); - mAdapter = new SettingsListAdapter(getActivity()); - mListView.setAdapter(mAdapter); - return view; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mBinding.unbind(); - } - - @Override - public void onSelfParticipantDataLoaded(SettingsData data) { - mBinding.ensureBound(data); - mAdapter.setSettingsItems(data.getSettingsItems()); - } - - /** - * An adapter that displays a list of SettingsItem. - */ - private class SettingsListAdapter extends ArrayAdapter { - public SettingsListAdapter(final Context context) { - super(context, R.layout.settings_item_view, new ArrayList()); - } - - public void setSettingsItems(final List newList) { - clear(); - addAll(newList); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, final View convertView, - final ViewGroup parent) { - View itemView; - if (convertView != null) { - itemView = convertView; - } else { - final LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - itemView = inflater.inflate( - R.layout.settings_item_view, parent, false); - } - final SettingsItem item = getItem(position); - final TextView titleTextView = (TextView) itemView.findViewById(R.id.title); - final TextView subtitleTextView = (TextView) itemView.findViewById(R.id.subtitle); - final String summaryText = item.getDisplayDetail(); - titleTextView.setText(item.getDisplayName()); - if (!TextUtils.isEmpty(summaryText)) { - subtitleTextView.setText(summaryText); - subtitleTextView.setVisibility(View.VISIBLE); - } else { - subtitleTextView.setVisibility(View.GONE); - } - itemView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - switch (item.getType()) { - case SettingsItem.TYPE_GENERAL_SETTINGS: - UIIntents.get().launchApplicationSettingsActivity(getActivity(), - false /* topLevel */); - break; - - case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS: - UIIntents.get().launchPerSubscriptionSettingsActivity(getActivity(), - item.getSubId(), item.getActivityTitle()); - break; - - default: - Assert.fail("unrecognized setting type!"); - break; - } - } - }); - return itemView; - } - } - } -} diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java deleted file mode 100644 index a540597..0000000 --- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.attachmentchooser; - -import android.app.Fragment; -import android.os.Bundle; - -import com.android.messaging.R; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost; -import com.android.messaging.util.Assert; - -public class AttachmentChooserActivity extends BugleActionBarActivity implements - AttachmentChooserFragmentHost { - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.attachment_chooser_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - if (fragment instanceof AttachmentChooserFragment) { - final String conversationId = - getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - Assert.notNull(conversationId); - final AttachmentChooserFragment chooserFragment = - (AttachmentChooserFragment) fragment; - chooserFragment.setConversationId(conversationId); - chooserFragment.setHost(this); - } - } - - @Override - public void onConfirmSelection() { - setResult(RESULT_OK); - finish(); - } -} diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java deleted file mode 100644 index b39dc3e..0000000 --- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.attachmentchooser; - -import android.app.Fragment; -import android.content.Context; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.attachmentchooser.AttachmentGridView.AttachmentGridHost; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.List; - -public class AttachmentChooserFragment extends Fragment implements DraftMessageDataListener, - AttachmentGridHost { - public interface AttachmentChooserFragmentHost { - void onConfirmSelection(); - } - - private AttachmentGridView mAttachmentGridView; - private AttachmentGridAdapter mAdapter; - private AttachmentChooserFragmentHost mHost; - - @VisibleForTesting - Binding mBinding = BindingBase.createBinding(this); - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.attachment_chooser_fragment, container, false); - mAttachmentGridView = (AttachmentGridView) view.findViewById(R.id.grid); - mAdapter = new AttachmentGridAdapter(getActivity()); - mAttachmentGridView.setAdapter(mAdapter); - mAttachmentGridView.setHost(this); - setHasOptionsMenu(true); - return view; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mBinding.unbind(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.attachment_chooser_menu, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_confirm_selection: - confirmSelection(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - @VisibleForTesting - void confirmSelection() { - if (mBinding.isBound()) { - mBinding.getData().removeExistingAttachments( - mAttachmentGridView.getUnselectedAttachments()); - mBinding.getData().saveToStorage(mBinding); - mHost.onConfirmSelection(); - } - } - - public void setConversationId(final String conversationId) { - mBinding.bind(DataModel.get().createDraftMessageData(conversationId)); - mBinding.getData().addListener(this); - mBinding.getData().loadFromStorage(mBinding, null, false); - } - - public void setHost(final AttachmentChooserFragmentHost host) { - mHost = host; - } - - @Override - public void onDraftChanged(final DraftMessageData data, final int changeFlags) { - mBinding.ensureBound(data); - if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) == - DraftMessageData.ATTACHMENTS_CHANGED) { - mAdapter.onAttachmentsLoaded(data.getReadOnlyAttachments()); - } - } - - @Override - public void onDraftAttachmentLimitReached(final DraftMessageData data) { - // Do nothing since the user is in the process of unselecting attachments. - } - - @Override - public void onDraftAttachmentLoadFailed() { - // Do nothing since the user is in the process of unselecting attachments. - } - - @Override - public void displayPhoto(final Rect viewRect, final Uri photoUri) { - final Uri imagesUri = MessagingContentProvider.buildDraftImagesUri( - mBinding.getData().getConversationId()); - UIIntents.get().launchFullScreenPhotoViewer( - getActivity(), photoUri, viewRect, imagesUri); - } - - @Override - public void updateSelectionCount(int count) { - if (getActivity() instanceof BugleActionBarActivity) { - final ActionBar actionBar = ((BugleActionBarActivity) getActivity()) - .getSupportActionBar(); - if (actionBar != null) { - actionBar.setTitle(getResources().getString( - R.string.attachment_chooser_selection, count)); - } - } - } - - class AttachmentGridAdapter extends ArrayAdapter { - public AttachmentGridAdapter(final Context context) { - super(context, R.layout.attachment_grid_item_view, new ArrayList()); - } - - public void onAttachmentsLoaded(final List attachments) { - clear(); - addAll(attachments); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - AttachmentGridItemView itemView; - final MessagePartData item = getItem(position); - if (convertView != null && convertView instanceof AttachmentGridItemView) { - itemView = (AttachmentGridItemView) convertView; - } else { - final LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - itemView = (AttachmentGridItemView) inflater.inflate( - R.layout.attachment_grid_item_view, parent, false); - } - itemView.bind(item, mAttachmentGridView); - return itemView; - } - } -} diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java deleted file mode 100644 index 8bb7356..0000000 --- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.attachmentchooser; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.TouchDelegate; -import android.view.View; -import android.widget.CheckBox; -import android.widget.FrameLayout; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.AttachmentPreviewFactory; -import com.android.messaging.util.Assert; -import com.google.common.annotations.VisibleForTesting; - -/** - * Shows an item in the attachment picker grid. - */ -public class AttachmentGridItemView extends FrameLayout { - public interface HostInterface { - boolean isItemSelected(MessagePartData attachment); - void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment); - void onItemClicked(AttachmentGridItemView view, MessagePartData attachment); - } - - @VisibleForTesting - MessagePartData mAttachmentData; - private FrameLayout mAttachmentViewContainer; - private CheckBox mCheckBox; - private HostInterface mHostInterface; - - public AttachmentGridItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mAttachmentViewContainer = (FrameLayout) findViewById(R.id.attachment_container); - mCheckBox = (CheckBox) findViewById(R.id.checkbox); - mCheckBox.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mHostInterface.onItemCheckedChanged(AttachmentGridItemView.this, mAttachmentData); - } - }); - setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mHostInterface.onItemClicked(AttachmentGridItemView.this, mAttachmentData); - } - }); - addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - // Enlarge the clickable region for the checkbox. - final int touchAreaIncrease = getResources().getDimensionPixelOffset( - R.dimen.attachment_grid_checkbox_area_increase); - final Rect region = new Rect(); - mCheckBox.getHitRect(region); - region.inset(-touchAreaIncrease, -touchAreaIncrease); - setTouchDelegate(new TouchDelegate(region, mCheckBox)); - } - }); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - // The grid view auto-fits the columns, so we want to let the height match the width - // to make the attachment preview square. - super.onMeasure(widthMeasureSpec, widthMeasureSpec); - } - - public void bind(final MessagePartData attachment, final HostInterface hostInterface) { - Assert.isTrue(attachment.isAttachment()); - mHostInterface = hostInterface; - updateSelectedState(); - if (mAttachmentData == null || !mAttachmentData.equals(attachment)) { - mAttachmentData = attachment; - updateAttachmentView(); - } - } - - @VisibleForTesting - HostInterface testGetHostInterface() { - return mHostInterface; - } - - public void updateSelectedState() { - mCheckBox.setChecked(mHostInterface.isItemSelected(mAttachmentData)); - } - - private void updateAttachmentView() { - mAttachmentViewContainer.removeAllViews(); - final LayoutInflater inflater = LayoutInflater.from(getContext()); - final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(inflater, - mAttachmentData, mAttachmentViewContainer, - AttachmentPreviewFactory.TYPE_CHOOSER_GRID, true /* startImageRequest */, null); - mAttachmentViewContainer.addView(attachmentView); - } -} diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java deleted file mode 100644 index abf61dc..0000000 --- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.attachmentchooser; - -import android.content.Context; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.widget.GridView; - -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentGridAdapter; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Displays a grid of attachment previews for the user to choose which to select/unselect - */ -public class AttachmentGridView extends GridView implements - AttachmentGridItemView.HostInterface { - public interface AttachmentGridHost { - void displayPhoto(final Rect viewRect, final Uri photoUri); - void updateSelectionCount(final int count); - } - - // By default everything is selected so only need to keep track of the unselected set. - private final Set mUnselectedSet; - private AttachmentGridHost mHost; - - public AttachmentGridView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mUnselectedSet = new HashSet<>(); - } - - public void setHost(final AttachmentGridHost host) { - mHost = host; - } - - @Override - public boolean isItemSelected(final MessagePartData attachment) { - return !mUnselectedSet.contains(attachment); - } - - @Override - public void onItemClicked(final AttachmentGridItemView view, final MessagePartData attachment) { - // If the item is an image, show the photo viewer. All the other types (video, audio, - // vcard) have internal click handling for showing previews so we don't need to handle them - if (attachment.isImage()) { - mHost.displayPhoto(UiUtils.getMeasuredBoundsOnScreen(view), attachment.getContentUri()); - } - } - - @Override - public void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment) { - // Toggle selection. - if (isItemSelected(attachment)) { - mUnselectedSet.add(attachment); - } else { - mUnselectedSet.remove(attachment); - } - view.updateSelectedState(); - updateSelectionCount(); - } - - public Set getUnselectedAttachments() { - return Collections.unmodifiableSet(mUnselectedSet); - } - - private void updateSelectionCount() { - final int count = getAdapter().getCount() - mUnselectedSet.size(); - Assert.isTrue(count >= 0); - mHost.updateSelectionCount(count); - } - - private void refreshViews() { - if (getAdapter() instanceof AttachmentGridAdapter) { - ((AttachmentGridAdapter) getAdapter()).notifyDataSetChanged(); - } - } - - @Override - public Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final SavedState savedState = new SavedState(superState); - savedState.unselectedParts = mUnselectedSet - .toArray(new MessagePartData[mUnselectedSet.size()]); - return savedState; - } - - @Override - public void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - mUnselectedSet.clear(); - for (int i = 0; i < savedState.unselectedParts.length; i++) { - final MessagePartData unselectedPart = savedState.unselectedParts[i]; - mUnselectedSet.add(unselectedPart); - } - refreshViews(); - } - - /** - * Persists the item selection state to saved instance state so we can restore on activity - * recreation - */ - public static class SavedState extends BaseSavedState { - MessagePartData[] unselectedParts; - - SavedState(final Parcelable superState) { - super(superState); - } - - private SavedState(final Parcel in) { - super(in); - - // Read parts - final int partCount = in.readInt(); - unselectedParts = new MessagePartData[partCount]; - for (int i = 0; i < partCount; i++) { - unselectedParts[i] = ((MessagePartData) in.readParcelable( - MessagePartData.class.getClassLoader())); - } - } - - @Override - public void writeToParcel(final Parcel out, final int flags) { - super.writeToParcel(out, flags); - - // Write parts - out.writeInt(unselectedParts.length); - for (final MessagePartData image : unselectedParts) { - out.writeParcelable(image, flags); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(final Parcel in) { - return new SavedState(in); - } - @Override - public SavedState[] newArray(final int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java b/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java deleted file mode 100644 index 9c1393d..0000000 --- a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.net.Uri; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.AccessibilityUtil; - -public class AddContactsConfirmationDialog implements DialogInterface.OnClickListener { - private final Context mContext; - private final Uri mAvatarUri; - private final String mNormalizedDestination; - - public AddContactsConfirmationDialog(final Context context, final Uri avatarUri, - final String normalizedDestination) { - mContext = context; - mAvatarUri = avatarUri; - mNormalizedDestination = normalizedDestination; - } - - public void show() { - final int confirmAddContactStringId = R.string.add_contact_confirmation; - final int cancelStringId = android.R.string.cancel; - final AlertDialog alertDialog = new AlertDialog.Builder(mContext) - .setTitle(R.string.add_contact_confirmation_dialog_title) - .setView(createBodyView()) - .setPositiveButton(confirmAddContactStringId, this) - .setNegativeButton(cancelStringId, null) - .create(); - alertDialog.show(); - final Resources resources = mContext.getResources(); - final Button cancelButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE); - if (cancelButton != null) { - cancelButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color)); - } - final Button addButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); - if (addButton != null) { - addButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color)); - } - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - UIIntents.get().launchAddContactActivity(mContext, mNormalizedDestination); - } - - private View createBodyView() { - final View view = LayoutInflater.from(mContext).inflate( - R.layout.add_contacts_confirmation_dialog_body, null); - final ContactIconView iconView = (ContactIconView) view.findViewById(R.id.contact_icon); - iconView.setImageResourceUri(mAvatarUri); - final TextView textView = (TextView) view.findViewById(R.id.participant_name); - textView.setText(mNormalizedDestination); - // Accessibility reason : in case phone numbers are mixed in the display name, - // we need to vocalize it for talkback. - final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber( - mContext.getResources(), mNormalizedDestination); - textView.setContentDescription(vocalizedDisplayName); - return view; - } -} diff --git a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java b/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java deleted file mode 100644 index 7263c54..0000000 --- a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; - -import com.android.messaging.R; -import com.android.messaging.ui.CustomHeaderPagerListViewHolder; -import com.android.messaging.ui.contact.ContactListItemView.HostInterface; - -/** - * Holds the all contacts view for the contact picker's view pager. - */ -public class AllContactsListViewHolder extends CustomHeaderPagerListViewHolder { - public AllContactsListViewHolder(final Context context, final HostInterface clivHostInterface) { - super(context, new ContactListAdapter(context, null, clivHostInterface, - true /* needAlphabetHeader */)); - } - - @Override - protected int getLayoutResId() { - return R.layout.all_contacts_list_view; - } - - @Override - protected int getPageTitleResId() { - return R.string.contact_picker_all_contacts_tab_title; - } - - @Override - protected int getEmptyViewResId() { - return R.id.empty_view; - } - - @Override - protected int getListViewResId() { - return R.id.all_contacts_list; - } - - @Override - protected int getEmptyViewTitleResId() { - return R.string.contact_list_empty_text; - } - - @Override - protected int getEmptyViewImageResId() { - return R.drawable.ic_oobe_freq_list; - } -} diff --git a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java deleted file mode 100644 index 7df62de..0000000 --- a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.graphics.drawable.StateListDrawable; -import android.net.Uri; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import com.android.ex.chips.DropdownChipLayouter; -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ContactRecipientEntryUtils; - -/** - * An implementation for {@link DropdownChipLayouter}. Layouts the dropdown - * list in the ContactRecipientAutoCompleteView in Material style. - */ -public class ContactDropdownLayouter extends DropdownChipLayouter { - private final ContactListItemView.HostInterface mClivHostInterface; - - public ContactDropdownLayouter(final LayoutInflater inflater, final Context context, - final ContactListItemView.HostInterface clivHostInterface) { - super(inflater, context); - mClivHostInterface = new ContactListItemView.HostInterface() { - - @Override - public void onContactListItemClicked(final ContactListItemData item, - final ContactListItemView view) { - // The chips UI will handle auto-complete item click events, so No-op here. - } - - @Override - public boolean isContactSelected(final ContactListItemData item) { - // In chips drop down we don't show any selected checkmark per design. - return false; - } - }; - } - - /** - * Bind a drop down view to a RecipientEntry. We'd like regular dropdown items (BASE_RECIPIENT) - * to behave the same as regular ContactListItemViews, while using the chips library's - * item styling for alternates dropdown items (happens when you click on a chip). - */ - @Override - public View bindView(final View convertView, final ViewGroup parent, final RecipientEntry entry, - final int position, AdapterType type, final String substring, - final StateListDrawable deleteDrawable) { - if (type != AdapterType.BASE_RECIPIENT) { - if (type == AdapterType.SINGLE_RECIPIENT) { - // Treat single recipients the same way as alternates. The base implementation of - // single recipients would try to simplify the destination by tokenizing. We'd - // like to always show the full destination address per design request. - type = AdapterType.RECIPIENT_ALTERNATES; - } - return super.bindView(convertView, parent, entry, position, type, substring, - deleteDrawable); - } - - // Default to show all the information - // RTL : To format contact name and detail if they happen to be phone numbers. - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - final String displayName = bidiFormatter.unicodeWrap( - ContactRecipientEntryUtils.getDisplayNameForContactList(entry), - TextDirectionHeuristicsCompat.LTR); - final String destination = bidiFormatter.unicodeWrap( - ContactRecipientEntryUtils.formatDestination(entry), - TextDirectionHeuristicsCompat.LTR); - final View itemView = reuseOrInflateView(convertView, parent, type); - - // Bold the string that is matched. - final CharSequence[] styledResults = - getStyledResults(substring, displayName, destination); - - Assert.isTrue(itemView instanceof ContactListItemView); - final ContactListItemView contactListItemView = (ContactListItemView) itemView; - contactListItemView.setImageClickHandlerDisabled(true); - contactListItemView.bind(entry, styledResults[0], styledResults[1], - mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT)); - return itemView; - } - - @Override - protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view, - AdapterType type) { - if (showImage && view instanceof ContactIconView) { - final ContactIconView contactView = (ContactIconView) view; - // These show contact cards by default, but that isn't what we want here - contactView.setImageClickHandlerDisabled(true); - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - ParticipantData.getFromRecipientEntry(entry)); - contactView.setImageResourceUri(avatarUri); - } else { - super.bindIconToView(showImage, entry, view, type); - } - } - - @Override - protected int getItemLayoutResId(AdapterType type) { - switch (type) { - case BASE_RECIPIENT: - return R.layout.contact_list_item_view; - case RECIPIENT_ALTERNATES: - return R.layout.chips_alternates_dropdown_item; - default: - return R.layout.chips_alternates_dropdown_item; - } - } - - @Override - protected int getAlternateItemLayoutResId(AdapterType type) { - return getItemLayoutResId(type); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactListAdapter.java b/src/com/android/messaging/ui/contact/ContactListAdapter.java deleted file mode 100644 index d466b61..0000000 --- a/src/com/android/messaging/ui/contact/ContactListAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.SectionIndexer; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; - -public class ContactListAdapter extends CursorAdapter implements SectionIndexer { - private final ContactListItemView.HostInterface mClivHostInterface; - private final boolean mNeedAlphabetHeader; - private ContactSectionIndexer mSectionIndexer; - - public ContactListAdapter(final Context context, final Cursor cursor, - final ContactListItemView.HostInterface clivHostInterface, - final boolean needAlphabetHeader) { - super(context, cursor, 0); - mClivHostInterface = clivHostInterface; - mNeedAlphabetHeader = needAlphabetHeader; - mSectionIndexer = new ContactSectionIndexer(cursor); - } - - @Override - public void bindView(final View view, final Context context, final Cursor cursor) { - Assert.isTrue(view instanceof ContactListItemView); - final ContactListItemView contactListItemView = (ContactListItemView) view; - String alphabetHeader = null; - if (mNeedAlphabetHeader) { - final int position = cursor.getPosition(); - final int section = mSectionIndexer.getSectionForPosition(position); - // Check if the position is the first in the section. - if (mSectionIndexer.getPositionForSection(section) == position) { - alphabetHeader = (String) mSectionIndexer.getSections()[section]; - } - } - contactListItemView.bind(cursor, mClivHostInterface, mNeedAlphabetHeader, alphabetHeader); - } - - @Override - public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - return layoutInflater.inflate(R.layout.contact_list_item_view, parent, false); - } - - @Override - public Cursor swapCursor(final Cursor newCursor) { - mSectionIndexer = new ContactSectionIndexer(newCursor); - return super.swapCursor(newCursor); - } - - @Override - public Object[] getSections() { - return mSectionIndexer.getSections(); - } - - @Override - public int getPositionForSection(final int sectionIndex) { - return mSectionIndexer.getPositionForSection(sectionIndex); - } - - @Override - public int getSectionForPosition(final int position) { - return mSectionIndexer.getSectionForPosition(position); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactListItemView.java b/src/com/android/messaging/ui/contact/ContactListItemView.java deleted file mode 100644 index 6904da6..0000000 --- a/src/com/android/messaging/ui/contact/ContactListItemView.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.google.common.annotations.VisibleForTesting; - -/** - * The view for a single entry in a contact list. - */ -public class ContactListItemView extends LinearLayout implements OnClickListener { - public interface HostInterface { - void onContactListItemClicked(ContactListItemData item, ContactListItemView view); - boolean isContactSelected(ContactListItemData item); - } - - @VisibleForTesting - final ContactListItemData mData; - private TextView mContactNameTextView; - private TextView mContactDetailsTextView; - private TextView mContactDetailTypeTextView; - private TextView mAlphabetHeaderTextView; - private ContactIconView mContactIconView; - private ImageView mContactCheckmarkView; - private HostInterface mHostInterface; - private boolean mShouldShowAlphabetHeader; - - public ContactListItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = DataModel.get().createContactListItemData(); - } - - @Override - protected void onFinishInflate () { - mContactNameTextView = (TextView) findViewById(R.id.contact_name); - mContactDetailsTextView = (TextView) findViewById(R.id.contact_details); - mContactDetailTypeTextView = (TextView) findViewById(R.id.contact_detail_type); - mAlphabetHeaderTextView = (TextView) findViewById(R.id.alphabet_header); - mContactIconView = (ContactIconView) findViewById(R.id.contact_icon); - mContactCheckmarkView = (ImageView) findViewById(R.id.contact_checkmark); - } - - /** - * Fills in the data associated with this view by binding to a contact cursor provided by - * ContactUtil. - * @param cursor the contact cursor. - * @param hostInterface host interface to this view. - * @param shouldShowAlphabetHeader whether an alphabetical header should shown on the side - * of this view. If {@code headerLabel} is empty, we will still leave space for it. - * @param headerLabel the alphabetical header on the side of this view, if it should be shown. - */ - public void bind(final Cursor cursor, final HostInterface hostInterface, - final boolean shouldShowAlphabetHeader, final String headerLabel) { - mData.bind(cursor, headerLabel); - mHostInterface = hostInterface; - mShouldShowAlphabetHeader = shouldShowAlphabetHeader; - setOnClickListener(this); - updateViewAppearance(); - } - - /** - * Binds a RecipientEntry. This is used by the chips text view's dropdown layout. - * @param recipientEntry the source RecipientEntry provided by ContactDropdownLayouter, which - * was in turn directly from one of the existing chips, or from filtered results - * generated by ContactRecipientAdapter. - * @param styledName display name where the portion that matches the search text is bold. - * @param styledDestination number where the portion that matches the search text is bold. - * @param hostInterface host interface to this view. - * @param isSingleRecipient whether this item is shown as the only line item in the single - * recipient drop down from the chips view. If this is the case, we always show the - * contact avatar even if it's not a first-level entry. - */ - public void bind(final RecipientEntry recipientEntry, final CharSequence styledName, - final CharSequence styledDestination, final HostInterface hostInterface, - final boolean isSingleRecipient) { - mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient); - mHostInterface = hostInterface; - mShouldShowAlphabetHeader = false; - updateViewAppearance(); - } - - private void updateViewAppearance() { - mContactNameTextView.setText(mData.getDisplayName()); - mContactDetailsTextView.setText(mData.getDestination()); - mContactDetailTypeTextView.setText(Phone.getTypeLabel(getResources(), - mData.getDestinationType(), mData.getDestinationLabel())); - final RecipientEntry recipientEntry = mData.getRecipientEntry(); - final String destinationString = String.valueOf(mData.getDestination()); - if (mData.getIsSimpleContactItem()) { - // This is a special number-with-avatar type of contact (for unknown contact chips - // and for direct "send to destination" item). In this case, make sure we only show - // the display name (phone number) and the avatar and hide everything else. - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - ParticipantData.getFromRecipientEntry(recipientEntry)); - mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(), - mData.getLookupKey(), destinationString); - mContactIconView.setVisibility(VISIBLE); - mContactCheckmarkView.setVisibility(GONE); - mContactDetailTypeTextView.setVisibility(GONE); - mContactDetailsTextView.setVisibility(GONE); - mContactNameTextView.setVisibility(VISIBLE); - } else if (mData.getIsFirstLevel()) { - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - ParticipantData.getFromRecipientEntry(recipientEntry)); - mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(), - mData.getLookupKey(), destinationString); - mContactIconView.setVisibility(VISIBLE); - mContactNameTextView.setVisibility(VISIBLE); - final boolean isSelected = mHostInterface.isContactSelected(mData); - setSelected(isSelected); - mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE); - mContactDetailsTextView.setVisibility(VISIBLE); - mContactDetailTypeTextView.setVisibility(VISIBLE); - } else { - mContactIconView.setImageResourceUri(null); - mContactIconView.setVisibility(INVISIBLE); - mContactNameTextView.setVisibility(GONE); - final boolean isSelected = mHostInterface.isContactSelected(mData); - setSelected(isSelected); - mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE); - mContactDetailsTextView.setVisibility(VISIBLE); - mContactDetailTypeTextView.setVisibility(VISIBLE); - } - - if (mShouldShowAlphabetHeader) { - mAlphabetHeaderTextView.setVisibility(VISIBLE); - mAlphabetHeaderTextView.setText(mData.getAlphabetHeader()); - } else { - mAlphabetHeaderTextView.setVisibility(GONE); - } - } - - /** - * {@inheritDoc} from OnClickListener - */ - @Override - public void onClick(final View v) { - Assert.isTrue(v == this); - Assert.isTrue(mHostInterface != null); - mHostInterface.onContactListItemClicked(mData, this); - } - - public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) { - mContactIconView.setImageClickHandlerDisabled(isHandlerDisabled); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactPickerFragment.java b/src/com/android/messaging/ui/contact/ContactPickerFragment.java deleted file mode 100644 index d803087..0000000 --- a/src/com/android/messaging/ui/contact/ContactPickerFragment.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.contact; - -import android.app.Activity; -import android.app.Fragment; -import android.database.Cursor; -import android.graphics.Rect; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.Toolbar.OnMenuItemClickListener; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.transition.Explode; -import android.transition.Transition; -import android.transition.Transition.EpicenterCallback; -import android.transition.TransitionManager; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.action.ActionMonitor; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ContactPickerData; -import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.ui.CustomHeaderPagerViewHolder; -import com.android.messaging.ui.CustomHeaderViewPager; -import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation; -import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.RunsOnMainThread; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.Set; - - -/** - * Shows lists of contacts to start conversations with. - */ -public class ContactPickerFragment extends Fragment implements ContactPickerDataListener, - ContactListItemView.HostInterface, ContactChipsChangeListener, OnMenuItemClickListener, - GetOrCreateConversationActionListener { - public static final String FRAGMENT_TAG = "contactpicker"; - - // Undefined contact picker mode. We should never be in this state after the host activity has - // been created. - public static final int MODE_UNDEFINED = 0; - - // The initial contact picker mode for starting a new conversation with one contact. - public static final int MODE_PICK_INITIAL_CONTACT = 1; - - // The contact picker mode where one initial contact has been picked and we are showing - // only the chips edit box. - public static final int MODE_CHIPS_ONLY = 2; - - // The contact picker mode for picking more contacts after starting the initial 1-1. - public static final int MODE_PICK_MORE_CONTACTS = 3; - - // The contact picker mode when max number of participants is reached. - public static final int MODE_PICK_MAX_PARTICIPANTS = 4; - - public interface ContactPickerFragmentHost { - void onGetOrCreateNewConversation(String conversationId); - void onBackButtonPressed(); - void onInitiateAddMoreParticipants(); - void onParticipantCountChanged(boolean canAddMoreParticipants); - void invalidateActionBar(); - } - - @VisibleForTesting - final Binding mBinding = BindingBase.createBinding(this); - - private ContactPickerFragmentHost mHost; - private ContactRecipientAutoCompleteView mRecipientTextView; - private CustomHeaderViewPager mCustomHeaderViewPager; - private AllContactsListViewHolder mAllContactsListViewHolder; - private FrequentContactsListViewHolder mFrequentContactsListViewHolder; - private View mRootView; - private View mPendingExplodeView; - private View mComposeDivider; - private Toolbar mToolbar; - private int mContactPickingMode = MODE_UNDEFINED; - - // Keeps track of the currently selected phone numbers in the chips view to enable fast lookup. - private Set mSelectedPhoneNumbers = null; - - /** - * {@inheritDoc} from Fragment - */ - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mAllContactsListViewHolder = new AllContactsListViewHolder(getActivity(), this); - mFrequentContactsListViewHolder = new FrequentContactsListViewHolder(getActivity(), this); - - if (ContactUtil.hasReadContactsPermission()) { - mBinding.bind(DataModel.get().createContactPickerData(getActivity(), this)); - mBinding.getData().init(getLoaderManager(), mBinding); - } - } - - /** - * {@inheritDoc} from Fragment - */ - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.contact_picker_fragment, container, false); - mRecipientTextView = (ContactRecipientAutoCompleteView) - view.findViewById(R.id.recipient_text_view); - mRecipientTextView.setThreshold(0); - mRecipientTextView.setDropDownAnchor(R.id.compose_contact_divider); - - mRecipientTextView.setContactChipsListener(this); - mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater, - getActivity(), this)); - mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this)); - mRecipientTextView.addTextChangedListener(new TextWatcher() { - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, - final int count) { - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, - final int after) { - } - - @Override - public void afterTextChanged(final Editable s) { - updateTextInputButtonsVisibility(); - } - }); - - final CustomHeaderPagerViewHolder[] viewHolders = { - mFrequentContactsListViewHolder, - mAllContactsListViewHolder }; - - mCustomHeaderViewPager = (CustomHeaderViewPager) view.findViewById(R.id.contact_pager); - mCustomHeaderViewPager.setViewHolders(viewHolders); - mCustomHeaderViewPager.setViewPagerTabHeight(CustomHeaderViewPager.DEFAULT_TAB_STRIP_SIZE); - mCustomHeaderViewPager.setBackgroundColor(getResources() - .getColor(R.color.contact_picker_background)); - - // The view pager defaults to the frequent contacts page. - mCustomHeaderViewPager.setCurrentItem(0); - - mToolbar = (Toolbar) view.findViewById(R.id.toolbar); - mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_light); - mToolbar.setNavigationContentDescription(R.string.back); - mToolbar.setNavigationOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mHost.onBackButtonPressed(); - } - }); - - mToolbar.inflateMenu(R.menu.compose_menu); - mToolbar.setOnMenuItemClickListener(this); - - mComposeDivider = view.findViewById(R.id.compose_contact_divider); - mRootView = view; - return view; - } - - /** - * {@inheritDoc} - * - * Called when the host activity has been created. At this point, the host activity should - * have set the contact picking mode for us so that we may update our visuals. - */ - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Assert.isTrue(mContactPickingMode != MODE_UNDEFINED); - updateVisualsForContactPickingMode(false /* animate */); - mHost.invalidateActionBar(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - // We could not have bound to the data if the permission was denied. - if (mBinding.isBound()) { - mBinding.unbind(); - } - - if (mMonitor != null) { - mMonitor.unregister(); - } - mMonitor = null; - } - - @Override - public boolean onMenuItemClick(final MenuItem menuItem) { - switch (menuItem.getItemId()) { - case R.id.action_ime_dialpad_toggle: - final int baseInputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE; - if ((mRecipientTextView.getInputType() & InputType.TYPE_CLASS_PHONE) != - InputType.TYPE_CLASS_PHONE) { - mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_PHONE); - menuItem.setIcon(R.drawable.ic_ime_light); - } else { - mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_TEXT); - menuItem.setIcon(R.drawable.ic_numeric_dialpad); - } - ImeUtil.get().showImeKeyboard(getActivity(), mRecipientTextView); - return true; - - case R.id.action_add_more_participants: - mHost.onInitiateAddMoreParticipants(); - return true; - - case R.id.action_confirm_participants: - maybeGetOrCreateConversation(); - return true; - - case R.id.action_delete_text: - Assert.equals(MODE_PICK_INITIAL_CONTACT, mContactPickingMode); - mRecipientTextView.setText(""); - return true; - } - return false; - } - - @Override // From ContactPickerDataListener - public void onAllContactsCursorUpdated(final Cursor data) { - mBinding.ensureBound(); - mAllContactsListViewHolder.onContactsCursorUpdated(data); - } - - @Override // From ContactPickerDataListener - public void onFrequentContactsCursorUpdated(final Cursor data) { - mBinding.ensureBound(); - mFrequentContactsListViewHolder.onContactsCursorUpdated(data); - if (data != null && data.getCount() == 0) { - // Show the all contacts list when there's no frequents. - mCustomHeaderViewPager.setCurrentItem(1); - } - } - - @Override // From ContactListItemView.HostInterface - public void onContactListItemClicked(final ContactListItemData item, - final ContactListItemView view) { - if (!isContactSelected(item)) { - if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) { - mPendingExplodeView = view; - } - mRecipientTextView.appendRecipientEntry(item.getRecipientEntry()); - } else if (mContactPickingMode != MODE_PICK_INITIAL_CONTACT) { - mRecipientTextView.removeRecipientEntry(item.getRecipientEntry()); - } - } - - @Override // From ContactListItemView.HostInterface - public boolean isContactSelected(final ContactListItemData item) { - return mSelectedPhoneNumbers != null && - mSelectedPhoneNumbers.contains(PhoneUtils.getDefault().getCanonicalBySystemLocale( - item.getRecipientEntry().getDestination())); - } - - /** - * Call this immediately after attaching the fragment, or when there's a ui state change that - * changes our host (i.e. restore from saved instance state). - */ - public void setHost(final ContactPickerFragmentHost host) { - mHost = host; - } - - public void setContactPickingMode(final int mode, final boolean animate) { - if (mContactPickingMode != mode) { - // Guard against impossible transitions. - Assert.isTrue( - // We may start from undefined mode to any mode when we are restoring state. - (mContactPickingMode == MODE_UNDEFINED) || - (mContactPickingMode == MODE_PICK_INITIAL_CONTACT && mode == MODE_CHIPS_ONLY) || - (mContactPickingMode == MODE_CHIPS_ONLY && mode == MODE_PICK_MORE_CONTACTS) || - (mContactPickingMode == MODE_PICK_MORE_CONTACTS - && mode == MODE_PICK_MAX_PARTICIPANTS) || - (mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS - && mode == MODE_PICK_MORE_CONTACTS)); - - mContactPickingMode = mode; - updateVisualsForContactPickingMode(animate); - } - } - - private void showImeKeyboard() { - Assert.notNull(mRecipientTextView); - mRecipientTextView.requestFocus(); - - // showImeKeyboard() won't work until the layout is ready, so wait until layout is complete - // before showing the soft keyboard. - UiUtils.doOnceAfterLayoutChange(mRootView, new Runnable() { - @Override - public void run() { - final Activity activity = getActivity(); - if (activity != null) { - ImeUtil.get().showImeKeyboard(activity, mRecipientTextView); - } - } - }); - mRecipientTextView.invalidate(); - } - - private void updateVisualsForContactPickingMode(final boolean animate) { - // Don't update visuals if the visuals haven't been inflated yet. - if (mRootView != null) { - final Menu menu = mToolbar.getMenu(); - final MenuItem addMoreParticipantsItem = menu.findItem( - R.id.action_add_more_participants); - final MenuItem confirmParticipantsItem = menu.findItem( - R.id.action_confirm_participants); - switch (mContactPickingMode) { - case MODE_PICK_INITIAL_CONTACT: - addMoreParticipantsItem.setVisible(false); - confirmParticipantsItem.setVisible(false); - mCustomHeaderViewPager.setVisibility(View.VISIBLE); - mComposeDivider.setVisibility(View.INVISIBLE); - mRecipientTextView.setEnabled(true); - showImeKeyboard(); - break; - - case MODE_CHIPS_ONLY: - if (animate) { - if (mPendingExplodeView == null) { - // The user didn't click on any contact item, so use the toolbar as - // the view to "explode." - mPendingExplodeView = mToolbar; - } - startExplodeTransitionForContactLists(false /* show */); - - ViewGroupItemVerticalExplodeAnimation.startAnimationForView( - mCustomHeaderViewPager, mPendingExplodeView, mRootView, - true /* snapshotView */, UiUtils.COMPOSE_TRANSITION_DURATION); - showHideContactPagerWithAnimation(false /* show */); - } else { - mCustomHeaderViewPager.setVisibility(View.GONE); - } - - addMoreParticipantsItem.setVisible(true); - confirmParticipantsItem.setVisible(false); - mComposeDivider.setVisibility(View.VISIBLE); - mRecipientTextView.setEnabled(true); - break; - - case MODE_PICK_MORE_CONTACTS: - if (animate) { - // Correctly set the start visibility state for the view pager and - // individual list items (hidden initially), so that the transition - // manager can properly track the visibility change for the explode. - mCustomHeaderViewPager.setVisibility(View.VISIBLE); - toggleContactListItemsVisibilityForPendingTransition(false /* show */); - startExplodeTransitionForContactLists(true /* show */); - } - addMoreParticipantsItem.setVisible(false); - confirmParticipantsItem.setVisible(true); - mCustomHeaderViewPager.setVisibility(View.VISIBLE); - mComposeDivider.setVisibility(View.INVISIBLE); - mRecipientTextView.setEnabled(true); - showImeKeyboard(); - break; - - case MODE_PICK_MAX_PARTICIPANTS: - addMoreParticipantsItem.setVisible(false); - confirmParticipantsItem.setVisible(true); - mCustomHeaderViewPager.setVisibility(View.VISIBLE); - mComposeDivider.setVisibility(View.INVISIBLE); - // TODO: Verify that this is okay for accessibility - mRecipientTextView.setEnabled(false); - break; - - default: - Assert.fail("Unsupported contact picker mode!"); - break; - } - updateTextInputButtonsVisibility(); - } - } - - private void updateTextInputButtonsVisibility() { - final Menu menu = mToolbar.getMenu(); - final MenuItem keypadToggleItem = menu.findItem(R.id.action_ime_dialpad_toggle); - final MenuItem deleteTextItem = menu.findItem(R.id.action_delete_text); - if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) { - if (TextUtils.isEmpty(mRecipientTextView.getText())) { - deleteTextItem.setVisible(false); - keypadToggleItem.setVisible(true); - } else { - deleteTextItem.setVisible(true); - keypadToggleItem.setVisible(false); - } - } else { - deleteTextItem.setVisible(false); - keypadToggleItem.setVisible(false); - } - } - - private void maybeGetOrCreateConversation() { - final ArrayList participants = - mRecipientTextView.getRecipientParticipantDataForConversationCreation(); - if (ContactPickerData.isTooManyParticipants(participants.size())) { - UiUtils.showToast(R.string.too_many_participants); - } else if (participants.size() > 0 && mMonitor == null) { - mMonitor = GetOrCreateConversationAction.getOrCreateConversation(participants, - null, this); - } - } - - /** - * Watches changes in contact chips to determine possible state transitions (e.g. creating - * the initial conversation, adding more participants or finish the current conversation) - */ - @Override - public void onContactChipsChanged(final int oldCount, final int newCount) { - Assert.isTrue(oldCount != newCount); - if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) { - // Initial picking mode. Start a conversation once a recipient has been picked. - maybeGetOrCreateConversation(); - } else if (mContactPickingMode == MODE_CHIPS_ONLY) { - // oldCount == 0 means we are restoring from savedInstanceState to add the existing - // chips, don't switch to "add more participants" mode in this case. - if (oldCount > 0 && mRecipientTextView.isFocused()) { - // Chips only mode. The user may have picked an additional contact or deleted the - // only existing contact. Either way, switch to picking more participants mode. - mHost.onInitiateAddMoreParticipants(); - } - } - mHost.onParticipantCountChanged(ContactPickerData.getCanAddMoreParticipants(newCount)); - - // Refresh our local copy of the selected chips set to keep it up-to-date. - mSelectedPhoneNumbers = mRecipientTextView.getSelectedDestinations(); - invalidateContactLists(); - } - - /** - * Listens for notification that invalid contacts have been removed during resolving them. - * These contacts were not local contacts, valid email, or valid phone numbers - */ - @Override - public void onInvalidContactChipsPruned(final int prunedCount) { - Assert.isTrue(prunedCount > 0); - UiUtils.showToast(R.plurals.add_invalid_contact_error, prunedCount); - } - - /** - * Listens for notification that the user has pressed enter/done on the keyboard with all - * contacts in place and we should create or go to the existing conversation now - */ - @Override - public void onEntryComplete() { - if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT || - mContactPickingMode == MODE_PICK_MORE_CONTACTS || - mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS) { - // Avoid multiple calls to create in race cases (hit done right after selecting contact) - maybeGetOrCreateConversation(); - } - } - - private void invalidateContactLists() { - mAllContactsListViewHolder.invalidateList(); - mFrequentContactsListViewHolder.invalidateList(); - } - - /** - * Kicks off a scene transition that animates visibility changes of individual contact list - * items via explode animation. - * @param show whether the contact lists are to be shown or hidden. - */ - private void startExplodeTransitionForContactLists(final boolean show) { - if (!OsUtil.isAtLeastL()) { - // Explode animation is not supported pre-L. - return; - } - final Explode transition = new Explode(); - final Rect epicenter = mPendingExplodeView == null ? null : - UiUtils.getMeasuredBoundsOnScreen(mPendingExplodeView); - transition.setDuration(UiUtils.COMPOSE_TRANSITION_DURATION); - transition.setInterpolator(UiUtils.EASE_IN_INTERPOLATOR); - transition.setEpicenterCallback(new EpicenterCallback() { - @Override - public Rect onGetEpicenter(final Transition transition) { - return epicenter; - } - }); - - // Kick off the delayed scene explode transition. Anything happens after this line in this - // method before the next frame will be tracked by the transition manager for visibility - // changes and animated accordingly. - TransitionManager.beginDelayedTransition(mCustomHeaderViewPager, - transition); - - toggleContactListItemsVisibilityForPendingTransition(show); - } - - /** - * Toggle the visibility of contact list items in the contact lists for them to be tracked by - * the transition manager for pending explode transition. - */ - private void toggleContactListItemsVisibilityForPendingTransition(final boolean show) { - if (!OsUtil.isAtLeastL()) { - // Explode animation is not supported pre-L. - return; - } - mAllContactsListViewHolder.toggleVisibilityForPendingTransition(show, mPendingExplodeView); - mFrequentContactsListViewHolder.toggleVisibilityForPendingTransition(show, - mPendingExplodeView); - } - - private void showHideContactPagerWithAnimation(final boolean show) { - final boolean isPagerVisible = (mCustomHeaderViewPager.getVisibility() == View.VISIBLE); - if (show == isPagerVisible) { - return; - } - - mCustomHeaderViewPager.animate().alpha(show ? 1F : 0F) - .setStartDelay(!show ? UiUtils.COMPOSE_TRANSITION_DURATION : 0) - .withStartAction(new Runnable() { - @Override - public void run() { - mCustomHeaderViewPager.setVisibility(View.VISIBLE); - mCustomHeaderViewPager.setAlpha(show ? 0F : 1F); - } - }) - .withEndAction(new Runnable() { - @Override - public void run() { - mCustomHeaderViewPager.setVisibility(show ? View.VISIBLE : View.GONE); - mCustomHeaderViewPager.setAlpha(1F); - } - }); - } - - @Override - public void onContactCustomColorLoaded(final ContactPickerData data) { - mBinding.ensureBound(data); - invalidateContactLists(); - } - - public void updateActionBar(final ActionBar actionBar) { - // Hide the action bar for contact picker mode. The custom ToolBar containing chips UI - // etc. will take the spot of the action bar. - actionBar.hide(); - UiUtils.setStatusBarColor(getActivity(), - getResources().getColor(R.color.compose_notification_bar_background)); - } - - private GetOrCreateConversationActionMonitor mMonitor; - - @Override - @RunsOnMainThread - public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor, - final Object data, final String conversationId) { - Assert.isTrue(monitor == mMonitor); - Assert.isTrue(conversationId != null); - - mRecipientTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE | - InputType.TYPE_CLASS_TEXT); - mHost.onGetOrCreateNewConversation(conversationId); - - mMonitor = null; - } - - @Override - @RunsOnMainThread - public void onGetOrCreateConversationFailed(final ActionMonitor monitor, - final Object data) { - Assert.isTrue(monitor == mMonitor); - LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed"); - mMonitor = null; - } -} diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java deleted file mode 100644 index 25f422e..0000000 --- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.database.Cursor; -import android.database.MergeCursor; -import android.support.v4.util.Pair; -import android.text.TextUtils; -import android.text.util.Rfc822Token; -import android.text.util.Rfc822Tokenizer; -import android.widget.Filter; - -import com.android.ex.chips.BaseRecipientAdapter; -import com.android.ex.chips.RecipientAlternatesAdapter; -import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback; -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.ContactRecipientEntryUtils; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.PhoneUtils; - -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * An extension on the base {@link BaseRecipientAdapter} that uses data layer from Bugle, - * such as the ContactRecipientPhotoManager that uses our own MediaResourceManager, and - * contact lookup that relies on ContactUtil. It provides data source and filtering ability - * for {@link ContactRecipientAutoCompleteView} - */ -public final class ContactRecipientAdapter extends BaseRecipientAdapter { - public ContactRecipientAdapter(final Context context, - final ContactListItemView.HostInterface clivHost) { - this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost); - } - - public ContactRecipientAdapter(final Context context, final int preferredMaxResultCount, - final int queryMode, final ContactListItemView.HostInterface clivHost) { - super(context, preferredMaxResultCount, queryMode); - setPhotoManager(new ContactRecipientPhotoManager(context, clivHost)); - } - - @Override - public boolean forceShowAddress() { - // We should always use the SingleRecipientAddressAdapter - // And never use the RecipientAlternatesAdapter - return true; - } - - @Override - public Filter getFilter() { - return new ContactFilter(); - } - - /** - * A Filter for a RecipientEditTextView that queries Bugle's ContactUtil for auto-complete - * results. - */ - public class ContactFilter extends Filter { - // Used to sort filtered contacts when it has combined results from email and phone. - private final RecipientEntryComparator mComparator = new RecipientEntryComparator(); - - /** - * Returns a cursor containing the filtered results in contacts given the search text, - * and a boolean indicating whether the results are sorted. - * - * The queries are synchronously performed since this is not run on the main thread. - * - * Some locales (e.g. JPN) expect email addresses to be auto-completed for MMS. - * If this is the case, perform two queries on phone number followed by email and - * return the merged results. - */ - @DoesNotRunOnMainThread - private Pair getFilteredResultsCursor(final Context context, - final String searchText) { - Assert.isNotMainThread(); - if (BugleGservices.get().getBoolean( - BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS, - BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT)) { - return Pair.create((Cursor) new MergeCursor(new Cursor[] { - ContactUtil.filterPhones(getContext(), searchText) - .performSynchronousQuery(), - ContactUtil.filterEmails(getContext(), searchText) - .performSynchronousQuery() - }), false /* the merged cursor is not sorted */); - } else { - return Pair.create(ContactUtil.filterDestination(getContext(), searchText) - .performSynchronousQuery(), true); - } - } - - @Override - protected FilterResults performFiltering(final CharSequence constraint) { - Assert.isNotMainThread(); - final FilterResults results = new FilterResults(); - - // No query, return empty results. - if (TextUtils.isEmpty(constraint)) { - clearTempEntries(); - return results; - } - - final String searchText = constraint.toString(); - - // Query for auto-complete results, since performFiltering() is not done on the - // main thread, perform the cursor loader queries directly. - final Pair filteredResults = getFilteredResultsCursor(getContext(), - searchText); - final Cursor cursor = filteredResults.first; - final boolean sorted = filteredResults.second; - if (cursor != null) { - try { - final List entries = new ArrayList(); - - // First check if the constraint is a valid SMS destination. If so, add the - // destination as a suggestion item to the drop down. - if (PhoneUtils.isValidSmsMmsDestination(searchText)) { - entries.add(ContactRecipientEntryUtils - .constructSendToDestinationEntry(searchText)); - } - - HashSet existingContactIds = new HashSet(); - while (cursor.moveToNext()) { - // Make sure there's only one first-level contact (i.e. contact for which - // we show the avatar picture and name) for every contact id. - final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID); - final boolean isFirstLevel = !existingContactIds.contains(contactId); - if (isFirstLevel) { - existingContactIds.add(contactId); - } - entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor, - isFirstLevel)); - } - - if (!sorted) { - Collections.sort(entries, mComparator); - } - results.values = entries; - results.count = 1; - - } finally { - cursor.close(); - } - } - return results; - } - - @Override - protected void publishResults(final CharSequence constraint, final FilterResults results) { - mCurrentConstraint = constraint; - clearTempEntries(); - - if (results.values != null) { - @SuppressWarnings("unchecked") - final List entries = (List) results.values; - updateEntries(entries); - } else { - updateEntries(Collections.emptyList()); - } - } - - private class RecipientEntryComparator implements Comparator { - private final Collator mCollator; - - public RecipientEntryComparator() { - mCollator = Collator.getInstance(Locale.getDefault()); - mCollator.setStrength(Collator.PRIMARY); - } - - /** - * Compare two RecipientEntry's, first by locale-aware display name comparison, then by - * contact id comparison, finally by first-level-ness comparison. - */ - @Override - public int compare(RecipientEntry lhs, RecipientEntry rhs) { - // Send-to-destinations always appear before everything else. - final boolean sendToLhs = ContactRecipientEntryUtils - .isSendToDestinationContact(lhs); - final boolean sendToRhs = ContactRecipientEntryUtils - .isSendToDestinationContact(lhs); - if (sendToLhs != sendToRhs) { - if (sendToLhs) { - return -1; - } else if (sendToRhs) { - return 1; - } - } - - final int displayNameCompare = mCollator.compare(lhs.getDisplayName(), - rhs.getDisplayName()); - if (displayNameCompare != 0) { - return displayNameCompare; - } - - // Long.compare could accomplish the following three lines, but this is only - // available in API 19+ - final long lhsContactId = lhs.getContactId(); - final long rhsContactId = rhs.getContactId(); - final int contactCompare = lhsContactId < rhsContactId ? -1 : - (lhsContactId == rhsContactId ? 0 : 1); - if (contactCompare != 0) { - return contactCompare; - } - - // These are the same contact. Make sure first-level contacts always - // appear at the front. - if (lhs.isFirstLevel()) { - return -1; - } else if (rhs.isFirstLevel()) { - return 1; - } else { - return 0; - } - } - } - } - - /** - * Called when we need to substitute temporary recipient chips with better alternatives. - * For example, if a list of comma-delimited phone numbers are pasted into the edit box, - * we want to be able to look up in the ContactUtil for exact matches and get contact - * details such as name and photo thumbnail for the contact to display a better chip. - */ - @Override - public void getMatchingRecipients(final ArrayList inAddresses, - final RecipientMatchCallback callback) { - final int addressesSize = Math.min( - RecipientAlternatesAdapter.MAX_LOOKUPS, inAddresses.size()); - final HashSet addresses = new HashSet(); - for (int i = 0; i < addressesSize; i++) { - final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase()); - addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i)); - } - - final Map recipientEntries = - new HashMap(); - // query for each address - for (final String address : addresses) { - final Cursor cursor = ContactUtil.lookupDestination(getContext(), address) - .performSynchronousQuery(); - if (cursor != null) { - try { - if (cursor.moveToNext()) { - // There may be multiple matches to the same number, always take the - // first match. - // TODO: May need to consider if there's an existing conversation - // that matches this particular contact and prioritize that contact. - final RecipientEntry entry = - ContactUtil.createRecipientEntryForPhoneQuery(cursor, true); - recipientEntries.put(address, entry); - } - - } finally { - cursor.close(); - } - } - } - - // report matches - callback.matchesFound(recipientEntries); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java b/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java deleted file mode 100644 index c7c2731..0000000 --- a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Rect; -import android.os.AsyncTask; -import android.support.v7.appcompat.R; -import android.text.Editable; -import android.text.TextPaint; -import android.text.TextWatcher; -import android.text.util.Rfc822Tokenizer; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.KeyEvent; -import android.view.inputmethod.EditorInfo; -import android.widget.TextView; - -import com.android.ex.chips.RecipientEditTextView; -import com.android.ex.chips.RecipientEntry; -import com.android.ex.chips.recipientchip.DrawableRecipientChip; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.ContactRecipientEntryUtils; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * An extension for {@link RecipientEditTextView} which shows a list of Materialized contact chips. - * It uses Bugle's ContactUtil to perform contact lookup, and is able to return the list of - * recipients in the form of a ParticipantData list. - */ -public class ContactRecipientAutoCompleteView extends RecipientEditTextView { - public interface ContactChipsChangeListener { - void onContactChipsChanged(int oldCount, int newCount); - void onInvalidContactChipsPruned(int prunedCount); - void onEntryComplete(); - } - - private final int mTextHeight; - private ContactChipsChangeListener mChipsChangeListener; - - /** - * Watches changes in contact chips to determine possible state transitions. - */ - private class ContactChipsWatcher implements TextWatcher { - /** - * Tracks the old chips count before text changes. Note that we currently don't compare - * the entire chip sets but just the cheaper-to-do before and after counts, because - * the chips view don't allow for replacing chips. - */ - private int mLastChipsCount = 0; - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, - final int count) { - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, - final int after) { - // We don't take mLastChipsCount from here but from the last afterTextChanged() run. - // The reason is because at this point, any chip spans to be removed is already removed - // from s in the chips text view. - } - - @Override - public void afterTextChanged(final Editable s) { - final int currentChipsCount = s.getSpans(0, s.length(), - DrawableRecipientChip.class).length; - if (currentChipsCount != mLastChipsCount) { - // When a sanitizing task is running, we don't want to notify any chips count - // change, but we do want to track the last chip count. - if (mChipsChangeListener != null && mCurrentSanitizeTask == null) { - mChipsChangeListener.onContactChipsChanged(mLastChipsCount, currentChipsCount); - } - mLastChipsCount = currentChipsCount; - } - } - } - - private static final String TEXT_HEIGHT_SAMPLE = "a"; - - public ContactRecipientAutoCompleteView(final Context context, final AttributeSet attrs) { - super(new ContextThemeWrapper(context, R.style.ColorAccentGrayOverrideStyle), attrs); - - // Get the height of the text, given the currently set font face and size. - final Rect textBounds = new Rect(0, 0, 0, 0); - final TextPaint paint = getPaint(); - paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), textBounds); - mTextHeight = textBounds.height(); - - setTokenizer(new Rfc822Tokenizer()); - addTextChangedListener(new ContactChipsWatcher()); - setOnFocusListShrinkRecipients(false); - - setBackground(context.getResources().getDrawable( - R.drawable.abc_textfield_search_default_mtrl_alpha)); - } - - public void setContactChipsListener(final ContactChipsChangeListener listener) { - mChipsChangeListener = listener; - } - - /** - * A tuple of chips which AsyncContactChipSanitizeTask reports as progress to have the - * chip actually replaced/removed on the UI thread. - */ - private class ChipReplacementTuple { - public final DrawableRecipientChip removedChip; - public final RecipientEntry replacedChipEntry; - - public ChipReplacementTuple(final DrawableRecipientChip removedChip, - final RecipientEntry replacedChipEntry) { - this.removedChip = removedChip; - this.replacedChipEntry = replacedChipEntry; - } - } - - /** - * An AsyncTask that cleans up contact chips on every chips commit (i.e. get or create a new - * conversation with the given chips). - */ - private class AsyncContactChipSanitizeTask extends - AsyncTask { - - @Override - protected Integer doInBackground(final Void... params) { - final DrawableRecipientChip[] recips = getText() - .getSpans(0, getText().length(), DrawableRecipientChip.class); - int invalidChipsRemoved = 0; - for (final DrawableRecipientChip recipient : recips) { - final RecipientEntry entry = recipient.getEntry(); - if (entry != null) { - if (entry.isValid()) { - if (RecipientEntry.isCreatedRecipient(entry.getContactId()) || - ContactRecipientEntryUtils.isSendToDestinationContact(entry)) { - // This is a generated/send-to contact chip, try to look it up and - // display a chip for the corresponding local contact. - final Cursor lookupResult = ContactUtil.lookupDestination(getContext(), - entry.getDestination()).performSynchronousQuery(); - if (lookupResult != null && lookupResult.moveToNext()) { - // Found a match, remove the generated entry and replace with - // a better local entry. - publishProgress(new ChipReplacementTuple(recipient, - ContactUtil.createRecipientEntryForPhoneQuery( - lookupResult, true))); - } else if (PhoneUtils.isValidSmsMmsDestination( - entry.getDestination())){ - // No match was found, but we have a valid destination so let's at - // least create an entry that shows an avatar. - publishProgress(new ChipReplacementTuple(recipient, - ContactRecipientEntryUtils.constructNumberWithAvatarEntry( - entry.getDestination()))); - } else { - // Not a valid contact. Remove and show an error. - publishProgress(new ChipReplacementTuple(recipient, null)); - invalidChipsRemoved++; - } - } - } else { - publishProgress(new ChipReplacementTuple(recipient, null)); - invalidChipsRemoved++; - } - } - } - return invalidChipsRemoved; - } - - @Override - protected void onProgressUpdate(final ChipReplacementTuple... values) { - for (final ChipReplacementTuple tuple : values) { - if (tuple.removedChip != null) { - final Editable text = getText(); - final int chipStart = text.getSpanStart(tuple.removedChip); - final int chipEnd = text.getSpanEnd(tuple.removedChip); - if (chipStart >= 0 && chipEnd >= 0) { - text.delete(chipStart, chipEnd); - } - - if (tuple.replacedChipEntry != null) { - appendRecipientEntry(tuple.replacedChipEntry); - } - } - } - } - - @Override - protected void onPostExecute(final Integer invalidChipsRemoved) { - mCurrentSanitizeTask = null; - if (invalidChipsRemoved > 0) { - mChipsChangeListener.onInvalidContactChipsPruned(invalidChipsRemoved); - } - } - } - - /** - * We don't use SafeAsyncTask but instead use a single threaded executor to ensure that - * all sanitization tasks are serially executed so as not to interfere with each other. - */ - private static final Executor SANITIZE_EXECUTOR = Executors.newSingleThreadExecutor(); - - private AsyncContactChipSanitizeTask mCurrentSanitizeTask; - - /** - * Whenever the caller wants to start a new conversation with the list of chips we have, - * make sure we asynchronously: - * 1. Remove invalid chips. - * 2. Attempt to resolve unknown contacts to known local contacts. - * 3. Convert still unknown chips to chips with generated avatar. - * - * Note that we don't need to perform this synchronously since we can - * resolve any unknown contacts to local contacts when needed. - */ - private void sanitizeContactChips() { - if (mCurrentSanitizeTask != null && !mCurrentSanitizeTask.isCancelled()) { - mCurrentSanitizeTask.cancel(false); - mCurrentSanitizeTask = null; - } - mCurrentSanitizeTask = new AsyncContactChipSanitizeTask(); - mCurrentSanitizeTask.executeOnExecutor(SANITIZE_EXECUTOR); - } - - /** - * Returns a list of ParticipantData from the entered chips in order to create - * new conversation. - */ - public ArrayList getRecipientParticipantDataForConversationCreation() { - final DrawableRecipientChip[] recips = getText() - .getSpans(0, getText().length(), DrawableRecipientChip.class); - final ArrayList contacts = - new ArrayList(recips.length); - for (final DrawableRecipientChip recipient : recips) { - final RecipientEntry entry = recipient.getEntry(); - if (entry != null && entry.isValid() && entry.getDestination() != null && - PhoneUtils.isValidSmsMmsDestination(entry.getDestination())) { - contacts.add(ParticipantData.getFromRecipientEntry(recipient.getEntry())); - } - } - sanitizeContactChips(); - return contacts; - } - - /**c - * Gets a set of currently selected chips' emails/phone numbers. This will facilitate the - * consumer with determining quickly whether a contact is currently selected. - */ - public Set getSelectedDestinations() { - Set set = new HashSet(); - final DrawableRecipientChip[] recips = getText() - .getSpans(0, getText().length(), DrawableRecipientChip.class); - - for (final DrawableRecipientChip recipient : recips) { - final RecipientEntry entry = recipient.getEntry(); - if (entry != null && entry.isValid() && entry.getDestination() != null) { - set.add(PhoneUtils.getDefault().getCanonicalBySystemLocale( - entry.getDestination())); - } - } - return set; - } - - @Override - public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - mChipsChangeListener.onEntryComplete(); - } - return super.onEditorAction(view, actionId, event); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java b/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java deleted file mode 100644 index d69ba64..0000000 --- a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.net.Uri; - -import com.android.ex.chips.PhotoManager; -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.media.AvatarRequestDescriptor; -import com.android.messaging.datamodel.media.BindableMediaRequest; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.ThreadUtil; - -/** - * An implementation of {@link PhotoManager} that hooks up the chips UI's photos with our own - * {@link MediaResourceManager} for retrieving and caching contact avatars. - */ -public class ContactRecipientPhotoManager implements PhotoManager { - private static final String IMAGE_BYTES_REQUEST_STATIC_BINDING_ID = "imagebytes"; - private final Context mContext; - private final int mIconSize; - private final ContactListItemView.HostInterface mClivHostInterface; - - public ContactRecipientPhotoManager(final Context context, - final ContactListItemView.HostInterface clivHostInterface) { - mContext = context; - mIconSize = context.getResources().getDimensionPixelSize( - R.dimen.compose_message_chip_height) - context.getResources().getDimensionPixelSize( - R.dimen.compose_message_chip_padding) * 2; - mClivHostInterface = clivHostInterface; - } - - /** - * {@inheritDoc} - */ - @Override - public void populatePhotoBytesAsync(final RecipientEntry entry, - final PhotoManagerCallback callback) { - // Post all media resource request to the main thread. - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - ParticipantData.getFromRecipientEntry(entry)); - final AvatarRequestDescriptor descriptor = - new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize); - final BindableMediaRequest req = descriptor.buildAsyncMediaRequest( - mContext, - new MediaResourceLoadListener() { - @Override - public void onMediaResourceLoaded(final MediaRequest request, - final ImageResource resource, final boolean isCached) { - entry.setPhotoBytes(resource.getBytes()); - callback.onPhotoBytesAsynchronouslyPopulated(); - } - - @Override - public void onMediaResourceLoadError(final MediaRequest request, - final Exception exception) { - LogUtil.e(LogUtil.BUGLE_TAG, "Photo bytes loading failed due to " + - exception + " request key=" + request.getKey()); - - // Fall back to the default avatar image. - callback.onPhotoBytesAsyncLoadFailed(); - }}); - - // Statically bind the request since it's not bound to any specific piece of UI. - req.bind(IMAGE_BYTES_REQUEST_STATIC_BINDING_ID); - - Factory.get().getMediaResourceManager().requestMediaResourceAsync(req); - } - }); - } -} diff --git a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java deleted file mode 100644 index 1d5abf3..0000000 --- a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.database.Cursor; -import android.os.Bundle; -import android.provider.ContactsContract.Contacts; -import android.text.TextUtils; -import android.widget.SectionIndexer; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; - -/** - * Indexes contact alphabetical sections so we can report to the fast scrolling list view - * where we are in the list when the user scrolls through the contact list, allowing us to show - * alphabetical indicators for the fast scroller as well as list section headers. - */ -public class ContactSectionIndexer implements SectionIndexer { - private String[] mSections; - private ArrayList mSectionStartingPositions; - private static final String BLANK_HEADER_STRING = " "; - - public ContactSectionIndexer(final Cursor contactsCursor) { - buildIndexer(contactsCursor); - } - - @Override - public Object[] getSections() { - return mSections; - } - - @Override - public int getPositionForSection(final int sectionIndex) { - if (mSectionStartingPositions.isEmpty()) { - return 0; - } - // Clamp to the bounds of the section position array per Android API doc. - return mSectionStartingPositions.get( - Math.max(Math.min(sectionIndex, mSectionStartingPositions.size() - 1), 0)); - } - - @Override - public int getSectionForPosition(final int position) { - if (mSectionStartingPositions.isEmpty()) { - return 0; - } - - // Perform a binary search on the starting positions of the sections to the find the - // section for the position. - int left = 0; - int right = mSectionStartingPositions.size() - 1; - - // According to getSectionForPosition()'s doc, we should always clamp the value when the - // position is out of bound. - if (position <= mSectionStartingPositions.get(left)) { - return left; - } else if (position >= mSectionStartingPositions.get(right)) { - return right; - } - - while (left <= right) { - final int mid = (left + right) / 2; - final int startingPos = mSectionStartingPositions.get(mid); - final int nextStartingPos = mSectionStartingPositions.get(mid + 1); - if (position >= startingPos && position < nextStartingPos) { - return mid; - } else if (position < startingPos) { - right = mid - 1; - } else if (position >= nextStartingPos) { - left = mid + 1; - } - } - Assert.fail("Invalid section indexer state: couldn't find section for pos " + position); - return -1; - } - - private boolean buildIndexerFromCursorExtras(final Cursor cursor) { - if (cursor == null) { - return false; - } - final Bundle cursorExtras = cursor.getExtras(); - if (cursorExtras == null) { - return false; - } - final String[] sections = cursorExtras.getStringArray( - Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); - final int[] counts = cursorExtras.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); - if (sections == null || counts == null) { - return false; - } - - if (sections.length != counts.length) { - return false; - } - - this.mSections = sections; - mSectionStartingPositions = new ArrayList(counts.length); - int position = 0; - for (int i = 0; i < counts.length; i++) { - if (TextUtils.isEmpty(mSections[i])) { - mSections[i] = BLANK_HEADER_STRING; - } else if (!mSections[i].equals(BLANK_HEADER_STRING)) { - mSections[i] = mSections[i].trim(); - } - - mSectionStartingPositions.add(position); - position += counts[i]; - } - return true; - } - - private void buildIndexerFromDisplayNames(final Cursor cursor) { - // Loop through the contact cursor and get the starting position for each first character. - // The result is stored into two arrays, one for the section header (i.e. the first - // character), and one for the starting position, which is guaranteed to be sorted in - // ascending order. - final ArrayList sections = new ArrayList(); - mSectionStartingPositions = new ArrayList(); - if (cursor != null) { - cursor.moveToPosition(-1); - int currentPosition = 0; - while (cursor.moveToNext()) { - // The sort key is typically the contact's display name, so for example, a contact - // named "Bob" will go into section "B". The Contacts provider generally uses a - // a slightly more sophisticated heuristic, but as a fallback this is good enough. - final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY); - final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING : - sortKey.substring(0, 1).toUpperCase(); - - final int lastIndex = sections.size() - 1; - final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null; - if (!TextUtils.equals(currentSection, section)) { - sections.add(section); - mSectionStartingPositions.add(currentPosition); - } - currentPosition++; - } - } - mSections = new String[sections.size()]; - sections.toArray(mSections); - } - - private void buildIndexer(final Cursor cursor) { - // First check if we get indexer label extras from the contact provider; if not, fall back - // to building from display names. - if (!buildIndexerFromCursorExtras(cursor)) { - LogUtil.w(LogUtil.BUGLE_TAG, "contact provider didn't provide contact label " + - "information, fall back to using display name!"); - buildIndexerFromDisplayNames(cursor); - } - } -} diff --git a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java b/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java deleted file mode 100644 index 1f3c795..0000000 --- a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; - -import com.android.messaging.R; -import com.android.messaging.ui.CustomHeaderPagerListViewHolder; -import com.android.messaging.ui.contact.ContactListItemView.HostInterface; - -/** - * Holds the frequent contacts view for the contact picker's view pager. - */ -public class FrequentContactsListViewHolder extends CustomHeaderPagerListViewHolder { - public FrequentContactsListViewHolder(final Context context, - final HostInterface clivHostInterface) { - super(context, new ContactListAdapter(context, null, clivHostInterface, - false /* needAlphabetHeader */)); - } - - @Override - protected int getLayoutResId() { - return R.layout.frequent_contacts_list_view; - } - - @Override - protected int getPageTitleResId() { - return R.string.contact_picker_frequents_tab_title; - } - - @Override - protected int getEmptyViewResId() { - return R.id.empty_view; - } - - @Override - protected int getListViewResId() { - return R.id.frequent_contacts_list; - } - - @Override - protected int getEmptyViewTitleResId() { - return R.string.contact_list_empty_text; - } - - @Override - protected int getEmptyViewImageResId() { - return R.drawable.ic_oobe_freq_list; - } -} diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java deleted file mode 100644 index 17f8f74..0000000 --- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java +++ /dev/null @@ -1,962 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.text.Editable; -import android.text.Html; -import android.text.InputFilter; -import android.text.InputFilter.LengthFilter; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.KeyEvent; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.EditorInfo; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask; -import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.ui.AttachmentPreview; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.PlainTextEditText; -import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -import java.util.Collection; -import java.util.List; - -/** - * This view contains the UI required to generate and send messages. - */ -public class ComposeMessageView extends LinearLayout - implements TextView.OnEditorActionListener, DraftMessageDataListener, TextWatcher, - ConversationInputSink { - - public interface IComposeMessageViewHost extends - DraftMessageData.DraftMessageSubscriptionDataProvider { - void sendMessage(MessageData message); - void onComposeEditTextFocused(); - void onAttachmentsCleared(); - void onAttachmentsChanged(final boolean haveAttachments); - void displayPhoto(Uri photoUri, Rect imageBounds, boolean isDraft); - void promptForSelfPhoneNumber(); - boolean isReadyForAction(); - void warnOfMissingActionConditions(final boolean sending, - final Runnable commandToRunAfterActionConditionResolved); - void warnOfExceedingMessageLimit(final boolean showAttachmentChooser, - boolean tooManyVideos); - void notifyOfAttachmentLoadFailed(); - void showAttachmentChooser(); - boolean shouldShowSubjectEditor(); - boolean shouldHideAttachmentsWhenSimSelectorShown(); - Uri getSelfSendButtonIconUri(); - int overrideCounterColor(); - int getAttachmentsClearedFlags(); - } - - public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10; - - // There is no draft and there is no need for the SIM selector - private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1; - // There is no draft but we need to show the SIM selector - private static final int SEND_WIDGET_MODE_SIM_SELECTOR = 2; - // There is a draft - private static final int SEND_WIDGET_MODE_SEND_BUTTON = 3; - - private PlainTextEditText mComposeEditText; - private PlainTextEditText mComposeSubjectText; - private TextView mCharCounter; - private TextView mMmsIndicator; - private SimIconView mSelfSendIcon; - private ImageButton mSendButton; - private View mSubjectView; - private ImageButton mDeleteSubjectButton; - private AttachmentPreview mAttachmentPreview; - private ImageButton mAttachMediaButton; - - private final Binding mBinding; - private IComposeMessageViewHost mHost; - private final Context mOriginalContext; - private int mSendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR; - - // Shared data model object binding from the conversation. - private ImmutableBindingRef mConversationDataModel; - - // Centrally manages all the mutual exclusive UI components accepting user input, i.e. - // media picker, IME keyboard and SIM selector. - private ConversationInputManager mInputManager; - - private final ConversationDataListener mDataListener = new SimpleConversationDataListener() { - @Override - public void onConversationMetadataUpdated(ConversationData data) { - mConversationDataModel.ensureBound(data); - updateVisualsOnDraftChanged(); - } - - @Override - public void onConversationParticipantDataLoaded(ConversationData data) { - mConversationDataModel.ensureBound(data); - updateVisualsOnDraftChanged(); - } - - @Override - public void onSubscriptionListDataLoaded(ConversationData data) { - mConversationDataModel.ensureBound(data); - updateOnSelfSubscriptionChange(); - updateVisualsOnDraftChanged(); - } - }; - - public ComposeMessageView(final Context context, final AttributeSet attrs) { - super(new ContextThemeWrapper(context, R.style.ColorAccentBlueOverrideStyle), attrs); - mOriginalContext = context; - mBinding = BindingBase.createBinding(this); - } - - /** - * Host calls this to bind view to DraftMessageData object - */ - public void bind(final DraftMessageData data, final IComposeMessageViewHost host) { - mHost = host; - mBinding.bind(data); - data.addListener(this); - data.setSubscriptionDataProvider(host); - - final int counterColor = mHost.overrideCounterColor(); - if (counterColor != -1) { - mCharCounter.setTextColor(counterColor); - } - } - - /** - * Host calls this to unbind view - */ - public void unbind() { - mBinding.unbind(); - mHost = null; - mInputManager.onDetach(); - } - - @Override - protected void onFinishInflate() { - mComposeEditText = (PlainTextEditText) findViewById( - R.id.compose_message_text); - mComposeEditText.setOnEditorActionListener(this); - mComposeEditText.addTextChangedListener(this); - mComposeEditText.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(final View v, final boolean hasFocus) { - if (v == mComposeEditText && hasFocus) { - mHost.onComposeEditTextFocused(); - } - } - }); - mComposeEditText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View arg0) { - if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) { - hideSimSelector(); - } - } - }); - - // onFinishInflate() is called before self is loaded from db. We set the default text - // limit here, and apply the real limit later in updateOnSelfSubscriptionChange(). - mComposeEditText.setFilters(new InputFilter[] { - new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID) - .getMaxTextLimit()) }); - - mSelfSendIcon = (SimIconView) findViewById(R.id.self_send_icon); - mSelfSendIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - boolean shown = mInputManager.toggleSimSelector(true /* animate */, - getSelfSubscriptionListEntry()); - hideAttachmentsWhenShowingSims(shown); - } - }); - mSelfSendIcon.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(final View v) { - if (mHost.shouldShowSubjectEditor()) { - showSubjectEditor(); - } else { - boolean shown = mInputManager.toggleSimSelector(true /* animate */, - getSelfSubscriptionListEntry()); - hideAttachmentsWhenShowingSims(shown); - } - return true; - } - }); - - mComposeSubjectText = (PlainTextEditText) findViewById( - R.id.compose_subject_text); - // We need the listener to change the avatar to the send button when the user starts - // typing a subject without a message. - mComposeSubjectText.addTextChangedListener(this); - // onFinishInflate() is called before self is loaded from db. We set the default text - // limit here, and apply the real limit later in updateOnSelfSubscriptionChange(). - mComposeSubjectText.setFilters(new InputFilter[] { - new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID) - .getMaxSubjectLength())}); - - mDeleteSubjectButton = (ImageButton) findViewById(R.id.delete_subject_button); - mDeleteSubjectButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View clickView) { - hideSubjectEditor(); - mComposeSubjectText.setText(null); - mBinding.getData().setMessageSubject(null); - } - }); - - mSubjectView = findViewById(R.id.subject_view); - - mSendButton = (ImageButton) findViewById(R.id.send_message_button); - mSendButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View clickView) { - sendMessageInternal(true /* checkMessageSize */); - } - }); - mSendButton.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(final View arg0) { - boolean shown = mInputManager.toggleSimSelector(true /* animate */, - getSelfSubscriptionListEntry()); - hideAttachmentsWhenShowingSims(shown); - if (mHost.shouldShowSubjectEditor()) { - showSubjectEditor(); - } - return true; - } - }); - mSendButton.setAccessibilityDelegate(new AccessibilityDelegate() { - @Override - public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(host, event); - // When the send button is long clicked, we want TalkBack to announce the real - // action (select SIM or edit subject), as opposed to "long press send button." - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) { - event.getText().clear(); - event.getText().add(getResources() - .getText(shouldShowSimSelector(mConversationDataModel.getData()) ? - R.string.send_button_long_click_description_with_sim_selector : - R.string.send_button_long_click_description_no_sim_selector)); - // Make this an announcement so TalkBack will read our custom message. - event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); - } - } - }); - - mAttachMediaButton = - (ImageButton) findViewById(R.id.attach_media_button); - mAttachMediaButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View clickView) { - // Showing the media picker is treated as starting to compose the message. - mInputManager.showHideMediaPicker(true /* show */, true /* animate */); - } - }); - - mAttachmentPreview = (AttachmentPreview) findViewById(R.id.attachment_draft_view); - mAttachmentPreview.setComposeMessageView(this); - - mCharCounter = (TextView) findViewById(R.id.char_counter); - mMmsIndicator = (TextView) findViewById(R.id.mms_indicator); - } - - private void hideAttachmentsWhenShowingSims(final boolean simPickerVisible) { - if (!mHost.shouldHideAttachmentsWhenSimSelectorShown()) { - return; - } - final boolean haveAttachments = mBinding.getData().hasAttachments(); - if (simPickerVisible && haveAttachments) { - mHost.onAttachmentsChanged(false); - mAttachmentPreview.hideAttachmentPreview(); - } else { - mHost.onAttachmentsChanged(haveAttachments); - mAttachmentPreview.onAttachmentsChanged(mBinding.getData()); - } - } - - public void setInputManager(final ConversationInputManager inputManager) { - mInputManager = inputManager; - } - - public void setConversationDataModel(final ImmutableBindingRef refDataModel) { - mConversationDataModel = refDataModel; - mConversationDataModel.getData().addConversationDataListener(mDataListener); - } - - ImmutableBindingRef getDraftDataModel() { - return BindingBase.createBindingReference(mBinding); - } - - // returns true if it actually shows the subject editor and false if already showing - private boolean showSubjectEditor() { - // show the subject editor - if (mSubjectView.getVisibility() == View.GONE) { - mSubjectView.setVisibility(View.VISIBLE); - mSubjectView.requestFocus(); - return true; - } - return false; - } - - private void hideSubjectEditor() { - mSubjectView.setVisibility(View.GONE); - mComposeEditText.requestFocus(); - } - - /** - * {@inheritDoc} from TextView.OnEditorActionListener - */ - @Override // TextView.OnEditorActionListener.onEditorAction - public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - sendMessageInternal(true /* checkMessageSize */); - return true; - } - return false; - } - - private void sendMessageInternal(final boolean checkMessageSize) { - LogUtil.i(LogUtil.BUGLE_TAG, "UI initiated message sending in conversation " + - mBinding.getData().getConversationId()); - if (mBinding.getData().isCheckingDraft()) { - // Don't send message if we are currently checking draft for sending. - LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: still checking draft"); - return; - } - // Check the host for pre-conditions about any action. - if (mHost.isReadyForAction()) { - mInputManager.showHideSimSelector(false /* show */, true /* animate */); - final String messageToSend = mComposeEditText.getText().toString(); - mBinding.getData().setMessageText(messageToSend); - final String subject = mComposeSubjectText.getText().toString(); - mBinding.getData().setMessageSubject(subject); - // Asynchronously check the draft against various requirements before sending. - mBinding.getData().checkDraftForAction(checkMessageSize, - mHost.getConversationSelfSubId(), new CheckDraftTaskCallback() { - @Override - public void onDraftChecked(DraftMessageData data, int result) { - mBinding.ensureBound(data); - switch (result) { - case CheckDraftForSendTask.RESULT_PASSED: - // Continue sending after check succeeded. - final MessageData message = mBinding.getData() - .prepareMessageForSending(mBinding); - if (message != null && message.hasContent()) { - playSentSound(); - mHost.sendMessage(message); - hideSubjectEditor(); - if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) { - AccessibilityUtil.announceForAccessibilityCompat( - ComposeMessageView.this, null, - R.string.sending_message); - } - } - break; - - case CheckDraftForSendTask.RESULT_HAS_PENDING_ATTACHMENTS: - // Cannot send while there's still attachment(s) being loaded. - UiUtils.showToastAtBottom( - R.string.cant_send_message_while_loading_attachments); - break; - - case CheckDraftForSendTask.RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS: - mHost.promptForSelfPhoneNumber(); - break; - - case CheckDraftForSendTask.RESULT_MESSAGE_OVER_LIMIT: - Assert.isTrue(checkMessageSize); - mHost.warnOfExceedingMessageLimit( - true /*sending*/, false /* tooManyVideos */); - break; - - case CheckDraftForSendTask.RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED: - Assert.isTrue(checkMessageSize); - mHost.warnOfExceedingMessageLimit( - true /*sending*/, true /* tooManyVideos */); - break; - - case CheckDraftForSendTask.RESULT_SIM_NOT_READY: - // Cannot send if there is no active subscription - UiUtils.showToastAtBottom( - R.string.cant_send_message_without_active_subscription); - break; - - default: - break; - } - } - }, mBinding); - } else { - mHost.warnOfMissingActionConditions(true /*sending*/, - new Runnable() { - @Override - public void run() { - sendMessageInternal(checkMessageSize); - } - - }); - } - } - - public static void playSentSound() { - // Check if this setting is enabled before playing - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - final Context context = Factory.get().getApplicationContext(); - final String prefKey = context.getString(R.string.send_sound_pref_key); - final boolean defaultValue = context.getResources().getBoolean( - R.bool.send_sound_pref_default); - if (!prefs.getBoolean(prefKey, defaultValue)) { - return; - } - MediaUtil.get().playSound(context, R.raw.message_sent, null /* completionListener */); - } - - /** - * {@inheritDoc} from DraftMessageDataListener - */ - @Override // From DraftMessageDataListener - public void onDraftChanged(final DraftMessageData data, final int changeFlags) { - // As this is called asynchronously when message read check bound before updating text - mBinding.ensureBound(data); - - // We have to cache the values of the DraftMessageData because when we set - // mComposeEditText, its onTextChanged calls updateVisualsOnDraftChanged, - // which immediately reloads the text from the subject and message fields and replaces - // what's in the DraftMessageData. - - final String subject = data.getMessageSubject(); - final String message = data.getMessageText(); - - if ((changeFlags & DraftMessageData.MESSAGE_SUBJECT_CHANGED) == - DraftMessageData.MESSAGE_SUBJECT_CHANGED) { - mComposeSubjectText.setText(subject); - - // Set the cursor selection to the end since setText resets it to the start - mComposeSubjectText.setSelection(mComposeSubjectText.getText().length()); - } - - if ((changeFlags & DraftMessageData.MESSAGE_TEXT_CHANGED) == - DraftMessageData.MESSAGE_TEXT_CHANGED) { - mComposeEditText.setText(message); - - // Set the cursor selection to the end since setText resets it to the start - mComposeEditText.setSelection(mComposeEditText.getText().length()); - } - - if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) == - DraftMessageData.ATTACHMENTS_CHANGED) { - final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data); - mHost.onAttachmentsChanged(haveAttachments); - } - - if ((changeFlags & DraftMessageData.SELF_CHANGED) == DraftMessageData.SELF_CHANGED) { - updateOnSelfSubscriptionChange(); - } - updateVisualsOnDraftChanged(); - } - - @Override // From DraftMessageDataListener - public void onDraftAttachmentLimitReached(final DraftMessageData data) { - mBinding.ensureBound(data); - mHost.warnOfExceedingMessageLimit(false /* sending */, false /* tooManyVideos */); - } - - private void updateOnSelfSubscriptionChange() { - // Refresh the length filters according to the selected self's MmsConfig. - mComposeEditText.setFilters(new InputFilter[] { - new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId()) - .getMaxTextLimit()) }); - mComposeSubjectText.setFilters(new InputFilter[] { - new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId()) - .getMaxSubjectLength())}); - } - - @Override - public void onMediaItemsSelected(final Collection items) { - mBinding.getData().addAttachments(items); - announceMediaItemState(true /*isSelected*/); - } - - @Override - public void onMediaItemsUnselected(final MessagePartData item) { - mBinding.getData().removeAttachment(item); - announceMediaItemState(false /*isSelected*/); - } - - @Override - public void onPendingAttachmentAdded(final PendingAttachmentData pendingItem) { - mBinding.getData().addPendingAttachment(pendingItem, mBinding); - resumeComposeMessage(); - } - - private void announceMediaItemState(final boolean isSelected) { - final Resources res = getContext().getResources(); - final String announcement = isSelected ? res.getString( - R.string.mediapicker_gallery_item_selected_content_description) : - res.getString(R.string.mediapicker_gallery_item_unselected_content_description); - AccessibilityUtil.announceForAccessibilityCompat( - this, null, announcement); - } - - private void announceAttachmentState() { - if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) { - int attachmentCount = mBinding.getData().getReadOnlyAttachments().size() - + mBinding.getData().getReadOnlyPendingAttachments().size(); - final String announcement = getContext().getResources().getQuantityString( - R.plurals.attachment_changed_accessibility_announcement, - attachmentCount, attachmentCount); - AccessibilityUtil.announceForAccessibilityCompat( - this, null, announcement); - } - } - - @Override - public void resumeComposeMessage() { - mComposeEditText.requestFocus(); - mInputManager.showHideImeKeyboard(true, true); - announceAttachmentState(); - } - - public void clearAttachments() { - mBinding.getData().clearAttachments(mHost.getAttachmentsClearedFlags()); - mHost.onAttachmentsCleared(); - } - - public void requestDraftMessage(boolean clearLocalDraft) { - mBinding.getData().loadFromStorage(mBinding, null, clearLocalDraft); - } - - public void setDraftMessage(final MessageData message) { - mBinding.getData().loadFromStorage(mBinding, message, false); - } - - public void writeDraftMessage() { - final String messageText = mComposeEditText.getText().toString(); - mBinding.getData().setMessageText(messageText); - - final String subject = mComposeSubjectText.getText().toString(); - mBinding.getData().setMessageSubject(subject); - - mBinding.getData().saveToStorage(mBinding); - } - - private void updateConversationSelfId(final String selfId, final boolean notify) { - mBinding.getData().setSelfId(selfId, notify); - } - - private Uri getSelfSendButtonIconUri() { - final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri(); - if (overridenSelfUri != null) { - return overridenSelfUri; - } - final SubscriptionListEntry subscriptionListEntry = getSelfSubscriptionListEntry(); - - if (subscriptionListEntry != null) { - return subscriptionListEntry.selectedIconUri; - } - - // Fall back to default self-avatar in the base case. - final ParticipantData self = mConversationDataModel.getData().getDefaultSelfParticipant(); - return self == null ? null : AvatarUriUtil.createAvatarUri(self); - } - - private SubscriptionListEntry getSelfSubscriptionListEntry() { - return mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant( - mBinding.getData().getSelfId(), false /* excludeDefault */); - } - - private boolean isDataLoadedForMessageSend() { - // Check data loading prerequisites for sending a message. - return mConversationDataModel != null && mConversationDataModel.isBound() && - mConversationDataModel.getData().getParticipantsLoaded(); - } - - private void updateVisualsOnDraftChanged() { - final String messageText = mComposeEditText.getText().toString(); - final DraftMessageData draftMessageData = mBinding.getData(); - draftMessageData.setMessageText(messageText); - - final String subject = mComposeSubjectText.getText().toString(); - draftMessageData.setMessageSubject(subject); - if (!TextUtils.isEmpty(subject)) { - mSubjectView.setVisibility(View.VISIBLE); - } - - final boolean hasMessageText = (TextUtils.getTrimmedLength(messageText) > 0); - final boolean hasSubject = (TextUtils.getTrimmedLength(subject) > 0); - final boolean hasWorkingDraft = hasMessageText || hasSubject || - mBinding.getData().hasAttachments(); - - // Update the SMS text counter. - final int messageCount = draftMessageData.getNumMessagesToBeSent(); - final int codePointsRemaining = draftMessageData.getCodePointsRemainingInCurrentMessage(); - // Show the counter only if: - // - We are not in MMS mode - // - We are going to send more than one message OR we are getting close - boolean showCounter = false; - if (!draftMessageData.getIsMms() && (messageCount > 1 || - codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN)) { - showCounter = true; - } - - if (showCounter) { - // Update the remaining characters and number of messages required. - final String counterText = messageCount > 1 ? codePointsRemaining + " / " + - messageCount : String.valueOf(codePointsRemaining); - mCharCounter.setText(counterText); - mCharCounter.setVisibility(View.VISIBLE); - } else { - mCharCounter.setVisibility(View.INVISIBLE); - } - - // Update the send message button. Self icon uri might be null if self participant data - // and/or conversation metadata hasn't been loaded by the host. - final Uri selfSendButtonUri = getSelfSendButtonIconUri(); - int sendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR; - if (selfSendButtonUri != null) { - if (hasWorkingDraft && isDataLoadedForMessageSend()) { - UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null); - if (isOverriddenAvatarAGroup()) { - // If the host has overriden the avatar to show a group avatar where the - // send button sits, we have to hide the group avatar because it can be larger - // than the send button and pieces of the avatar will stick out from behind - // the send button. - UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null); - } - mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE); - sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON; - } else { - mSelfSendIcon.setImageResourceUri(selfSendButtonUri); - if (isOverriddenAvatarAGroup()) { - UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, VISIBLE, null); - } - UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null); - mMmsIndicator.setVisibility(INVISIBLE); - if (shouldShowSimSelector(mConversationDataModel.getData())) { - sendWidgetMode = SEND_WIDGET_MODE_SIM_SELECTOR; - } - } - } else { - mSelfSendIcon.setImageResourceUri(null); - } - - if (mSendWidgetMode != sendWidgetMode || sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR) { - setSendButtonAccessibility(sendWidgetMode); - mSendWidgetMode = sendWidgetMode; - } - - // Update the text hint on the message box depending on the attachment type. - final List attachments = draftMessageData.getReadOnlyAttachments(); - final int attachmentCount = attachments.size(); - if (attachmentCount == 0) { - final SubscriptionListEntry subscriptionListEntry = - mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant( - mBinding.getData().getSelfId(), false /* excludeDefault */); - if (subscriptionListEntry == null) { - mComposeEditText.setHint(R.string.compose_message_view_hint_text); - } else { - mComposeEditText.setHint(Html.fromHtml(getResources().getString( - R.string.compose_message_view_hint_text_multi_sim, - subscriptionListEntry.displayName))); - } - } else { - int type = -1; - for (final MessagePartData attachment : attachments) { - int newType; - if (attachment.isImage()) { - newType = ContentType.TYPE_IMAGE; - } else if (attachment.isAudio()) { - newType = ContentType.TYPE_AUDIO; - } else if (attachment.isVideo()) { - newType = ContentType.TYPE_VIDEO; - } else if (attachment.isVCard()) { - newType = ContentType.TYPE_VCARD; - } else { - newType = ContentType.TYPE_OTHER; - } - - if (type == -1) { - type = newType; - } else if (type != newType || type == ContentType.TYPE_OTHER) { - type = ContentType.TYPE_OTHER; - break; - } - } - - switch (type) { - case ContentType.TYPE_IMAGE: - mComposeEditText.setHint(getResources().getQuantityString( - R.plurals.compose_message_view_hint_text_photo, attachmentCount)); - break; - - case ContentType.TYPE_AUDIO: - mComposeEditText.setHint(getResources().getQuantityString( - R.plurals.compose_message_view_hint_text_audio, attachmentCount)); - break; - - case ContentType.TYPE_VIDEO: - mComposeEditText.setHint(getResources().getQuantityString( - R.plurals.compose_message_view_hint_text_video, attachmentCount)); - break; - - case ContentType.TYPE_VCARD: - mComposeEditText.setHint(getResources().getQuantityString( - R.plurals.compose_message_view_hint_text_vcard, attachmentCount)); - break; - - case ContentType.TYPE_OTHER: - mComposeEditText.setHint(getResources().getQuantityString( - R.plurals.compose_message_view_hint_text_attachments, attachmentCount)); - break; - - default: - Assert.fail("Unsupported attachment type!"); - break; - } - } - } - - private void setSendButtonAccessibility(final int sendWidgetMode) { - switch (sendWidgetMode) { - case SEND_WIDGET_MODE_SELF_AVATAR: - // No send button and no SIM selector; the self send button is no longer - // important for accessibility. - mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mSelfSendIcon.setContentDescription(null); - mSendButton.setVisibility(View.GONE); - setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SELF_AVATAR); - break; - - case SEND_WIDGET_MODE_SIM_SELECTOR: - mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - mSelfSendIcon.setContentDescription(getSimContentDescription()); - setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SIM_SELECTOR); - break; - - case SEND_WIDGET_MODE_SEND_BUTTON: - mMmsIndicator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mMmsIndicator.setContentDescription(null); - setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SEND_BUTTON); - break; - } - } - - private String getSimContentDescription() { - final SubscriptionListEntry sub = getSelfSubscriptionListEntry(); - if (sub != null) { - return getResources().getString( - R.string.sim_selector_button_content_description_with_selection, - sub.displayName); - } else { - return getResources().getString( - R.string.sim_selector_button_content_description); - } - } - - // Set accessibility traversal order of the components in the send widget. - private void setSendWidgetAccessibilityTraversalOrder(final int mode) { - if (OsUtil.isAtLeastL_MR1()) { - mAttachMediaButton.setAccessibilityTraversalBefore(R.id.compose_message_text); - switch (mode) { - case SEND_WIDGET_MODE_SIM_SELECTOR: - mComposeEditText.setAccessibilityTraversalBefore(R.id.self_send_icon); - break; - case SEND_WIDGET_MODE_SEND_BUTTON: - mComposeEditText.setAccessibilityTraversalBefore(R.id.send_message_button); - break; - default: - break; - } - } - } - - @Override - public void afterTextChanged(final Editable editable) { - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, - final int after) { - if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) { - hideSimSelector(); - } - } - - private void hideSimSelector() { - if (mInputManager.showHideSimSelector(false /* show */, true /* animate */)) { - // Now that the sim selector has been hidden, reshow the attachments if they - // have been hidden. - hideAttachmentsWhenShowingSims(false /*simPickerVisible*/); - } - } - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, - final int count) { - final BugleActionBarActivity activity = (mOriginalContext instanceof BugleActionBarActivity) - ? (BugleActionBarActivity) mOriginalContext : null; - if (activity != null && activity.getIsDestroyed()) { - LogUtil.v(LogUtil.BUGLE_TAG, "got onTextChanged after onDestroy"); - - // if we get onTextChanged after the activity is destroyed then, ah, wtf - // b/18176615 - // This appears to have occurred as the result of orientation change. - return; - } - - mBinding.ensureBound(); - updateVisualsOnDraftChanged(); - } - - @Override - public PlainTextEditText getComposeEditText() { - return mComposeEditText; - } - - public void displayPhoto(final Uri photoUri, final Rect imageBounds) { - mHost.displayPhoto(photoUri, imageBounds, true /* isDraft */); - } - - public void updateConversationSelfIdOnExternalChange(final String selfId) { - updateConversationSelfId(selfId, true /* notify */); - } - - /** - * The selfId of the conversation. As soon as the DraftMessageData successfully loads (i.e. - * getSelfId() is non-null), the selfId in DraftMessageData is treated as the sole source - * of truth for conversation self id since it reflects any pending self id change the user - * makes in the UI. - */ - public String getConversationSelfId() { - return mBinding.getData().getSelfId(); - } - - public void selectSim(SubscriptionListEntry subscriptionData) { - final String oldSelfId = getConversationSelfId(); - final String newSelfId = subscriptionData.selfParticipantId; - Assert.notNull(newSelfId); - // Don't attempt to change self if self hasn't been loaded, or if self hasn't changed. - if (oldSelfId == null || TextUtils.equals(oldSelfId, newSelfId)) { - return; - } - updateConversationSelfId(newSelfId, true /* notify */); - } - - public void hideAllComposeInputs(final boolean animate) { - mInputManager.hideAllInputs(animate); - } - - public void saveInputState(final Bundle outState) { - mInputManager.onSaveInputState(outState); - } - - public void resetMediaPickerState() { - mInputManager.resetMediaPickerState(); - } - - public boolean onBackPressed() { - return mInputManager.onBackPressed(); - } - - public boolean onNavigationUpPressed() { - return mInputManager.onNavigationUpPressed(); - } - - public boolean updateActionBar(final ActionBar actionBar) { - return mInputManager != null ? mInputManager.updateActionBar(actionBar) : false; - } - - public static boolean shouldShowSimSelector(final ConversationData convData) { - return OsUtil.isAtLeastL_MR1() && - convData.getSelfParticipantsCountExcludingDefault(true /* activeOnly */) > 1; - } - - public void sendMessageIgnoreMessageSizeLimit() { - sendMessageInternal(false /* checkMessageSize */); - } - - public void onAttachmentPreviewLongClicked() { - mHost.showAttachmentChooser(); - } - - @Override - public void onDraftAttachmentLoadFailed() { - mHost.notifyOfAttachmentLoadFailed(); - } - - private boolean isOverriddenAvatarAGroup() { - final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri(); - if (overridenSelfUri == null) { - return false; - } - return AvatarUriUtil.TYPE_GROUP_URI.equals(AvatarUriUtil.getAvatarType(overridenSelfUri)); - } - - @Override - public void setAccessibility(boolean enabled) { - if (enabled) { - mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - setSendButtonAccessibility(mSendWidgetMode); - } else { - mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java deleted file mode 100644 index 66310ea..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationActivity.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.text.TextUtils; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.contact.ContactPickerFragment; -import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost; -import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost; -import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost; -import com.android.messaging.ui.conversationlist.ConversationListActivity; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -public class ConversationActivity extends BugleActionBarActivity - implements ContactPickerFragmentHost, ConversationFragmentHost, - ConversationActivityUiStateHost { - public static final int FINISH_RESULT_CODE = 1; - private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate"; - - private ConversationActivityUiState mUiState; - - // Fragment transactions cannot be performed after onSaveInstanceState() has been called since - // it will cause state loss. We don't want to call commitAllowingStateLoss() since it's - // dangerous. Therefore, we note when instance state is saved and avoid performing UI state - // updates concerning fragments past that point. - private boolean mInstanceStateSaved; - - // Tracks whether onPause is called. - private boolean mIsPaused; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.conversation_activity); - - final Intent intent = getIntent(); - - // Do our best to restore UI state from saved instance state. - if (savedInstanceState != null) { - mUiState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY); - } else { - if (intent. - getBooleanExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, false)) { - // See the comment in BugleWidgetService.getViewMoreConversationsView() why this - // is unfortunately necessary. The Bugle desktop widget can display a list of - // conversations. When there are more conversations that can be displayed in - // the widget, the last item is a "More conversations" item. The way widgets - // are built, the list items can only go to a single fill-in intent which points - // to this ConversationActivity. When the user taps on "More conversations", we - // really want to go to the ConversationList. This code makes that possible. - finish(); - final Intent convListIntent = new Intent(this, ConversationListActivity.class); - convListIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(convListIntent); - return; - } - } - - // If saved instance state doesn't offer a clue, get the info from the intent. - if (mUiState == null) { - final String conversationId = intent.getStringExtra( - UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - mUiState = new ConversationActivityUiState(conversationId); - } - mUiState.setHost(this); - mInstanceStateSaved = false; - - // Don't animate UI state change for initial setup. - updateUiState(false /* animate */); - - // See if we're getting called from a widget to directly display an image or video - final String extraToDisplay = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI); - if (!TextUtils.isEmpty(extraToDisplay)) { - final String contentType = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE); - final Rect bounds = UiUtils.getMeasuredBoundsOnScreen( - findViewById(R.id.conversation_and_compose_container)); - if (ContentType.isImageType(contentType)) { - final Uri imagesUri = MessagingContentProvider.buildConversationImagesUri( - mUiState.getConversationId()); - UIIntents.get().launchFullScreenPhotoViewer( - this, Uri.parse(extraToDisplay), bounds, imagesUri); - } else if (ContentType.isVideoType(contentType)) { - UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay)); - } - } - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - // After onSaveInstanceState() is called, future changes to mUiState won't update the UI - // anymore, because fragment transactions are not allowed past this point. - // For an activity recreation due to orientation change, the saved instance state keeps - // using the in-memory copy of the UI state instead of writing it to parcel as an - // optimization, so the UI state values may still change in response to, for example, - // focus change from the framework, making mUiState and actual UI inconsistent. - // Therefore, save an exact "snapshot" (clone) of the UI state object to make sure the - // restored UI state ALWAYS matches the actual restored UI components. - outState.putParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY, mUiState.clone()); - mInstanceStateSaved = true; - } - - @Override - protected void onResume() { - super.onResume(); - - // we need to reset the mInstanceStateSaved flag since we may have just been restored from - // a previous onStop() instead of an onDestroy(). - mInstanceStateSaved = false; - mIsPaused = false; - } - - @Override - protected void onPause() { - super.onPause(); - mIsPaused = true; - } - - @Override - public void onWindowFocusChanged(final boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - final ConversationFragment conversationFragment = getConversationFragment(); - // When the screen is turned on, the last used activity gets resumed, but it gets - // window focus only after the lock screen is unlocked. - if (hasFocus && conversationFragment != null) { - conversationFragment.setConversationFocus(); - } - } - - @Override - public void onDisplayHeightChanged(final int heightSpecification) { - super.onDisplayHeightChanged(heightSpecification); - invalidateActionBar(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mUiState != null) { - mUiState.setHost(null); - } - } - - @Override - public void updateActionBar(final ActionBar actionBar) { - super.updateActionBar(actionBar); - final ConversationFragment conversation = getConversationFragment(); - final ContactPickerFragment contactPicker = getContactPicker(); - if (contactPicker != null && mUiState.shouldShowContactPickerFragment()) { - contactPicker.updateActionBar(actionBar); - } else if (conversation != null && mUiState.shouldShowConversationFragment()) { - conversation.updateActionBar(actionBar); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem menuItem) { - if (super.onOptionsItemSelected(menuItem)) { - return true; - } - if (menuItem.getItemId() == android.R.id.home) { - onNavigationUpPressed(); - return true; - } - return false; - } - - public void onNavigationUpPressed() { - // Let the conversation fragment handle the navigation up press. - final ConversationFragment conversationFragment = getConversationFragment(); - if (conversationFragment != null && conversationFragment.onNavigationUpPressed()) { - return; - } - onFinishCurrentConversation(); - } - - @Override - public void onBackPressed() { - // If action mode is active dismiss it - if (getActionMode() != null) { - dismissActionMode(); - return; - } - - // Let the conversation fragment handle the back press. - final ConversationFragment conversationFragment = getConversationFragment(); - if (conversationFragment != null && conversationFragment.onBackPressed()) { - return; - } - super.onBackPressed(); - } - - private ContactPickerFragment getContactPicker() { - return (ContactPickerFragment) getFragmentManager().findFragmentByTag( - ContactPickerFragment.FRAGMENT_TAG); - } - - private ConversationFragment getConversationFragment() { - return (ConversationFragment) getFragmentManager().findFragmentByTag( - ConversationFragment.FRAGMENT_TAG); - } - - @Override // From ContactPickerFragmentHost - public void onGetOrCreateNewConversation(final String conversationId) { - Assert.isTrue(conversationId != null); - mUiState.onGetOrCreateConversation(conversationId); - } - - @Override // From ContactPickerFragmentHost - public void onBackButtonPressed() { - onBackPressed(); - } - - @Override // From ContactPickerFragmentHost - public void onInitiateAddMoreParticipants() { - mUiState.onAddMoreParticipants(); - } - - - @Override - public void onParticipantCountChanged(final boolean canAddMoreParticipants) { - mUiState.onParticipantCountUpdated(canAddMoreParticipants); - } - - @Override // From ConversationFragmentHost - public void onStartComposeMessage() { - mUiState.onStartMessageCompose(); - } - - @Override // From ConversationFragmentHost - public void onConversationMetadataUpdated() { - invalidateActionBar(); - } - - @Override // From ConversationFragmentHost - public void onConversationMessagesUpdated(final int numberOfMessages) { - } - - @Override // From ConversationFragmentHost - public void onConversationParticipantDataLoaded(final int numberOfParticipants) { - } - - @Override // From ConversationFragmentHost - public boolean isActiveAndFocused() { - return !mIsPaused && hasWindowFocus(); - } - - @Override // From ConversationActivityUiStateListener - public void onConversationContactPickerUiStateChanged(final int oldState, final int newState, - final boolean animate) { - Assert.isTrue(oldState != newState); - updateUiState(animate); - } - - private void updateUiState(final boolean animate) { - if (mInstanceStateSaved || mIsPaused) { - return; - } - Assert.notNull(mUiState); - final Intent intent = getIntent(); - final String conversationId = mUiState.getConversationId(); - - final FragmentManager fragmentManager = getFragmentManager(); - final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - - final boolean needConversationFragment = mUiState.shouldShowConversationFragment(); - final boolean needContactPickerFragment = mUiState.shouldShowContactPickerFragment(); - ConversationFragment conversationFragment = getConversationFragment(); - - // Set up the conversation fragment. - if (needConversationFragment) { - Assert.notNull(conversationId); - if (conversationFragment == null) { - conversationFragment = new ConversationFragment(); - fragmentTransaction.add(R.id.conversation_fragment_container, - conversationFragment, ConversationFragment.FRAGMENT_TAG); - } - final MessageData draftData = intent.getParcelableExtra( - UIIntents.UI_INTENT_EXTRA_DRAFT_DATA); - if (!needContactPickerFragment) { - // Once the user has committed the audience,remove the draft data from the - // intent to prevent reuse - intent.removeExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA); - } - conversationFragment.setHost(this); - conversationFragment.setConversationInfo(this, conversationId, draftData); - } else if (conversationFragment != null) { - // Don't save draft to DB when removing conversation fragment and switching to - // contact picking mode. The draft is intended for the new group. - conversationFragment.suppressWriteDraft(); - fragmentTransaction.remove(conversationFragment); - } - - // Set up the contact picker fragment. - ContactPickerFragment contactPickerFragment = getContactPicker(); - if (needContactPickerFragment) { - if (contactPickerFragment == null) { - contactPickerFragment = new ContactPickerFragment(); - fragmentTransaction.add(R.id.contact_picker_fragment_container, - contactPickerFragment, ContactPickerFragment.FRAGMENT_TAG); - } - contactPickerFragment.setHost(this); - contactPickerFragment.setContactPickingMode(mUiState.getDesiredContactPickingMode(), - animate); - } else if (contactPickerFragment != null) { - fragmentTransaction.remove(contactPickerFragment); - } - - fragmentTransaction.commit(); - invalidateActionBar(); - } - - @Override - public void onFinishCurrentConversation() { - // Simply finish the current activity. The current design is to leave any empty - // conversations as is. - if (OsUtil.isAtLeastL()) { - finishAfterTransition(); - } else { - finish(); - } - } - - @Override - public boolean shouldResumeComposeMessage() { - return mUiState.shouldResumeComposeMessage(); - } - - @Override - protected void onActivityResult(final int requestCode, final int resultCode, - final Intent data) { - if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS && - resultCode == RESULT_OK) { - final ConversationFragment conversationFragment = getConversationFragment(); - if (conversationFragment != null) { - conversationFragment.onAttachmentChoosen(); - } else { - LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " + - "AttachmentChooserActivity!"); - } - } else if (resultCode == FINISH_RESULT_CODE) { - finish(); - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java b/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java deleted file mode 100644 index 1469c93..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.messaging.ui.contact.ContactPickerFragment; -import com.android.messaging.util.Assert; -import com.google.common.annotations.VisibleForTesting; - -/** - * Keeps track of the different UI states that the ConversationActivity may be in. This acts as - * a state machine which, based on different actions (e.g. onAddMoreParticipants), notifies the - * ConversationActivity about any state UI change so it can update the visuals. This class - * implements Parcelable and it's persisted across activity tear down and relaunch. - */ -public class ConversationActivityUiState implements Parcelable, Cloneable { - interface ConversationActivityUiStateHost { - void onConversationContactPickerUiStateChanged(int oldState, int newState, boolean animate); - } - - /*------ Overall UI states (conversation & contact picker) ------*/ - - /** Only a full screen conversation is showing. */ - public static final int STATE_CONVERSATION_ONLY = 1; - /** Only a full screen contact picker is showing asking user to pick the initial contact. */ - public static final int STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT = 2; - /** - * Only a full screen contact picker is showing asking user to pick more participants. This - * happens after the user picked the initial contact, and then decide to go back and add more. - */ - public static final int STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS = 3; - /** - * Only a full screen contact picker is showing asking user to pick more participants. However - * user has reached max number of conversation participants and can add no more. - */ - public static final int STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS = 4; - /** - * A hybrid mode where the conversation view + contact chips view are showing. This happens - * right after the user picked the initial contact for which a 1-1 conversation is fetched or - * created. - */ - public static final int STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW = 5; - - // The overall UI state of the ConversationActivity. - private int mConversationContactUiState; - - // The currently displayed conversation (if any). - private String mConversationId; - - // Indicates whether we should put focus in the compose message view when the - // ConversationFragment is attached. This is a transient state that's not persisted as - // part of the parcelable. - private boolean mPendingResumeComposeMessage = false; - - // The owner ConversationActivity. This is not parceled since the instance always change upon - // object reuse. - private ConversationActivityUiStateHost mHost; - - // Indicates the owning ConverastionActivity is in the process of updating its UI presentation - // to be in sync with the UI states. Outside of the UI updates, the UI states here should - // ALWAYS be consistent with the actual states of the activity. - private int mUiUpdateCount; - - /** - * Create a new instance with an initial conversation id. - */ - ConversationActivityUiState(final String conversationId) { - // The conversation activity may be initialized with only one of two states: - // Conversation-only (when there's a conversation id) or picking initial contact - // (when no conversation id is given). - mConversationId = conversationId; - mConversationContactUiState = conversationId == null ? - STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT : STATE_CONVERSATION_ONLY; - } - - public void setHost(final ConversationActivityUiStateHost host) { - mHost = host; - } - - public boolean shouldShowConversationFragment() { - return mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW || - mConversationContactUiState == STATE_CONVERSATION_ONLY; - } - - public boolean shouldShowContactPickerFragment() { - return mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS || - mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS || - mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT || - mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW; - } - - /** - * Returns whether there's a pending request to resume message compose (i.e. set focus to - * the compose message view and show the soft keyboard). If so, this request will be served - * when the conversation fragment get created and resumed. This happens when the user commits - * participant selection for a group conversation and goes back to the conversation fragment. - * Since conversation fragment creation happens asynchronously, we issue and track this - * pending request for it to be eventually fulfilled. - */ - public boolean shouldResumeComposeMessage() { - if (mPendingResumeComposeMessage) { - // This is a one-shot operation that just keeps track of the pending resume compose - // state. This is also a non-critical operation so we don't care about failure case. - mPendingResumeComposeMessage = false; - return true; - } - return false; - } - - public int getDesiredContactPickingMode() { - switch (mConversationContactUiState) { - case STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS: - return ContactPickerFragment.MODE_PICK_MORE_CONTACTS; - case STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS: - return ContactPickerFragment.MODE_PICK_MAX_PARTICIPANTS; - case STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT: - return ContactPickerFragment.MODE_PICK_INITIAL_CONTACT; - case STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW: - return ContactPickerFragment.MODE_CHIPS_ONLY; - default: - Assert.fail("Invalid contact picking mode for ConversationActivity!"); - return ContactPickerFragment.MODE_UNDEFINED; - } - } - - public String getConversationId() { - return mConversationId; - } - - /** - * Called whenever the contact picker fragment successfully fetched or created a conversation. - */ - public void onGetOrCreateConversation(final String conversationId) { - int newState = STATE_CONVERSATION_ONLY; - if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) { - newState = STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW; - } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS || - mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS) { - newState = STATE_CONVERSATION_ONLY; - } else { - // New conversation should only be created when we are in one of the contact picking - // modes. - Assert.fail("Invalid conversation activity state: can't create conversation!"); - } - mConversationId = conversationId; - performUiStateUpdate(newState, true); - } - - /** - * Called when the user started composing message. If we are in the hybrid chips state, we - * should commit to enter the conversation only state. - */ - public void onStartMessageCompose() { - // This cannot happen when we are in one of the full-screen contact picking states. - Assert.isTrue(mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT && - mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS && - mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS); - if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) { - performUiStateUpdate(STATE_CONVERSATION_ONLY, true); - } - } - - /** - * Called when the user initiated an action to add more participants in the hybrid state, - * namely clicking on the "add more participants" button or entered a new contact chip via - * auto-complete. - */ - public void onAddMoreParticipants() { - if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) { - mPendingResumeComposeMessage = true; - performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, true); - } else { - // This is only possible in the hybrid state. - Assert.fail("Invalid conversation activity state: can't add more participants!"); - } - } - - /** - * Called each time the number of participants is updated to check against the limit and - * update the ui state accordingly. - */ - public void onParticipantCountUpdated(final boolean canAddMoreParticipants) { - if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS - && !canAddMoreParticipants) { - performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS, false); - } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS - && canAddMoreParticipants) { - performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, false); - } - } - - private void performUiStateUpdate(final int conversationContactState, final boolean animate) { - // This starts one UI update cycle, during which we allow the conversation activity's - // UI presentation to be temporarily out of sync with the states here. - beginUiUpdate(); - - if (conversationContactState != mConversationContactUiState) { - final int oldState = mConversationContactUiState; - mConversationContactUiState = conversationContactState; - notifyOnOverallUiStateChanged(oldState, mConversationContactUiState, animate); - } - endUiUpdate(); - } - - private void notifyOnOverallUiStateChanged( - final int oldState, final int newState, final boolean animate) { - // Always verify state validity whenever we have a state change. - assertValidState(); - Assert.isTrue(isUiUpdateInProgress()); - - // Only do this if we are still attached to the host. mHost can be null if the host - // activity is already destroyed, but due to timing the contained UI components may still - // receive events such as focus change and trigger a callback to the Ui state. We'd like - // to guard against those cases. - if (mHost != null) { - mHost.onConversationContactPickerUiStateChanged(oldState, newState, animate); - } - } - - private void assertValidState() { - // Conversation id may be null IF AND ONLY IF the user is picking the initial contact to - // start a conversation. - Assert.isTrue((mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) == - (mConversationId == null)); - } - - private void beginUiUpdate() { - mUiUpdateCount++; - } - - private void endUiUpdate() { - if (--mUiUpdateCount < 0) { - Assert.fail("Unbalanced Ui updates!"); - } - } - - private boolean isUiUpdateInProgress() { - return mUiUpdateCount > 0; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeInt(mConversationContactUiState); - dest.writeString(mConversationId); - } - - private ConversationActivityUiState(final Parcel in) { - mConversationContactUiState = in.readInt(); - mConversationId = in.readString(); - - // Always verify state validity whenever we initialize states. - assertValidState(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public ConversationActivityUiState createFromParcel(final Parcel in) { - return new ConversationActivityUiState(in); - } - - @Override - public ConversationActivityUiState[] newArray(final int size) { - return new ConversationActivityUiState[size]; - } - }; - - @Override - protected ConversationActivityUiState clone() { - try { - return (ConversationActivityUiState) super.clone(); - } catch (CloneNotSupportedException e) { - Assert.fail("ConversationActivityUiState: failed to clone(). Is there a mutable " + - "reference?"); - } - return null; - } - - /** - * allows for overridding the internal UI state. Should never be called except by test code. - */ - @VisibleForTesting - void testSetUiState(final int uiState) { - mConversationContactUiState = uiState; - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java b/src/com/android/messaging/ui/conversation/ConversationFastScroller.java deleted file mode 100644 index b15f05a..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.graphics.drawable.StateListDrawable; -import android.os.Handler; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.AdapterDataObserver; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.util.StateSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroupOverlay; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.util.Dates; -import com.android.messaging.util.OsUtil; - -/** - * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within - * the conversation and allows quickly moving to another position by dragging the scrollbar thumb - * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the - * date/time of the first visible message at the current position. - */ -public class ConversationFastScroller extends RecyclerView.OnScrollListener implements - OnLayoutChangeListener, RecyclerView.OnItemTouchListener { - - /** - * Creates a {@link ConversationFastScroller} instance, attached to the provided - * {@link RecyclerView}. - * - * @param rv the conversation RecyclerView - * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or - * {@code POSITION_LEFT_SIDE}) - * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported - * (the feature requires Jellybean MR2 or newer) - */ - public static ConversationFastScroller addTo(RecyclerView rv, int position) { - if (OsUtil.isAtLeastJB_MR2()) { - return new ConversationFastScroller(rv, position); - } - return null; - } - - public static final int POSITION_RIGHT_SIDE = 0; - public static final int POSITION_LEFT_SIDE = 1; - - private static final int MIN_PAGES_TO_ENABLE = 7; - private static final int SHOW_ANIMATION_DURATION_MS = 150; - private static final int HIDE_ANIMATION_DURATION_MS = 300; - private static final int HIDE_DELAY_MS = 1500; - - private final Context mContext; - private final RecyclerView mRv; - private final ViewGroupOverlay mOverlay; - private final ImageView mTrackImageView; - private final ImageView mThumbImageView; - private final TextView mPreviewTextView; - - private final int mTrackWidth; - private final int mThumbHeight; - private final int mPreviewHeight; - private final int mPreviewMinWidth; - private final int mPreviewMarginTop; - private final int mPreviewMarginLeftRight; - private final int mTouchSlop; - - private final Rect mContainer = new Rect(); - private final Handler mHandler = new Handler(); - - // Whether to render the scrollbar on the right side (otherwise it'll be on the left). - private final boolean mPosRight; - - // Whether the scrollbar is currently visible (it may still be animating). - private boolean mVisible = false; - - // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped). - private boolean mPendingHide = false; - - // Whether the user is currently dragging the thumb up or down. - private boolean mDragging = false; - - // Animations responsible for hiding the scrollbar & preview. May be null. - private AnimatorSet mHideAnimation; - private ObjectAnimator mHidePreviewAnimation; - - private final Runnable mHideTrackRunnable = new Runnable() { - @Override - public void run() { - hide(true /* animate */); - mPendingHide = false; - } - }; - - private ConversationFastScroller(RecyclerView rv, int position) { - mContext = rv.getContext(); - mRv = rv; - mRv.addOnLayoutChangeListener(this); - mRv.addOnScrollListener(this); - mRv.addOnItemTouchListener(this); - mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() { - @Override - public void onChanged() { - updateScrollPos(); - } - }); - mPosRight = (position == POSITION_RIGHT_SIDE); - - // Cache the dimensions we'll need during layout - final Resources res = mContext.getResources(); - mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width); - mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height); - mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height); - mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width); - mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top); - mPreviewMarginLeftRight = res.getDimensionPixelOffset( - R.dimen.fastscroll_preview_margin_left_right); - mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop); - - final LayoutInflater inflator = LayoutInflater.from(mContext); - mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null); - mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null); - mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null); - - refreshConversationThemeColor(); - - // Add the fast scroll views to the overlay, so they are rendered above the list - mOverlay = rv.getOverlay(); - mOverlay.add(mTrackImageView); - mOverlay.add(mThumbImageView); - mOverlay.add(mPreviewTextView); - - hide(false /* animate */); - mPreviewTextView.setAlpha(0f); - } - - public void refreshConversationThemeColor() { - mPreviewTextView.setBackground( - ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight)); - if (OsUtil.isAtLeastL()) { - final StateListDrawable drawable = new StateListDrawable(); - drawable.addState(new int[]{ android.R.attr.state_pressed }, - ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */)); - drawable.addState(StateSet.WILD_CARD, - ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */)); - mThumbImageView.setImageDrawable(drawable); - } else { - // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted - // drawable (it's rendered in the filter base color, which is red), so fall back to - // just the regular (non-pressed) drawable. - mThumbImageView.setImageDrawable( - ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */)); - } - } - - @Override - public void onScrollStateChanged(final RecyclerView view, final int newState) { - if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - // Only show the scrollbar once the user starts scrolling - if (!mVisible && isEnabled()) { - show(); - } - cancelAnyPendingHide(); - } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) { - // Hide the scrollbar again after scrolling stops - hideAfterDelay(); - } - } - - private boolean isEnabled() { - final int range = mRv.computeVerticalScrollRange(); - final int extent = mRv.computeVerticalScrollExtent(); - - if (range == 0 || extent == 0) { - return false; // Conversation isn't long enough to scroll - } - // Only enable scrollbars for conversations long enough that they would require several - // flings to scroll through. - final float pages = (float) range / extent; - return (pages > MIN_PAGES_TO_ENABLE); - } - - private void show() { - if (mHideAnimation != null && mHideAnimation.isRunning()) { - mHideAnimation.cancel(); - } - // Slide the scrollbar in from the side - ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0); - ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0); - AnimatorSet animation = new AnimatorSet(); - animation.playTogether(trackSlide, thumbSlide); - animation.setDuration(SHOW_ANIMATION_DURATION_MS); - animation.start(); - - mVisible = true; - updateScrollPos(); - } - - private void hideAfterDelay() { - cancelAnyPendingHide(); - mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS); - mPendingHide = true; - } - - private void cancelAnyPendingHide() { - if (mPendingHide) { - mHandler.removeCallbacks(mHideTrackRunnable); - } - } - - private void hide(boolean animate) { - final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth; - if (animate) { - // Slide the scrollbar off to the side - ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, - hiddenTranslationX); - ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, - hiddenTranslationX); - mHideAnimation = new AnimatorSet(); - mHideAnimation.playTogether(trackSlide, thumbSlide); - mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS); - mHideAnimation.start(); - } else { - mTrackImageView.setTranslationX(hiddenTranslationX); - mThumbImageView.setTranslationX(hiddenTranslationX); - } - - mVisible = false; - } - - private void showPreview() { - if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) { - mHidePreviewAnimation.cancel(); - } - mPreviewTextView.setAlpha(1f); - } - - private void hidePreview() { - mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f); - mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS); - mHidePreviewAnimation.start(); - } - - @Override - public void onScrolled(final RecyclerView view, final int dx, final int dy) { - updateScrollPos(); - } - - private void updateScrollPos() { - if (!mVisible) { - return; - } - final int verticalScrollLength = mContainer.height() - mThumbHeight; - final int verticalScrollStart = mContainer.top + mThumbHeight / 2; - - final float scrollRatio = computeScrollRatio(); - final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio); - layoutThumb(thumbCenterY); - - if (mDragging) { - updatePreviewText(); - layoutPreview(thumbCenterY); - } - } - - /** - * Returns the current position in the conversation, as a value between 0 and 1, inclusive. - * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on. - */ - private float computeScrollRatio() { - final int range = mRv.computeVerticalScrollRange(); - final int extent = mRv.computeVerticalScrollExtent(); - int offset = mRv.computeVerticalScrollOffset(); - - if (range == 0 || extent == 0) { - // If the conversation doesn't scroll, we're at the bottom. - return 1.0f; - } - final int scrollRange = range - extent; - offset = Math.min(offset, scrollRange); - return offset / (float) scrollRange; - } - - private void updatePreviewText() { - final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager(); - final int pos = lm.findFirstVisibleItemPosition(); - if (pos == RecyclerView.NO_POSITION) { - return; - } - final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos); - if (vh == null) { - // This can happen if the messages update while we're dragging the thumb. - return; - } - final ConversationMessageView messageView = (ConversationMessageView) vh.itemView; - final ConversationMessageData messageData = messageView.getData(); - final long timestamp = messageData.getReceivedTimeStamp(); - final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp); - mPreviewTextView.setText(timestampText); - } - - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { - if (!mVisible) { - return false; - } - // If the user presses down on the scroll thumb, we'll start intercepting events from the - // RecyclerView so we can handle the move events while they're dragging it up/down. - final int action = e.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - if (isInsideThumb(e.getX(), e.getY())) { - startDrag(); - return true; - } - break; - case MotionEvent.ACTION_MOVE: - if (mDragging) { - return true; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (mDragging) { - cancelDrag(); - } - return false; - } - return false; - } - - private boolean isInsideThumb(float x, float y) { - final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop; - final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop; - - if (x < hitTargetLeft || x > hitTargetRight) { - return false; - } - if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) { - return false; - } - return true; - } - - @Override - public void onTouchEvent(RecyclerView rv, MotionEvent e) { - if (!mDragging) { - return; - } - final int action = e.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_MOVE: - handleDragMove(e.getY()); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - cancelDrag(); - break; - } - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - } - - private void startDrag() { - mDragging = true; - mThumbImageView.setPressed(true); - updateScrollPos(); - showPreview(); - cancelAnyPendingHide(); - } - - private void handleDragMove(float y) { - final int verticalScrollLength = mContainer.height() - mThumbHeight; - final int verticalScrollStart = mContainer.top + (mThumbHeight / 2); - - // Convert the desired position from px to a scroll position in the conversation. - float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength; - dragScrollRatio = Math.max(dragScrollRatio, 0.0f); - dragScrollRatio = Math.min(dragScrollRatio, 1.0f); - - // Scroll the RecyclerView to a new position. - final int itemCount = mRv.getAdapter().getItemCount(); - final int itemPos = (int)((itemCount - 1) * dragScrollRatio); - mRv.scrollToPosition(itemPos); - } - - private void cancelDrag() { - mDragging = false; - mThumbImageView.setPressed(false); - hidePreview(); - hideAfterDelay(); - } - - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (!mVisible) { - hide(false /* animate */); - } - // The container is the size of the RecyclerView that's visible on screen. We have to - // exclude the top padding, because it's usually hidden behind the conversation action bar. - mContainer.set(left, top + mRv.getPaddingTop(), right, bottom); - layoutTrack(); - updateScrollPos(); - } - - private void layoutTrack() { - int trackHeight = Math.max(0, mContainer.height()); - int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY); - int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY); - mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec); - - int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left; - int top = mContainer.top; - int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth); - int bottom = mContainer.bottom; - mTrackImageView.layout(left, top, right, bottom); - } - - private void layoutThumb(int centerY) { - int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY); - int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY); - mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec); - - int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left; - int top = centerY - (mThumbImageView.getHeight() / 2); - int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth); - int bottom = top + mThumbHeight; - mThumbImageView.layout(left, top, right, bottom); - } - - private void layoutPreview(int centerY) { - int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST); - int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY); - mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec); - - // Ensure that the preview bubble is at least as wide as it is tall - if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) { - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY); - mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec); - } - final int previewMinY = mContainer.top + mPreviewMarginTop; - - final int left, right; - if (mPosRight) { - right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight; - left = right - mPreviewTextView.getMeasuredWidth(); - } else { - left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight; - right = left + mPreviewTextView.getMeasuredWidth(); - } - - int bottom = centerY; - int top = bottom - mPreviewTextView.getMeasuredHeight(); - if (top < previewMinY) { - top = previewMinY; - bottom = top + mPreviewTextView.getMeasuredHeight(); - } - mPreviewTextView.layout(left, top, right, bottom); - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java deleted file mode 100644 index a6a191a..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationFragment.java +++ /dev/null @@ -1,1662 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.Manifest; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DownloadManager; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.database.Cursor; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Parcelable; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.text.TextUtils; -import android.view.ActionMode; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.InsertNewMessageAction; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.ConversationParticipantsData; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.ui.AttachmentPreview; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.ui.SnackBar; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.animation.PopupTransitionAnimation; -import com.android.messaging.ui.contact.AddContactsConfirmationDialog; -import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost; -import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost; -import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost; -import com.android.messaging.ui.mediapicker.MediaPicker; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ChangeDefaultSmsAppHelper; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.TextUtil; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * Shows a list of messages/parts comprising a conversation. - */ -public class ConversationFragment extends Fragment implements ConversationDataListener, - IComposeMessageViewHost, ConversationMessageViewHost, ConversationInputHost, - DraftMessageDataListener { - - public interface ConversationFragmentHost extends ImeUtil.ImeStateHost { - void onStartComposeMessage(); - void onConversationMetadataUpdated(); - boolean shouldResumeComposeMessage(); - void onFinishCurrentConversation(); - void invalidateActionBar(); - ActionMode startActionMode(ActionMode.Callback callback); - void dismissActionMode(); - ActionMode getActionMode(); - void onConversationMessagesUpdated(int numberOfMessages); - void onConversationParticipantDataLoaded(int numberOfParticipants); - boolean isActiveAndFocused(); - } - - public static final String FRAGMENT_TAG = "conversation"; - - static final int REQUEST_CHOOSE_ATTACHMENTS = 2; - private static final int JUMP_SCROLL_THRESHOLD = 15; - // We animate the message from draft to message list, if we the message doesn't show up in the - // list within this time limit, then we just do a fade in animation instead - public static final int MESSAGE_ANIMATION_MAX_WAIT = 500; - - private ComposeMessageView mComposeMessageView; - private RecyclerView mRecyclerView; - private ConversationMessageAdapter mAdapter; - private ConversationFastScroller mFastScroller; - - private View mConversationComposeDivider; - private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper; - - private String mConversationId; - // If the fragment receives a draft as part of the invocation this is set - private MessageData mIncomingDraft; - - // This binding keeps track of our associated ConversationData instance - // A binding should have the lifetime of the owning component, - // don't recreate, unbind and bind if you need new data - @VisibleForTesting - final Binding mBinding = BindingBase.createBinding(this); - - // Saved Instance State Data - only for temporal data which is nice to maintain but not - // critical for correctness. - private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY = "conversationViewState"; - private Parcelable mListState; - - private ConversationFragmentHost mHost; - - protected List mFilterResults; - - // The minimum scrolling distance between RecyclerView's scroll change event beyong which - // a fling motion is considered fast, in which case we'll delay load image attachments for - // perf optimization. - private int mFastFlingThreshold; - - // ConversationMessageView that is currently selected - private ConversationMessageView mSelectedMessage; - - // Attachment data for the attachment within the selected message that was long pressed - private MessagePartData mSelectedAttachment; - - // Normally, as soon as draft message is loaded, we trust the UI state held in - // ComposeMessageView to be the only source of truth (incl. the conversation self id). However, - // there can be external events that forces the UI state to change, such as SIM state changes - // or SIM auto-switching on receiving a message. This receiver is used to receive such - // local broadcast messages and reflect the change in the UI. - private final BroadcastReceiver mConversationSelfIdChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final String conversationId = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - final String selfId = - intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID); - Assert.notNull(conversationId); - Assert.notNull(selfId); - if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) { - mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId); - } - } - }; - - // Flag to prevent writing draft to DB on pause - private boolean mSuppressWriteDraft; - - // Indicates whether local draft should be cleared due to external draft changes that must - // be reloaded from db - private boolean mClearLocalDraft; - private ImmutableBindingRef mDraftMessageDataModel; - - private boolean isScrolledToBottom() { - if (mRecyclerView.getChildCount() == 0) { - return true; - } - final View lastView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); - int lastVisibleItem = ((LinearLayoutManager) mRecyclerView - .getLayoutManager()).findLastVisibleItemPosition(); - if (lastVisibleItem < 0) { - // If the recyclerView height is 0, then the last visible item position is -1 - // Try to compute the position of the last item, even though it's not visible - final long id = mRecyclerView.getChildItemId(lastView); - final RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForItemId(id); - if (holder != null) { - lastVisibleItem = holder.getAdapterPosition(); - } - } - final int totalItemCount = mRecyclerView.getAdapter().getItemCount(); - final boolean isAtBottom = (lastVisibleItem + 1 == totalItemCount); - return isAtBottom && lastView.getBottom() <= mRecyclerView.getHeight(); - } - - private void scrollToBottom(final boolean smoothScroll) { - if (mAdapter.getItemCount() > 0) { - scrollToPosition(mAdapter.getItemCount() - 1, smoothScroll); - } - } - - private int mScrollToDismissThreshold; - private final RecyclerView.OnScrollListener mListScrollListener = - new RecyclerView.OnScrollListener() { - // Keeps track of cumulative scroll delta during a scroll event, which we may use to - // hide the media picker & co. - private int mCumulativeScrollDelta; - private boolean mScrollToDismissHandled; - private boolean mWasScrolledToBottom = true; - private int mScrollState = RecyclerView.SCROLL_STATE_IDLE; - - @Override - public void onScrollStateChanged(final RecyclerView view, final int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - // Reset scroll states. - mCumulativeScrollDelta = 0; - mScrollToDismissHandled = false; - } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - mRecyclerView.getItemAnimator().endAnimations(); - } - mScrollState = newState; - } - - @Override - public void onScrolled(final RecyclerView view, final int dx, final int dy) { - if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING && - !mScrollToDismissHandled) { - mCumulativeScrollDelta += dy; - // Dismiss the keyboard only when the user scroll up (into the past). - if (mCumulativeScrollDelta < -mScrollToDismissThreshold) { - mComposeMessageView.hideAllComposeInputs(false /* animate */); - mScrollToDismissHandled = true; - } - } - if (mWasScrolledToBottom != isScrolledToBottom()) { - mConversationComposeDivider.animate().alpha(isScrolledToBottom() ? 0 : 1); - mWasScrolledToBottom = isScrolledToBottom(); - } - } - }; - - private final ActionMode.Callback mMessageActionModeCallback = new ActionMode.Callback() { - @Override - public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) { - if (mSelectedMessage == null) { - return false; - } - final ConversationMessageData data = mSelectedMessage.getData(); - final MenuInflater menuInflater = getActivity().getMenuInflater(); - menuInflater.inflate(R.menu.conversation_fragment_select_menu, menu); - menu.findItem(R.id.action_download).setVisible(data.getShowDownloadMessage()); - menu.findItem(R.id.action_send).setVisible(data.getShowResendMessage()); - - // ShareActionProvider does not work with ActionMode. So we use a normal menu item. - menu.findItem(R.id.share_message_menu).setVisible(data.getCanForwardMessage()); - menu.findItem(R.id.save_attachment).setVisible(mSelectedAttachment != null); - menu.findItem(R.id.forward_message_menu).setVisible(data.getCanForwardMessage()); - - // TODO: We may want to support copying attachments in the future, but it's - // unclear which attachment to pick when we make this context menu at the message level - // instead of the part level - menu.findItem(R.id.copy_text).setVisible(data.getCanCopyMessageToClipboard()); - - return true; - } - - @Override - public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) { - return true; - } - - @Override - public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) { - final ConversationMessageData data = mSelectedMessage.getData(); - final String messageId = data.getMessageId(); - switch (menuItem.getItemId()) { - case R.id.save_attachment: - if (OsUtil.hasStoragePermission()) { - final SaveAttachmentTask saveAttachmentTask = new SaveAttachmentTask( - getActivity()); - for (final MessagePartData part : data.getAttachments()) { - saveAttachmentTask.addAttachmentToSave(part.getContentUri(), - part.getContentType()); - } - if (saveAttachmentTask.getAttachmentCount() > 0) { - saveAttachmentTask.executeOnThreadPool(); - mHost.dismissActionMode(); - } - } else { - getActivity().requestPermissions( - new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); - } - return true; - case R.id.action_delete_message: - if (mSelectedMessage != null) { - deleteMessage(messageId); - } - return true; - case R.id.action_download: - if (mSelectedMessage != null) { - retryDownload(messageId); - mHost.dismissActionMode(); - } - return true; - case R.id.action_send: - if (mSelectedMessage != null) { - retrySend(messageId); - mHost.dismissActionMode(); - } - return true; - case R.id.copy_text: - Assert.isTrue(data.hasText()); - final ClipboardManager clipboard = (ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setPrimaryClip( - ClipData.newPlainText(null /* label */, data.getText())); - mHost.dismissActionMode(); - return true; - case R.id.details_menu: - MessageDetailsDialog.show( - getActivity(), data, mBinding.getData().getParticipants(), - mBinding.getData().getSelfParticipantById(data.getSelfParticipantId())); - mHost.dismissActionMode(); - return true; - case R.id.share_message_menu: - shareMessage(data); - mHost.dismissActionMode(); - return true; - case R.id.forward_message_menu: - // TODO: Currently we are forwarding one part at a time, instead of - // the entire message. Change this to forwarding the entire message when we - // use message-based cursor in conversation. - final MessageData message = mBinding.getData().createForwardedMessage(data); - UIIntents.get().launchForwardMessageActivity(getActivity(), message); - mHost.dismissActionMode(); - return true; - } - return false; - } - - private void shareMessage(final ConversationMessageData data) { - // Figure out what to share. - MessagePartData attachmentToShare = mSelectedAttachment; - // If the user long-pressed on the background, we will share the text (if any) - // or the first attachment. - if (mSelectedAttachment == null - && TextUtil.isAllWhitespace(data.getText())) { - final List attachments = data.getAttachments(); - if (attachments.size() > 0) { - attachmentToShare = attachments.get(0); - } - } - - final Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - if (attachmentToShare == null) { - shareIntent.putExtra(Intent.EXTRA_TEXT, data.getText()); - shareIntent.setType("text/plain"); - } else { - shareIntent.putExtra( - Intent.EXTRA_STREAM, attachmentToShare.getContentUri()); - shareIntent.setType(attachmentToShare.getContentType()); - } - final CharSequence title = getResources().getText(R.string.action_share); - startActivity(Intent.createChooser(shareIntent, title)); - } - - @Override - public void onDestroyActionMode(final ActionMode actionMode) { - selectMessage(null); - } - }; - - /** - * {@inheritDoc} from Fragment - */ - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mFastFlingThreshold = getResources().getDimensionPixelOffset( - R.dimen.conversation_fast_fling_threshold); - mAdapter = new ConversationMessageAdapter(getActivity(), null, this, - null, - // Sets the item click listener on the Recycler item views. - new View.OnClickListener() { - @Override - public void onClick(final View v) { - final ConversationMessageView messageView = (ConversationMessageView) v; - handleMessageClick(messageView); - } - }, - new View.OnLongClickListener() { - @Override - public boolean onLongClick(final View view) { - selectMessage((ConversationMessageView) view); - return true; - } - } - ); - } - - /** - * setConversationInfo() may be called before or after onCreate(). When a user initiate a - * conversation from compose, the ConversationActivity creates this fragment and calls - * setConversationInfo(), so it happens before onCreate(). However, when the activity is - * restored from saved instance state, the ConversationFragment is created automatically by - * the fragment, before ConversationActivity has a chance to call setConversationInfo(). Since - * the ability to start loading data depends on both methods being called, we need to start - * loading when onActivityCreated() is called, which is guaranteed to happen after both. - */ - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - // Delay showing the message list until the participant list is loaded. - mRecyclerView.setVisibility(View.INVISIBLE); - mBinding.ensureBound(); - mBinding.getData().init(getLoaderManager(), mBinding); - - // Build the input manager with all its required dependencies and pass it along to the - // compose message view. - final ConversationInputManager inputManager = new ConversationInputManager( - getActivity(), this, mComposeMessageView, mHost, getFragmentManagerToUse(), - mBinding, mComposeMessageView.getDraftDataModel(), savedInstanceState); - mComposeMessageView.setInputManager(inputManager); - mComposeMessageView.setConversationDataModel(BindingBase.createBindingReference(mBinding)); - mHost.invalidateActionBar(); - - mDraftMessageDataModel = - BindingBase.createBindingReference(mComposeMessageView.getDraftDataModel()); - mDraftMessageDataModel.getData().addListener(this); - } - - public void onAttachmentChoosen() { - // Attachment has been choosen in the AttachmentChooserActivity, so clear local draft - // and reload draft on resume. - mClearLocalDraft = true; - } - - private int getScrollToMessagePosition() { - final Activity activity = getActivity(); - if (activity == null) { - return -1; - } - - final Intent intent = activity.getIntent(); - if (intent == null) { - return -1; - } - - return intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1); - } - - private void clearScrollToMessagePosition() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - final Intent intent = activity.getIntent(); - if (intent == null) { - return; - } - intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1); - } - - private final Handler mHandler = new Handler(); - - /** - * {@inheritDoc} from Fragment - */ - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.conversation_fragment, container, false); - mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list); - final LinearLayoutManager manager = new LinearLayoutManager(getActivity()); - manager.setStackFromEnd(true); - manager.setReverseLayout(false); - mRecyclerView.setHasFixedSize(true); - mRecyclerView.setLayoutManager(manager); - mRecyclerView.setItemAnimator(new DefaultItemAnimator() { - private final List mAddAnimations = new ArrayList(); - private PopupTransitionAnimation mPopupTransitionAnimation; - - @Override - public boolean animateAdd(final ViewHolder holder) { - final ConversationMessageView view = - (ConversationMessageView) holder.itemView; - final ConversationMessageData data = view.getData(); - endAnimation(holder); - final long timeSinceSend = System.currentTimeMillis() - data.getReceivedTimeStamp(); - if (data.getReceivedTimeStamp() == - InsertNewMessageAction.getLastSentMessageTimestamp() && - !data.getIsIncoming() && - timeSinceSend < MESSAGE_ANIMATION_MAX_WAIT) { - final ConversationMessageBubbleView messageBubble = - (ConversationMessageBubbleView) view - .findViewById(R.id.message_content); - final Rect startRect = UiUtils.getMeasuredBoundsOnScreen(mComposeMessageView); - final View composeBubbleView = mComposeMessageView.findViewById( - R.id.compose_message_text); - final Rect composeBubbleRect = - UiUtils.getMeasuredBoundsOnScreen(composeBubbleView); - final AttachmentPreview attachmentView = - (AttachmentPreview) mComposeMessageView.findViewById( - R.id.attachment_draft_view); - final Rect attachmentRect = UiUtils.getMeasuredBoundsOnScreen(attachmentView); - if (attachmentView.getVisibility() == View.VISIBLE) { - startRect.top = attachmentRect.top; - } else { - startRect.top = composeBubbleRect.top; - } - startRect.top -= view.getPaddingTop(); - startRect.bottom = - composeBubbleRect.bottom; - startRect.left += view.getPaddingRight(); - - view.setAlpha(0); - mPopupTransitionAnimation = new PopupTransitionAnimation(startRect, view); - mPopupTransitionAnimation.setOnStartCallback(new Runnable() { - @Override - public void run() { - final int startWidth = composeBubbleRect.width(); - attachmentView.onMessageAnimationStart(); - messageBubble.kickOffMorphAnimation(startWidth, - messageBubble.findViewById(R.id.message_text_and_info) - .getMeasuredWidth()); - } - }); - mPopupTransitionAnimation.setOnStopCallback(new Runnable() { - @Override - public void run() { - view.setAlpha(1); - } - }); - mPopupTransitionAnimation.startAfterLayoutComplete(); - mAddAnimations.add(holder); - return true; - } else { - return super.animateAdd(holder); - } - } - - @Override - public void endAnimation(final ViewHolder holder) { - if (mAddAnimations.remove(holder)) { - holder.itemView.clearAnimation(); - } - super.endAnimation(holder); - } - - @Override - public void endAnimations() { - for (final ViewHolder holder : mAddAnimations) { - holder.itemView.clearAnimation(); - } - mAddAnimations.clear(); - if (mPopupTransitionAnimation != null) { - mPopupTransitionAnimation.cancel(); - } - super.endAnimations(); - } - }); - mRecyclerView.setAdapter(mAdapter); - - if (savedInstanceState != null) { - mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY); - } - - mConversationComposeDivider = view.findViewById(R.id.conversation_compose_divider); - mScrollToDismissThreshold = ViewConfiguration.get(getActivity()).getScaledTouchSlop(); - mRecyclerView.addOnScrollListener(mListScrollListener); - mFastScroller = ConversationFastScroller.addTo(mRecyclerView, - UiUtils.isRtlMode() ? ConversationFastScroller.POSITION_LEFT_SIDE : - ConversationFastScroller.POSITION_RIGHT_SIDE); - - mComposeMessageView = (ComposeMessageView) - view.findViewById(R.id.message_compose_view_container); - // Bind the compose message view to the DraftMessageData - mComposeMessageView.bind(DataModel.get().createDraftMessageData( - mBinding.getData().getConversationId()), this); - - return view; - } - - private void scrollToPosition(final int targetPosition, final boolean smoothScroll) { - if (smoothScroll) { - final int maxScrollDelta = JUMP_SCROLL_THRESHOLD; - - final LinearLayoutManager layoutManager = - (LinearLayoutManager) mRecyclerView.getLayoutManager(); - final int firstVisibleItemPosition = - layoutManager.findFirstVisibleItemPosition(); - final int delta = targetPosition - firstVisibleItemPosition; - final int intermediatePosition; - - if (delta > maxScrollDelta) { - intermediatePosition = Math.max(0, targetPosition - maxScrollDelta); - } else if (delta < -maxScrollDelta) { - final int count = layoutManager.getItemCount(); - intermediatePosition = Math.min(count - 1, targetPosition + maxScrollDelta); - } else { - intermediatePosition = -1; - } - if (intermediatePosition != -1) { - mRecyclerView.scrollToPosition(intermediatePosition); - } - mRecyclerView.smoothScrollToPosition(targetPosition); - } else { - mRecyclerView.scrollToPosition(targetPosition); - } - } - - private int getScrollPositionFromBottom() { - final LinearLayoutManager layoutManager = - (LinearLayoutManager) mRecyclerView.getLayoutManager(); - final int lastVisibleItem = - layoutManager.findLastVisibleItemPosition(); - return Math.max(mAdapter.getItemCount() - 1 - lastVisibleItem, 0); - } - - /** - * Display a photo using the Photoviewer component. - */ - @Override - public void displayPhoto(final Uri photoUri, final Rect imageBounds, final boolean isDraft) { - displayPhoto(photoUri, imageBounds, isDraft, mConversationId, getActivity()); - } - - public static void displayPhoto(final Uri photoUri, final Rect imageBounds, - final boolean isDraft, final String conversationId, final Activity activity) { - final Uri imagesUri = - isDraft ? MessagingContentProvider.buildDraftImagesUri(conversationId) - : MessagingContentProvider.buildConversationImagesUri(conversationId); - UIIntents.get().launchFullScreenPhotoViewer( - activity, photoUri, imageBounds, imagesUri); - } - - private void selectMessage(final ConversationMessageView messageView) { - selectMessage(messageView, null /* attachment */); - } - - private void selectMessage(final ConversationMessageView messageView, - final MessagePartData attachment) { - mSelectedMessage = messageView; - if (mSelectedMessage == null) { - mAdapter.setSelectedMessage(null); - mHost.dismissActionMode(); - mSelectedAttachment = null; - return; - } - mSelectedAttachment = attachment; - mAdapter.setSelectedMessage(messageView.getData().getMessageId()); - mHost.startActionMode(mMessageActionModeCallback); - } - - @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - if (mListState != null) { - outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState); - } - mComposeMessageView.saveInputState(outState); - } - - @Override - public void onResume() { - super.onResume(); - - if (mIncomingDraft == null) { - mComposeMessageView.requestDraftMessage(mClearLocalDraft); - } else { - mComposeMessageView.setDraftMessage(mIncomingDraft); - mIncomingDraft = null; - } - mClearLocalDraft = false; - - // On resume, check if there's a pending request for resuming message compose. This - // may happen when the user commits the contact selection for a group conversation and - // goes from compose back to the conversation fragment. - if (mHost.shouldResumeComposeMessage()) { - mComposeMessageView.resumeComposeMessage(); - } - - setConversationFocus(); - - // On resume, invalidate all message views to show the updated timestamp. - mAdapter.notifyDataSetChanged(); - - LocalBroadcastManager.getInstance(getActivity()).registerReceiver( - mConversationSelfIdChangeReceiver, - new IntentFilter(UIIntents.CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION)); - } - - void setConversationFocus() { - if (mHost.isActiveAndFocused()) { - mBinding.getData().setFocus(); - } - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (mHost.getActionMode() != null) { - return; - } - - inflater.inflate(R.menu.conversation_menu, menu); - - final ConversationData data = mBinding.getData(); - - // Disable the "people & options" item if we haven't loaded participants yet. - menu.findItem(R.id.action_people_and_options).setEnabled(data.getParticipantsLoaded()); - - // See if we can show add contact action. - final ParticipantData participant = data.getOtherParticipant(); - final boolean addContactActionVisible = (participant != null - && TextUtils.isEmpty(participant.getLookupKey())); - menu.findItem(R.id.action_add_contact).setVisible(addContactActionVisible); - - // See if we should show archive or unarchive. - final boolean isArchived = data.getIsArchived(); - menu.findItem(R.id.action_archive).setVisible(!isArchived); - menu.findItem(R.id.action_unarchive).setVisible(isArchived); - - // Conditionally enable the phone call button. - final boolean supportCallAction = (PhoneUtils.getDefault().isVoiceCapable() && - data.getParticipantPhoneNumber() != null); - menu.findItem(R.id.action_call).setVisible(supportCallAction); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_people_and_options: - Assert.isTrue(mBinding.getData().getParticipantsLoaded()); - UIIntents.get().launchPeopleAndOptionsActivity(getActivity(), mConversationId); - return true; - - case R.id.action_call: - final String phoneNumber = mBinding.getData().getParticipantPhoneNumber(); - Assert.notNull(phoneNumber); - final View targetView = getActivity().findViewById(R.id.action_call); - Point centerPoint; - if (targetView != null) { - final int screenLocation[] = new int[2]; - targetView.getLocationOnScreen(screenLocation); - final int centerX = screenLocation[0] + targetView.getWidth() / 2; - final int centerY = screenLocation[1] + targetView.getHeight() / 2; - centerPoint = new Point(centerX, centerY); - } else { - // In the overflow menu, just use the center of the screen. - final Display display = getActivity().getWindowManager().getDefaultDisplay(); - centerPoint = new Point(display.getWidth() / 2, display.getHeight() / 2); - } - UIIntents.get().launchPhoneCallActivity(getActivity(), phoneNumber, centerPoint); - return true; - - case R.id.action_archive: - mBinding.getData().archiveConversation(mBinding); - closeConversation(mConversationId); - return true; - - case R.id.action_unarchive: - mBinding.getData().unarchiveConversation(mBinding); - return true; - - case R.id.action_settings: - return true; - - case R.id.action_add_contact: - final ParticipantData participant = mBinding.getData().getOtherParticipant(); - Assert.notNull(participant); - final String destination = participant.getNormalizedDestination(); - final Uri avatarUri = AvatarUriUtil.createAvatarUri(participant); - (new AddContactsConfirmationDialog(getActivity(), avatarUri, destination)).show(); - return true; - - case R.id.action_delete: - if (isReadyForAction()) { - new AlertDialog.Builder(getActivity()) - .setTitle(getResources().getQuantityString( - R.plurals.delete_conversations_confirmation_dialog_title, 1)) - .setPositiveButton(R.string.delete_conversation_confirmation_button, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - deleteConversation(); - } - }) - .setNegativeButton(R.string.delete_conversation_decline_button, null) - .show(); - } else { - warnOfMissingActionConditions(false /*sending*/, - null /*commandToRunAfterActionConditionResolved*/); - } - return true; - } - return super.onOptionsItemSelected(item); - } - - /** - * {@inheritDoc} from ConversationDataListener - */ - @Override - public void onConversationMessagesCursorUpdated(final ConversationData data, - final Cursor cursor, final ConversationMessageData newestMessage, - final boolean isSync) { - mBinding.ensureBound(data); - - // This needs to be determined before swapping cursor, which may change the scroll state. - final boolean scrolledToBottom = isScrolledToBottom(); - final int positionFromBottom = getScrollPositionFromBottom(); - - // If participants not loaded, assume 1:1 since that's the 99% case - final boolean oneOnOne = - !data.getParticipantsLoaded() || data.getOtherParticipant() != null; - mAdapter.setOneOnOne(oneOnOne, false /* invalidate */); - - // Ensure that the action bar is updated with the current data. - invalidateOptionsMenu(); - final Cursor oldCursor = mAdapter.swapCursor(cursor); - - if (cursor != null && oldCursor == null) { - if (mListState != null) { - mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState); - // RecyclerView restores scroll states without triggering scroll change events, so - // we need to manually ensure that they are correctly handled. - mListScrollListener.onScrolled(mRecyclerView, 0, 0); - } - } - - if (isSync) { - // This is a message sync. Syncing messages changes cursor item count, which would - // implicitly change RV's scroll position. We'd like the RV to keep scrolled to the same - // relative position from the bottom (because RV is stacked from bottom), so that it - // stays relatively put as we sync. - final int position = Math.max(mAdapter.getItemCount() - 1 - positionFromBottom, 0); - scrollToPosition(position, false /* smoothScroll */); - } else if (newestMessage != null) { - // Show a snack bar notification if we are not scrolled to the bottom and the new - // message is an incoming message. - if (!scrolledToBottom && newestMessage.getIsIncoming()) { - // If the conversation activity is started but not resumed (if another dialog - // activity was in the foregrond), we will show a system notification instead of - // the snack bar. - if (mBinding.getData().isFocused()) { - UiUtils.showSnackBarWithCustomAction(getActivity(), - getView().getRootView(), - getString(R.string.in_conversation_notify_new_message_text), - SnackBar.Action.createCustomAction(new Runnable() { - @Override - public void run() { - scrollToBottom(true /* smoothScroll */); - mComposeMessageView.hideAllComposeInputs(false /* animate */); - } - }, - getString(R.string.in_conversation_notify_new_message_action)), - null /* interactions */, - SnackBar.Placement.above(mComposeMessageView)); - } - } else { - // We are either already scrolled to the bottom or this is an outgoing message, - // scroll to the bottom to reveal it. - // Don't smooth scroll if we were already at the bottom; instead, we scroll - // immediately so RecyclerView's view animation will take place. - scrollToBottom(!scrolledToBottom); - } - } - - if (cursor != null) { - mHost.onConversationMessagesUpdated(cursor.getCount()); - - // Are we coming from a widget click where we're told to scroll to a particular item? - final int scrollToPos = getScrollToMessagePosition(); - if (scrollToPos >= 0) { - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_TAG, "onConversationMessagesCursorUpdated " + - " scrollToPos: " + scrollToPos + - " cursorCount: " + cursor.getCount()); - } - scrollToPosition(scrollToPos, true /*smoothScroll*/); - clearScrollToMessagePosition(); - } - } - - mHost.invalidateActionBar(); - } - - /** - * {@inheritDoc} from ConversationDataListener - */ - @Override - public void onConversationMetadataUpdated(final ConversationData conversationData) { - mBinding.ensureBound(conversationData); - - if (mSelectedMessage != null && mSelectedAttachment != null) { - // We may have just sent a message and the temp attachment we selected is now gone. - // and it was replaced with some new attachment. Since we don't know which one it - // is we shouldn't reselect it (unless there is just one) In the multi-attachment - // case we would just deselect the message and allow the user to reselect, otherwise we - // may act on old temp data and may crash. - final List currentAttachments = mSelectedMessage.getData().getAttachments(); - if (currentAttachments.size() == 1) { - mSelectedAttachment = currentAttachments.get(0); - } else if (!currentAttachments.contains(mSelectedAttachment)) { - selectMessage(null); - } - } - // Ensure that the action bar is updated with the current data. - invalidateOptionsMenu(); - mHost.onConversationMetadataUpdated(); - mAdapter.notifyDataSetChanged(); - } - - public void setConversationInfo(final Context context, final String conversationId, - final MessageData draftData) { - // TODO: Eventually I would like the Factory to implement - // Factory.get().bindConversationData(mBinding, getActivity(), this, conversationId)); - if (!mBinding.isBound()) { - mConversationId = conversationId; - mIncomingDraft = draftData; - mBinding.bind(DataModel.get().createConversationData(context, this, conversationId)); - } else { - Assert.isTrue(TextUtils.equals(mBinding.getData().getConversationId(), conversationId)); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - // Unbind all the views that we bound to data - if (mComposeMessageView != null) { - mComposeMessageView.unbind(); - } - - // And unbind this fragment from its data - mBinding.unbind(); - mConversationId = null; - } - - void suppressWriteDraft() { - mSuppressWriteDraft = true; - } - - @Override - public void onPause() { - super.onPause(); - if (mComposeMessageView != null && !mSuppressWriteDraft) { - mComposeMessageView.writeDraftMessage(); - } - mSuppressWriteDraft = false; - mBinding.getData().unsetFocus(); - mListState = mRecyclerView.getLayoutManager().onSaveInstanceState(); - - LocalBroadcastManager.getInstance(getActivity()) - .unregisterReceiver(mConversationSelfIdChangeReceiver); - } - - @Override - public void onConfigurationChanged(final Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mRecyclerView.getItemAnimator().endAnimations(); - } - - // TODO: Remove isBound and replace it with ensureBound after b/15704674. - public boolean isBound() { - return mBinding.isBound(); - } - - private FragmentManager getFragmentManagerToUse() { - return OsUtil.isAtLeastJB_MR1() ? getChildFragmentManager() : getFragmentManager(); - } - - public MediaPicker getMediaPicker() { - return (MediaPicker) getFragmentManagerToUse().findFragmentByTag( - MediaPicker.FRAGMENT_TAG); - } - - @Override - public void sendMessage(final MessageData message) { - if (isReadyForAction()) { - if (ensureKnownRecipients()) { - // Merge the caption text from attachments into the text body of the messages - message.consolidateText(); - - mBinding.getData().sendMessage(mBinding, message); - mComposeMessageView.resetMediaPickerState(); - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: conv participants not loaded"); - } - } else { - warnOfMissingActionConditions(true /*sending*/, - new Runnable() { - @Override - public void run() { - sendMessage(message); - } - }); - } - } - - public void setHost(final ConversationFragmentHost host) { - mHost = host; - } - - public String getConversationName() { - return mBinding.getData().getConversationName(); - } - - @Override - public void onComposeEditTextFocused() { - mHost.onStartComposeMessage(); - } - - @Override - public void onAttachmentsCleared() { - // When attachments are removed, reset transient media picker state such as image selection. - mComposeMessageView.resetMediaPickerState(); - } - - /** - * Called to check if all conditions are nominal and a "go" for some action, such as deleting - * a message, that requires this app to be the default app. This is also a precondition - * required for sending a draft. - * @return true if all conditions are nominal and we're ready to send a message - */ - @Override - public boolean isReadyForAction() { - return UiUtils.isReadyForAction(); - } - - /** - * When there's some condition that prevents an operation, such as sending a message, - * call warnOfMissingActionConditions to put up a snackbar and allow the user to repair - * that condition. - * @param sending - true if we're called during a sending operation - * @param commandToRunAfterActionConditionResolved - a runnable to run after the user responds - * positively to the condition prompt and resolves the condition. If null, - * the user will be shown a toast to tap the send button again. - */ - @Override - public void warnOfMissingActionConditions(final boolean sending, - final Runnable commandToRunAfterActionConditionResolved) { - if (mChangeDefaultSmsAppHelper == null) { - mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper(); - } - mChangeDefaultSmsAppHelper.warnOfMissingActionConditions(sending, - commandToRunAfterActionConditionResolved, mComposeMessageView, - getView().getRootView(), - getActivity(), this); - } - - private boolean ensureKnownRecipients() { - final ConversationData conversationData = mBinding.getData(); - - if (!conversationData.getParticipantsLoaded()) { - // We can't tell yet whether or not we have an unknown recipient - return false; - } - - final ConversationParticipantsData participants = conversationData.getParticipants(); - for (final ParticipantData participant : participants) { - - - if (participant.isUnknownSender()) { - UiUtils.showToast(R.string.unknown_sender); - return false; - } - } - - return true; - } - - public void retryDownload(final String messageId) { - if (isReadyForAction()) { - mBinding.getData().downloadMessage(mBinding, messageId); - } else { - warnOfMissingActionConditions(false /*sending*/, - null /*commandToRunAfterActionConditionResolved*/); - } - } - - public void retrySend(final String messageId) { - if (isReadyForAction()) { - if (ensureKnownRecipients()) { - mBinding.getData().resendMessage(mBinding, messageId); - } - } else { - warnOfMissingActionConditions(true /*sending*/, - new Runnable() { - @Override - public void run() { - retrySend(messageId); - } - - }); - } - } - - void deleteMessage(final String messageId) { - if (isReadyForAction()) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.delete_message_confirmation_dialog_title) - .setMessage(R.string.delete_message_confirmation_dialog_text) - .setPositiveButton(R.string.delete_message_confirmation_button, - new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - mBinding.getData().deleteMessage(mBinding, messageId); - } - }) - .setNegativeButton(android.R.string.cancel, null); - if (OsUtil.isAtLeastJB_MR1()) { - builder.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(final DialogInterface dialog) { - mHost.dismissActionMode(); - } - }); - } else { - builder.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(final DialogInterface dialog) { - mHost.dismissActionMode(); - } - }); - } - builder.create().show(); - } else { - warnOfMissingActionConditions(false /*sending*/, - null /*commandToRunAfterActionConditionResolved*/); - mHost.dismissActionMode(); - } - } - - public void deleteConversation() { - if (isReadyForAction()) { - final Context context = getActivity(); - mBinding.getData().deleteConversation(mBinding); - closeConversation(mConversationId); - } else { - warnOfMissingActionConditions(false /*sending*/, - null /*commandToRunAfterActionConditionResolved*/); - } - } - - @Override - public void closeConversation(final String conversationId) { - if (TextUtils.equals(conversationId, mConversationId)) { - mHost.onFinishCurrentConversation(); - // TODO: Explicitly transition to ConversationList (or just go back)? - } - } - - @Override - public void onConversationParticipantDataLoaded(final ConversationData data) { - mBinding.ensureBound(data); - if (mBinding.getData().getParticipantsLoaded()) { - final boolean oneOnOne = mBinding.getData().getOtherParticipant() != null; - mAdapter.setOneOnOne(oneOnOne, true /* invalidate */); - - // refresh the options menu which will enable the "people & options" item. - invalidateOptionsMenu(); - - mHost.invalidateActionBar(); - - mRecyclerView.setVisibility(View.VISIBLE); - mHost.onConversationParticipantDataLoaded - (mBinding.getData().getNumberOfParticipantsExcludingSelf()); - } - } - - @Override - public void onSubscriptionListDataLoaded(final ConversationData data) { - mBinding.ensureBound(data); - mAdapter.notifyDataSetChanged(); - } - - @Override - public void promptForSelfPhoneNumber() { - if (mComposeMessageView != null) { - // Avoid bug in system which puts soft keyboard over dialog after orientation change - ImeUtil.hideSoftInput(getActivity(), mComposeMessageView); - } - - final FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction(); - final EnterSelfPhoneNumberDialog dialog = EnterSelfPhoneNumberDialog - .newInstance(getConversationSelfSubId()); - dialog.setTargetFragment(this, 0/*requestCode*/); - dialog.show(ft, null/*tag*/); - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - if (mChangeDefaultSmsAppHelper == null) { - mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper(); - } - mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(requestCode, resultCode, null); - } - - public boolean hasMessages() { - return mAdapter != null && mAdapter.getItemCount() > 0; - } - - public boolean onBackPressed() { - if (mComposeMessageView.onBackPressed()) { - return true; - } - return false; - } - - public boolean onNavigationUpPressed() { - return mComposeMessageView.onNavigationUpPressed(); - } - - @Override - public boolean onAttachmentClick(final ConversationMessageView messageView, - final MessagePartData attachment, final Rect imageBounds, final boolean longPress) { - if (longPress) { - selectMessage(messageView, attachment); - return true; - } else if (messageView.getData().getOneClickResendMessage()) { - handleMessageClick(messageView); - return true; - } - - if (attachment.isImage()) { - displayPhoto(attachment.getContentUri(), imageBounds, false /* isDraft */); - } - - if (attachment.isVCard()) { - UIIntents.get().launchVCardDetailActivity(getActivity(), attachment.getContentUri()); - } - - return false; - } - - private void handleMessageClick(final ConversationMessageView messageView) { - if (messageView != mSelectedMessage) { - final ConversationMessageData data = messageView.getData(); - final boolean isReadyToSend = isReadyForAction(); - if (data.getOneClickResendMessage()) { - // Directly resend the message on tap if it's failed - retrySend(data.getMessageId()); - selectMessage(null); - } else if (data.getShowResendMessage() && isReadyToSend) { - // Select the message to show the resend/download/delete options - selectMessage(messageView); - } else if (data.getShowDownloadMessage() && isReadyToSend) { - // Directly download the message on tap - retryDownload(data.getMessageId()); - } else { - // Let the toast from warnOfMissingActionConditions show and skip - // selecting - warnOfMissingActionConditions(false /*sending*/, - null /*commandToRunAfterActionConditionResolved*/); - selectMessage(null); - } - } else { - selectMessage(null); - } - } - - private static class AttachmentToSave { - public final Uri uri; - public final String contentType; - public Uri persistedUri; - - AttachmentToSave(final Uri uri, final String contentType) { - this.uri = uri; - this.contentType = contentType; - } - } - - public static class SaveAttachmentTask extends SafeAsyncTask { - private final Context mContext; - private final List mAttachmentsToSave = new ArrayList<>(); - - public SaveAttachmentTask(final Context context, final Uri contentUri, - final String contentType) { - mContext = context; - addAttachmentToSave(contentUri, contentType); - } - - public SaveAttachmentTask(final Context context) { - mContext = context; - } - - public void addAttachmentToSave(final Uri contentUri, final String contentType) { - mAttachmentsToSave.add(new AttachmentToSave(contentUri, contentType)); - } - - public int getAttachmentCount() { - return mAttachmentsToSave.size(); - } - - @Override - protected Void doInBackgroundTimed(final Void... arg) { - final File appDir = new File(Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_PICTURES), - mContext.getResources().getString(R.string.app_name)); - final File downloadDir = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS); - for (final AttachmentToSave attachment : mAttachmentsToSave) { - final boolean isImageOrVideo = ContentType.isImageType(attachment.contentType) - || ContentType.isVideoType(attachment.contentType); - attachment.persistedUri = UriUtil.persistContent(attachment.uri, - isImageOrVideo ? appDir : downloadDir, attachment.contentType); - } - return null; - } - - @Override - protected void onPostExecute(final Void result) { - int failCount = 0; - int imageCount = 0; - int videoCount = 0; - int otherCount = 0; - for (final AttachmentToSave attachment : mAttachmentsToSave) { - if (attachment.persistedUri == null) { - failCount++; - continue; - } - - // Inform MediaScanner about the new file - final Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - scanFileIntent.setData(attachment.persistedUri); - mContext.sendBroadcast(scanFileIntent); - - if (ContentType.isImageType(attachment.contentType)) { - imageCount++; - } else if (ContentType.isVideoType(attachment.contentType)) { - videoCount++; - } else { - otherCount++; - // Inform DownloadManager of the file so it will show in the "downloads" app - final DownloadManager downloadManager = - (DownloadManager) mContext.getSystemService( - Context.DOWNLOAD_SERVICE); - final String filePath = attachment.persistedUri.getPath(); - final File file = new File(filePath); - - if (file.exists()) { - downloadManager.addCompletedDownload( - file.getName() /* title */, - mContext.getString( - R.string.attachment_file_description) /* description */, - true /* isMediaScannerScannable */, - attachment.contentType, - file.getAbsolutePath(), - file.length(), - false /* showNotification */); - } - } - } - - String message; - if (failCount > 0) { - message = mContext.getResources().getQuantityString( - R.plurals.attachment_save_error, failCount, failCount); - } else { - int messageId = R.plurals.attachments_saved; - if (otherCount > 0) { - if (imageCount + videoCount == 0) { - messageId = R.plurals.attachments_saved_to_downloads; - } - } else { - if (videoCount == 0) { - messageId = R.plurals.photos_saved_to_album; - } else if (imageCount == 0) { - messageId = R.plurals.videos_saved_to_album; - } else { - messageId = R.plurals.attachments_saved_to_album; - } - } - final String appName = mContext.getResources().getString(R.string.app_name); - final int count = imageCount + videoCount + otherCount; - message = mContext.getResources().getQuantityString( - messageId, count, count, appName); - } - UiUtils.showToastAtBottom(message); - } - } - - private void invalidateOptionsMenu() { - final Activity activity = getActivity(); - // TODO: Add the supportInvalidateOptionsMenu call to the host activity. - if (activity == null || !(activity instanceof BugleActionBarActivity)) { - return; - } - ((BugleActionBarActivity) activity).supportInvalidateOptionsMenu(); - } - - @Override - public void setOptionsMenuVisibility(final boolean visible) { - setHasOptionsMenu(visible); - } - - @Override - public int getConversationSelfSubId() { - final String selfParticipantId = mComposeMessageView.getConversationSelfId(); - final ParticipantData self = mBinding.getData().getSelfParticipantById(selfParticipantId); - // If the self id or the self participant data hasn't been loaded yet, fallback to - // the default setting. - return self == null ? ParticipantData.DEFAULT_SELF_SUB_ID : self.getSubId(); - } - - @Override - public void invalidateActionBar() { - mHost.invalidateActionBar(); - } - - @Override - public void dismissActionMode() { - mHost.dismissActionMode(); - } - - @Override - public void selectSim(final SubscriptionListEntry subscriptionData) { - mComposeMessageView.selectSim(subscriptionData); - mHost.onStartComposeMessage(); - } - - @Override - public void onStartComposeMessage() { - mHost.onStartComposeMessage(); - } - - @Override - public SubscriptionListEntry getSubscriptionEntryForSelfParticipant( - final String selfParticipantId, final boolean excludeDefault) { - // TODO: ConversationMessageView is the only one using this. We should probably - // inject this into the view during binding in the ConversationMessageAdapter. - return mBinding.getData().getSubscriptionEntryForSelfParticipant(selfParticipantId, - excludeDefault); - } - - @Override - public SimSelectorView getSimSelectorView() { - return (SimSelectorView) getView().findViewById(R.id.sim_selector); - } - - @Override - public MediaPicker createMediaPicker() { - return new MediaPicker(getActivity()); - } - - @Override - public void notifyOfAttachmentLoadFailed() { - UiUtils.showToastAtBottom(R.string.attachment_load_failed_dialog_message); - } - - @Override - public void warnOfExceedingMessageLimit(final boolean sending, final boolean tooManyVideos) { - warnOfExceedingMessageLimit(sending, mComposeMessageView, mConversationId, - getActivity(), tooManyVideos); - } - - public static void warnOfExceedingMessageLimit(final boolean sending, - final ComposeMessageView composeMessageView, final String conversationId, - final Activity activity, final boolean tooManyVideos) { - final AlertDialog.Builder builder = - new AlertDialog.Builder(activity) - .setTitle(R.string.mms_attachment_limit_reached); - - if (sending) { - if (tooManyVideos) { - builder.setMessage(R.string.video_attachment_limit_exceeded_when_sending); - } else { - builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_sending) - .setNegativeButton(R.string.attachment_limit_reached_send_anyway, - new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int which) { - composeMessageView.sendMessageIgnoreMessageSizeLimit(); - } - }); - } - builder.setPositiveButton(android.R.string.ok, new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - showAttachmentChooser(conversationId, activity); - }}); - } else { - builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_composing) - .setPositiveButton(android.R.string.ok, null); - } - builder.show(); - } - - @Override - public void showAttachmentChooser() { - showAttachmentChooser(mConversationId, getActivity()); - } - - public static void showAttachmentChooser(final String conversationId, - final Activity activity) { - UIIntents.get().launchAttachmentChooserActivity(activity, - conversationId, REQUEST_CHOOSE_ATTACHMENTS); - } - - private void updateActionAndStatusBarColor(final ActionBar actionBar) { - final int themeColor = ConversationDrawables.get().getConversationThemeColor(); - actionBar.setBackgroundDrawable(new ColorDrawable(themeColor)); - UiUtils.setStatusBarColor(getActivity(), themeColor); - } - - public void updateActionBar(final ActionBar actionBar) { - if (mComposeMessageView == null || !mComposeMessageView.updateActionBar(actionBar)) { - updateActionAndStatusBarColor(actionBar); - // We update this regardless of whether or not the action bar is showing so that we - // don't get a race when it reappears. - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - actionBar.setDisplayHomeAsUpEnabled(true); - // Reset the back arrow to its default - actionBar.setHomeAsUpIndicator(0); - View customView = actionBar.getCustomView(); - if (customView == null || customView.getId() != R.id.conversation_title_container) { - final LayoutInflater inflator = (LayoutInflater) - getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - customView = inflator.inflate(R.layout.action_bar_conversation_name, null); - customView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - onBackPressed(); - } - }); - actionBar.setCustomView(customView); - } - - final TextView conversationNameView = - (TextView) customView.findViewById(R.id.conversation_title); - final String conversationName = getConversationName(); - if (!TextUtils.isEmpty(conversationName)) { - // RTL : To format conversation title if it happens to be phone numbers. - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - final String formattedName = bidiFormatter.unicodeWrap( - UiUtils.commaEllipsize( - conversationName, - conversationNameView.getPaint(), - conversationNameView.getWidth(), - getString(R.string.plus_one), - getString(R.string.plus_n)).toString(), - TextDirectionHeuristicsCompat.LTR); - conversationNameView.setText(formattedName); - // In case phone numbers are mixed in the conversation name, we need to vocalize it. - final String vocalizedConversationName = - AccessibilityUtil.getVocalizedPhoneNumber(getResources(), conversationName); - conversationNameView.setContentDescription(vocalizedConversationName); - getActivity().setTitle(conversationName); - } else { - final String appName = getString(R.string.app_name); - conversationNameView.setText(appName); - getActivity().setTitle(appName); - } - - // When conversation is showing and media picker is not showing, then hide the action - // bar only when we are in landscape mode, with IME open. - if (mHost.isImeOpen() && UiUtils.isLandscapeMode()) { - actionBar.hide(); - } else { - actionBar.show(); - } - } - } - - @Override - public boolean shouldShowSubjectEditor() { - return true; - } - - @Override - public boolean shouldHideAttachmentsWhenSimSelectorShown() { - return false; - } - - @Override - public void showHideSimSelector(final boolean show) { - // no-op for now - } - - @Override - public int getSimSelectorItemLayoutId() { - return R.layout.sim_selector_item_view; - } - - @Override - public Uri getSelfSendButtonIconUri() { - return null; // use default button icon uri - } - - @Override - public int overrideCounterColor() { - return -1; // don't override the color - } - - @Override - public void onAttachmentsChanged(final boolean haveAttachments) { - // no-op for now - } - - @Override - public void onDraftChanged(final DraftMessageData data, final int changeFlags) { - mDraftMessageDataModel.ensureBound(data); - // We're specifically only interested in ATTACHMENTS_CHANGED from the widget. Ignore - // other changes. When the widget changes an attachment, we need to reload the draft. - if (changeFlags == - (DraftMessageData.WIDGET_CHANGED | DraftMessageData.ATTACHMENTS_CHANGED)) { - mClearLocalDraft = true; // force a reload of the draft in onResume - } - } - - @Override - public void onDraftAttachmentLimitReached(final DraftMessageData data) { - // no-op for now - } - - @Override - public void onDraftAttachmentLoadFailed() { - // no-op for now - } - - @Override - public int getAttachmentsClearedFlags() { - return DraftMessageData.ATTACHMENTS_CHANGED; - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationInput.java b/src/com/android/messaging/ui/conversation/ConversationInput.java deleted file mode 100644 index bf60aa8..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationInput.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.os.Bundle; -import android.support.v7.app.ActionBar; - -/** - * The base class for a method of user input, e.g. media picker. - */ -public abstract class ConversationInput { - /** - * The host component where all input components are contained. This is typically the - * conversation fragment but may be mocked in test code. - */ - public interface ConversationInputBase { - boolean showHideInternal(final ConversationInput target, final boolean show, - final boolean animate); - String getInputStateKey(final ConversationInput input); - void beginUpdate(); - void handleOnShow(final ConversationInput target); - void endUpdate(); - } - - protected boolean mShowing; - protected ConversationInputBase mConversationInputBase; - - public abstract boolean show(boolean animate); - public abstract boolean hide(boolean animate); - - public ConversationInput(ConversationInputBase baseHost, final boolean isShowing) { - mConversationInputBase = baseHost; - mShowing = isShowing; - } - - public boolean onBackPressed() { - if (mShowing) { - mConversationInputBase.showHideInternal(this, false /* show */, true /* animate */); - return true; - } - return false; - } - - public boolean onNavigationUpPressed() { - return false; - } - - /** - * Toggle the visibility of this view. - * @param animate - * @return true if the view is now shown, false if it now hidden - */ - public boolean toggle(final boolean animate) { - mConversationInputBase.showHideInternal(this, !mShowing /* show */, true /* animate */); - return mShowing; - } - - public void saveState(final Bundle savedState) { - savedState.putBoolean(mConversationInputBase.getInputStateKey(this), mShowing); - } - - public void restoreState(final Bundle savedState) { - // Things are hidden by default, so only handle show. - if (savedState.getBoolean(mConversationInputBase.getInputStateKey(this))) { - mConversationInputBase.showHideInternal(this, true /* show */, false /* animate */); - } - } - - public boolean updateActionBar(final ActionBar actionBar) { - return false; - } - - /** - * Update our visibility flag in response to visibility change, both for actions - * initiated by this class (through the show/hide methods), and for external changes - * tracked by event listeners (e.g. ImeStateObserver, MediaPickerListener). As part of - * handling an input showing, we will hide all other inputs to ensure they are mutually - * exclusive. - */ - protected void onVisibilityChanged(final boolean visible) { - if (mShowing != visible) { - mConversationInputBase.beginUpdate(); - mShowing = visible; - if (visible) { - mConversationInputBase.handleOnShow(this); - } - mConversationInputBase.endUpdate(); - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationInputManager.java b/src/com/android/messaging/ui/conversation/ConversationInputManager.java deleted file mode 100644 index e10abe7..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationInputManager.java +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.app.FragmentManager; -import android.content.Context; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.widget.EditText; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.ui.mediapicker.MediaPicker; -import com.android.messaging.ui.mediapicker.MediaPicker.MediaPickerListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.ImeUtil.ImeStateHost; -import com.google.common.annotations.VisibleForTesting; - -import java.util.Collection; - -/** - * Manages showing/hiding/persisting different mutually exclusive UI components nested in - * ConversationFragment that take user inputs, i.e. media picker, SIM selector and - * IME keyboard (the IME keyboard is not owned by Bugle, but we try to model it the same way - * as the other components). - */ -public class ConversationInputManager implements ConversationInput.ConversationInputBase { - /** - * The host component where all input components are contained. This is typically the - * conversation fragment but may be mocked in test code. - */ - public interface ConversationInputHost extends DraftMessageSubscriptionDataProvider { - void invalidateActionBar(); - void setOptionsMenuVisibility(boolean visible); - void dismissActionMode(); - void selectSim(SubscriptionListEntry subscriptionData); - void onStartComposeMessage(); - SimSelectorView getSimSelectorView(); - MediaPicker createMediaPicker(); - void showHideSimSelector(boolean show); - int getSimSelectorItemLayoutId(); - } - - /** - * The "sink" component where all inputs components will direct the user inputs to. This is - * typically the ComposeMessageView but may be mocked in test code. - */ - public interface ConversationInputSink { - void onMediaItemsSelected(Collection items); - void onMediaItemsUnselected(MessagePartData item); - void onPendingAttachmentAdded(PendingAttachmentData pendingItem); - void resumeComposeMessage(); - EditText getComposeEditText(); - void setAccessibility(boolean enabled); - } - - private final ConversationInputHost mHost; - private final ConversationInputSink mSink; - - /** Dependencies injected from the host during construction */ - private final FragmentManager mFragmentManager; - private final Context mContext; - private final ImeStateHost mImeStateHost; - private final ImmutableBindingRef mConversationDataModel; - private final ImmutableBindingRef mDraftDataModel; - - private final ConversationInput[] mInputs; - private final ConversationMediaPicker mMediaInput; - private final ConversationSimSelector mSimInput; - private final ConversationImeKeyboard mImeInput; - private int mUpdateCount; - - private final ImeUtil.ImeStateObserver mImeStateObserver = new ImeUtil.ImeStateObserver() { - @Override - public void onImeStateChanged(final boolean imeOpen) { - mImeInput.onVisibilityChanged(imeOpen); - } - }; - - private final ConversationDataListener mDataListener = new SimpleConversationDataListener() { - @Override - public void onConversationParticipantDataLoaded(ConversationData data) { - mConversationDataModel.ensureBound(data); - } - - @Override - public void onSubscriptionListDataLoaded(ConversationData data) { - mConversationDataModel.ensureBound(data); - mSimInput.onSubscriptionListDataLoaded(data.getSubscriptionListData()); - } - }; - - public ConversationInputManager( - final Context context, - final ConversationInputHost host, - final ConversationInputSink sink, - final ImeStateHost imeStateHost, - final FragmentManager fm, - final BindingBase conversationDataModel, - final BindingBase draftDataModel, - final Bundle savedState) { - mHost = host; - mSink = sink; - mFragmentManager = fm; - mContext = context; - mImeStateHost = imeStateHost; - mConversationDataModel = BindingBase.createBindingReference(conversationDataModel); - mDraftDataModel = BindingBase.createBindingReference(draftDataModel); - - // Register listeners on dependencies. - mImeStateHost.registerImeStateObserver(mImeStateObserver); - mConversationDataModel.getData().addConversationDataListener(mDataListener); - - // Initialize the inputs - mMediaInput = new ConversationMediaPicker(this); - mSimInput = new SimSelector(this); - mImeInput = new ConversationImeKeyboard(this, mImeStateHost.isImeOpen()); - mInputs = new ConversationInput[] { mMediaInput, mSimInput, mImeInput }; - - if (savedState != null) { - for (int i = 0; i < mInputs.length; i++) { - mInputs[i].restoreState(savedState); - } - } - updateHostOptionsMenu(); - } - - public void onDetach() { - mImeStateHost.unregisterImeStateObserver(mImeStateObserver); - // Don't need to explicitly unregister for data model events. It will unregister all - // listeners automagically on unbind. - } - - public void onSaveInputState(final Bundle savedState) { - for (int i = 0; i < mInputs.length; i++) { - mInputs[i].saveState(savedState); - } - } - - @Override - public String getInputStateKey(final ConversationInput input) { - return input.getClass().getCanonicalName() + "_savedstate_"; - } - - public boolean onBackPressed() { - for (int i = 0; i < mInputs.length; i++) { - if (mInputs[i].onBackPressed()) { - return true; - } - } - return false; - } - - public boolean onNavigationUpPressed() { - for (int i = 0; i < mInputs.length; i++) { - if (mInputs[i].onNavigationUpPressed()) { - return true; - } - } - return false; - } - - public void resetMediaPickerState() { - mMediaInput.resetViewHolderState(); - } - - public void showHideMediaPicker(final boolean show, final boolean animate) { - showHideInternal(mMediaInput, show, animate); - } - - /** - * Show or hide the sim selector - * @param show visibility - * @param animate whether to animate the change in visibility - * @return true if the state of the visibility was changed - */ - public boolean showHideSimSelector(final boolean show, final boolean animate) { - return showHideInternal(mSimInput, show, animate); - } - - public void showHideImeKeyboard(final boolean show, final boolean animate) { - showHideInternal(mImeInput, show, animate); - } - - public void hideAllInputs(final boolean animate) { - beginUpdate(); - for (int i = 0; i < mInputs.length; i++) { - showHideInternal(mInputs[i], false, animate); - } - endUpdate(); - } - - /** - * Toggle the visibility of the sim selector. - * @param animate - * @param subEntry - * @return true if the view is now shown, false if it now hidden - */ - public boolean toggleSimSelector(final boolean animate, final SubscriptionListEntry subEntry) { - mSimInput.setSelected(subEntry); - return mSimInput.toggle(animate); - } - - public boolean updateActionBar(final ActionBar actionBar) { - for (int i = 0; i < mInputs.length; i++) { - if (mInputs[i].mShowing) { - return mInputs[i].updateActionBar(actionBar); - } - } - return false; - } - - @VisibleForTesting - boolean isMediaPickerVisible() { - return mMediaInput.mShowing; - } - - @VisibleForTesting - boolean isSimSelectorVisible() { - return mSimInput.mShowing; - } - - @VisibleForTesting - boolean isImeKeyboardVisible() { - return mImeInput.mShowing; - } - - @VisibleForTesting - void testNotifyImeStateChanged(final boolean imeOpen) { - mImeStateObserver.onImeStateChanged(imeOpen); - } - - /** - * returns true if the state of the visibility was actually changed - */ - @Override - public boolean showHideInternal(final ConversationInput target, final boolean show, - final boolean animate) { - if (!mConversationDataModel.isBound()) { - return false; - } - - if (target.mShowing == show) { - return false; - } - beginUpdate(); - boolean success; - if (!show) { - success = target.hide(animate); - } else { - success = target.show(animate); - } - - if (success) { - target.onVisibilityChanged(show); - } - endUpdate(); - return true; - } - - @Override - public void handleOnShow(final ConversationInput target) { - if (!mConversationDataModel.isBound()) { - return; - } - beginUpdate(); - - // All inputs are mutually exclusive. Showing one will hide everything else. - // The one exception, is that the keyboard and location media chooser can be open at the - // time to enable searching within that chooser - for (int i = 0; i < mInputs.length; i++) { - final ConversationInput currInput = mInputs[i]; - if (currInput != target) { - // TODO : If there's more exceptions we will want to make this more - // generic - if (currInput instanceof ConversationMediaPicker && - target instanceof ConversationImeKeyboard && - mMediaInput.getExistingOrCreateMediaPicker() != null && - mMediaInput.getExistingOrCreateMediaPicker().canShowIme()) { - // Allow the keyboard and location mediaPicker to be open at the same time, - // but ensure the media picker is full screen to allow enough room - mMediaInput.getExistingOrCreateMediaPicker().setFullScreen(true); - continue; - } - showHideInternal(currInput, false /* show */, false /* animate */); - } - } - // Always dismiss action mode on show. - mHost.dismissActionMode(); - // Invoking any non-keyboard input UI is treated as starting message compose. - if (target != mImeInput) { - mHost.onStartComposeMessage(); - } - endUpdate(); - } - - @Override - public void beginUpdate() { - mUpdateCount++; - } - - @Override - public void endUpdate() { - Assert.isTrue(mUpdateCount > 0); - if (--mUpdateCount == 0) { - // Always try to update the host action bar after every update cycle. - mHost.invalidateActionBar(); - } - } - - private void updateHostOptionsMenu() { - mHost.setOptionsMenuVisibility(!mMediaInput.isOpen()); - } - - /** - * Manages showing/hiding the media picker in conversation. - */ - private class ConversationMediaPicker extends ConversationInput { - public ConversationMediaPicker(ConversationInputBase baseHost) { - super(baseHost, false); - } - - private MediaPicker mMediaPicker; - - @Override - public boolean show(boolean animate) { - if (mMediaPicker == null) { - mMediaPicker = getExistingOrCreateMediaPicker(); - setConversationThemeColor(ConversationDrawables.get().getConversationThemeColor()); - mMediaPicker.setSubscriptionDataProvider(mHost); - mMediaPicker.setDraftMessageDataModel(mDraftDataModel); - mMediaPicker.setListener(new MediaPickerListener() { - @Override - public void onOpened() { - handleStateChange(); - } - - @Override - public void onFullScreenChanged(boolean fullScreen) { - // When we're full screen, we want to disable accessibility on the - // ComposeMessageView controls (attach button, message input, sim chooser) - // that are hiding underneath the action bar. - mSink.setAccessibility(!fullScreen /*enabled*/); - handleStateChange(); - } - - @Override - public void onDismissed() { - // Re-enable accessibility on all controls now that the media picker is - // going away. - mSink.setAccessibility(true /*enabled*/); - handleStateChange(); - } - - private void handleStateChange() { - onVisibilityChanged(isOpen()); - mHost.invalidateActionBar(); - updateHostOptionsMenu(); - } - - @Override - public void onItemsSelected(final Collection items, - final boolean resumeCompose) { - mSink.onMediaItemsSelected(items); - mHost.invalidateActionBar(); - if (resumeCompose) { - mSink.resumeComposeMessage(); - } - } - - @Override - public void onItemUnselected(final MessagePartData item) { - mSink.onMediaItemsUnselected(item); - mHost.invalidateActionBar(); - } - - @Override - public void onConfirmItemSelection() { - mSink.resumeComposeMessage(); - } - - @Override - public void onPendingItemAdded(final PendingAttachmentData pendingItem) { - mSink.onPendingAttachmentAdded(pendingItem); - } - - @Override - public void onChooserSelected(final int chooserIndex) { - mHost.invalidateActionBar(); - mHost.dismissActionMode(); - } - }); - } - - mMediaPicker.open(MediaPicker.MEDIA_TYPE_DEFAULT, animate); - - return isOpen(); - } - - @Override - public boolean hide(boolean animate) { - if (mMediaPicker != null) { - mMediaPicker.dismiss(animate); - } - return !isOpen(); - } - - public void resetViewHolderState() { - if (mMediaPicker != null) { - mMediaPicker.resetViewHolderState(); - } - } - - public void setConversationThemeColor(final int themeColor) { - if (mMediaPicker != null) { - mMediaPicker.setConversationThemeColor(themeColor); - } - } - - private boolean isOpen() { - return (mMediaPicker != null && mMediaPicker.isOpen()); - } - - private MediaPicker getExistingOrCreateMediaPicker() { - if (mMediaPicker != null) { - return mMediaPicker; - } - MediaPicker mediaPicker = (MediaPicker) - mFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG); - if (mediaPicker == null) { - mediaPicker = mHost.createMediaPicker(); - if (mediaPicker == null) { - return null; // this use of ComposeMessageView doesn't support media picking - } - mFragmentManager.beginTransaction().replace( - R.id.mediapicker_container, - mediaPicker, - MediaPicker.FRAGMENT_TAG).commit(); - } - return mediaPicker; - } - - @Override - public boolean updateActionBar(ActionBar actionBar) { - if (isOpen()) { - mMediaPicker.updateActionBar(actionBar); - return true; - } - return false; - } - - @Override - public boolean onNavigationUpPressed() { - if (isOpen() && mMediaPicker.isFullScreen()) { - return onBackPressed(); - } - return super.onNavigationUpPressed(); - } - - public boolean onBackPressed() { - if (mMediaPicker != null && mMediaPicker.onBackPressed()) { - return true; - } - return super.onBackPressed(); - } - } - - /** - * Manages showing/hiding the SIM selector in conversation. - */ - private class SimSelector extends ConversationSimSelector { - public SimSelector(ConversationInputBase baseHost) { - super(baseHost); - } - - @Override - protected SimSelectorView getSimSelectorView() { - return mHost.getSimSelectorView(); - } - - @Override - public int getSimSelectorItemLayoutId() { - return mHost.getSimSelectorItemLayoutId(); - } - - @Override - protected void selectSim(SubscriptionListEntry item) { - mHost.selectSim(item); - } - - @Override - public boolean show(boolean animate) { - final boolean result = super.show(animate); - mHost.showHideSimSelector(true /*show*/); - return result; - } - - @Override - public boolean hide(boolean animate) { - final boolean result = super.hide(animate); - mHost.showHideSimSelector(false /*show*/); - return result; - } - } - - /** - * Manages showing/hiding the IME keyboard in conversation. - */ - private class ConversationImeKeyboard extends ConversationInput { - public ConversationImeKeyboard(ConversationInputBase baseHost, final boolean isShowing) { - super(baseHost, isShowing); - } - - @Override - public boolean show(boolean animate) { - ImeUtil.get().showImeKeyboard(mContext, mSink.getComposeEditText()); - return true; - } - - @Override - public boolean hide(boolean animate) { - ImeUtil.get().hideImeKeyboard(mContext, mSink.getComposeEditText()); - return true; - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java deleted file mode 100644 index 2748fff..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.CursorRecyclerAdapter; -import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader; -import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost; -import com.android.messaging.util.Assert; - -import java.util.HashSet; -import java.util.List; - -/** - * Provides an interface to expose Conversation Message Cursor data to a UI widget like a - * RecyclerView. - */ -public class ConversationMessageAdapter extends - CursorRecyclerAdapter { - - private final ConversationMessageViewHost mHost; - private final AsyncImageViewDelayLoader mImageViewDelayLoader; - private final View.OnClickListener mViewClickListener; - private final View.OnLongClickListener mViewLongClickListener; - private boolean mOneOnOne; - private String mSelectedMessageId; - - public ConversationMessageAdapter(final Context context, final Cursor cursor, - final ConversationMessageViewHost host, - final AsyncImageViewDelayLoader imageViewDelayLoader, - final View.OnClickListener viewClickListener, - final View.OnLongClickListener longClickListener) { - super(context, cursor, 0); - mHost = host; - mViewClickListener = viewClickListener; - mViewLongClickListener = longClickListener; - mImageViewDelayLoader = imageViewDelayLoader; - setHasStableIds(true); - } - - @Override - public void bindViewHolder(final ConversationMessageViewHolder holder, - final Context context, final Cursor cursor) { - Assert.isTrue(holder.mView instanceof ConversationMessageView); - final ConversationMessageView conversationMessageView = - (ConversationMessageView) holder.mView; - conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId); - } - - @Override - public ConversationMessageViewHolder createViewHolder(final Context context, - final ViewGroup parent, final int viewType) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - final ConversationMessageView conversationMessageView = (ConversationMessageView) - layoutInflater.inflate(R.layout.conversation_message_view, null); - conversationMessageView.setHost(mHost); - conversationMessageView.setImageViewDelayLoader(mImageViewDelayLoader); - return new ConversationMessageViewHolder(conversationMessageView, - mViewClickListener, mViewLongClickListener); - } - - public void setSelectedMessage(final String messageId) { - mSelectedMessageId = messageId; - notifyDataSetChanged(); - } - - public void setOneOnOne(final boolean oneOnOne, final boolean invalidate) { - if (mOneOnOne != oneOnOne) { - mOneOnOne = oneOnOne; - if (invalidate) { - notifyDataSetChanged(); - } - } - } - - /** - * ViewHolder that holds a ConversationMessageView. - */ - public static class ConversationMessageViewHolder extends RecyclerView.ViewHolder { - final View mView; - - /** - * @param viewClickListener a View.OnClickListener that should define the interaction when - * an item in the RecyclerView is clicked. - */ - public ConversationMessageViewHolder(final View itemView, - final View.OnClickListener viewClickListener, - final View.OnLongClickListener viewLongClickListener) { - super(itemView); - mView = itemView; - - mView.setOnClickListener(viewClickListener); - mView.setOnLongClickListener(viewLongClickListener); - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java deleted file mode 100644 index ef6aeb4..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.android.messaging.R; -import com.android.messaging.annotation.VisibleForAnimation; -import com.android.messaging.datamodel.data.ConversationMessageBubbleData; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.util.UiUtils; - -/** - * Shows the message bubble for one conversation message. It is able to animate size changes - * by morphing when the message content changes size. - */ -// TODO: Move functionality from ConversationMessageView into this class as appropriate -public class ConversationMessageBubbleView extends LinearLayout { - private int mIntrinsicWidth; - private int mMorphedWidth; - private ObjectAnimator mAnimator; - private boolean mShouldAnimateWidthChange; - private final ConversationMessageBubbleData mData; - private int mRunningStartWidth; - private ViewGroup mBubbleBackground; - - public ConversationMessageBubbleView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = new ConversationMessageBubbleData(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mBubbleBackground = (ViewGroup) findViewById(R.id.message_text_and_info); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - final int newIntrinsicWidth = getMeasuredWidth(); - if (mIntrinsicWidth == 0 && newIntrinsicWidth != mIntrinsicWidth) { - if (mShouldAnimateWidthChange) { - kickOffMorphAnimation(mIntrinsicWidth, newIntrinsicWidth); - } - mIntrinsicWidth = newIntrinsicWidth; - } - - if (mMorphedWidth > 0) { - mBubbleBackground.getLayoutParams().width = mMorphedWidth; - } else { - mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT; - } - mBubbleBackground.requestLayout(); - } - - @VisibleForAnimation - public void setMorphWidth(final int width) { - mMorphedWidth = width; - requestLayout(); - } - - public void bind(final ConversationMessageData data) { - final boolean changed = mData.bind(data); - // Animate width change only when we are binding to the same message, so that we may - // animate view size changes on the same message bubble due to things like status text - // change. - // Don't animate width change when the bubble contains attachments. Width animation is - // only suitable for text-only messages (where the bubble size change due to status or - // time stamp changes). - mShouldAnimateWidthChange = !changed && !data.hasAttachments(); - if (mAnimator == null) { - mMorphedWidth = 0; - } - } - - public void kickOffMorphAnimation(final int oldWidth, final int newWidth) { - if (mAnimator != null) { - mAnimator.setIntValues(mRunningStartWidth, newWidth); - return; - } - mRunningStartWidth = oldWidth; - mAnimator = ObjectAnimator.ofInt(this, "morphWidth", oldWidth, newWidth); - mAnimator.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION); - mAnimator.addListener(new AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - } - - @Override - public void onAnimationEnd(Animator animator) { - mAnimator = null; - mMorphedWidth = 0; - // Allow the bubble to resize if, for example, the status text changed during - // the animation. This will snap to the bigger size if needed. This is intentional - // as animating immediately after looks really bad and switching layout params - // during the original animation does not achieve the desired effect. - mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT; - mBubbleBackground.requestLayout(); - } - - @Override - public void onAnimationCancel(Animator animator) { - } - - @Override - public void onAnimationRepeat(Animator animator) { - } - }); - mAnimator.start(); - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java deleted file mode 100644 index e22e2c7..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java +++ /dev/null @@ -1,1206 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.text.style.URLSpan; -import android.text.util.Linkify; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader; -import com.android.messaging.ui.AudioAttachmentView; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.ui.MultiAttachmentLayout; -import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener; -import com.android.messaging.ui.PersonItemView; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.VideoThumbnailView; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.YouTubeUtil; -import com.google.common.base.Predicate; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * The view for a single entry in a conversation. - */ -public class ConversationMessageView extends FrameLayout implements View.OnClickListener, - View.OnLongClickListener, OnAttachmentClickListener { - public interface ConversationMessageViewHost { - boolean onAttachmentClick(ConversationMessageView view, MessagePartData attachment, - Rect imageBounds, boolean longPress); - SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId, - boolean excludeDefault); - } - - private final ConversationMessageData mData; - - private LinearLayout mMessageAttachmentsView; - private MultiAttachmentLayout mMultiAttachmentView; - private AsyncImageView mMessageImageView; - private TextView mMessageTextView; - private boolean mMessageTextHasLinks; - private boolean mMessageHasYouTubeLink; - private TextView mStatusTextView; - private TextView mTitleTextView; - private TextView mMmsInfoTextView; - private LinearLayout mMessageTitleLayout; - private TextView mSenderNameTextView; - private ContactIconView mContactIconView; - private ConversationMessageBubbleView mMessageBubble; - private View mSubjectView; - private TextView mSubjectLabel; - private TextView mSubjectText; - private View mDeliveredBadge; - private ViewGroup mMessageMetadataView; - private ViewGroup mMessageTextAndInfoView; - private TextView mSimNameView; - - private boolean mOneOnOne; - private ConversationMessageViewHost mHost; - - public ConversationMessageView(final Context context, final AttributeSet attrs) { - super(context, attrs); - // TODO: we should switch to using Binding and DataModel factory methods. - mData = new ConversationMessageData(); - } - - @Override - protected void onFinishInflate() { - mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon); - mContactIconView.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(final View view) { - ConversationMessageView.this.performLongClick(); - return true; - } - }); - - mMessageAttachmentsView = (LinearLayout) findViewById(R.id.message_attachments); - mMultiAttachmentView = (MultiAttachmentLayout) findViewById(R.id.multiple_attachments); - mMultiAttachmentView.setOnAttachmentClickListener(this); - - mMessageImageView = (AsyncImageView) findViewById(R.id.message_image); - mMessageImageView.setOnClickListener(this); - mMessageImageView.setOnLongClickListener(this); - - mMessageTextView = (TextView) findViewById(R.id.message_text); - mMessageTextView.setOnClickListener(this); - IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this); - - mStatusTextView = (TextView) findViewById(R.id.message_status); - mTitleTextView = (TextView) findViewById(R.id.message_title); - mMmsInfoTextView = (TextView) findViewById(R.id.mms_info); - mMessageTitleLayout = (LinearLayout) findViewById(R.id.message_title_layout); - mSenderNameTextView = (TextView) findViewById(R.id.message_sender_name); - mMessageBubble = (ConversationMessageBubbleView) findViewById(R.id.message_content); - mSubjectView = findViewById(R.id.subject_container); - mSubjectLabel = (TextView) mSubjectView.findViewById(R.id.subject_label); - mSubjectText = (TextView) mSubjectView.findViewById(R.id.subject_text); - mDeliveredBadge = findViewById(R.id.smsDeliveredBadge); - mMessageMetadataView = (ViewGroup) findViewById(R.id.message_metadata); - mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info); - mSimNameView = (TextView) findViewById(R.id.sim_name); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec); - final int iconSize = getResources() - .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size); - - final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY); - - mContactIconView.measure(iconMeasureSpec, iconMeasureSpec); - - final int arrowWidth = - getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width); - - // We need to subtract contact icon width twice from the horizontal space to get - // the max leftover space because we want the message bubble to extend no further than the - // starting position of the message bubble in the opposite direction. - final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2 - - arrowWidth - getPaddingLeft() - getPaddingRight(); - final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace, - MeasureSpec.AT_MOST); - - mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec); - - final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(), - mMessageBubble.getMeasuredHeight()); - setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop()); - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, - final int bottom) { - final boolean isRtl = AccessibilityUtil.isLayoutRtl(this); - - final int iconWidth = mContactIconView.getMeasuredWidth(); - final int iconHeight = mContactIconView.getMeasuredHeight(); - final int iconTop = getPaddingTop(); - final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight(); - final int contentHeight = mMessageBubble.getMeasuredHeight(); - final int contentTop = iconTop; - - final int iconLeft; - final int contentLeft; - if (mData.getIsIncoming()) { - if (isRtl) { - iconLeft = (right - left) - getPaddingRight() - iconWidth; - contentLeft = iconLeft - contentWidth; - } else { - iconLeft = getPaddingLeft(); - contentLeft = iconLeft + iconWidth; - } - } else { - if (isRtl) { - iconLeft = getPaddingLeft(); - contentLeft = iconLeft + iconWidth; - } else { - iconLeft = (right - left) - getPaddingRight() - iconWidth; - contentLeft = iconLeft - contentWidth; - } - } - - mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight); - - mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth, - contentTop + contentHeight); - } - - /** - * Fills in the data associated with this view. - * - * @param cursor The cursor from a MessageList that this view is in, pointing to its entry. - */ - public void bind(final Cursor cursor) { - bind(cursor, true, null); - } - - /** - * Fills in the data associated with this view. - * - * @param cursor The cursor from a MessageList that this view is in, pointing to its entry. - * @param oneOnOne Whether this is a 1:1 conversation - */ - public void bind(final Cursor cursor, - final boolean oneOnOne, final String selectedMessageId) { - mOneOnOne = oneOnOne; - - // Update our UI model - mData.bind(cursor); - setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId)); - - // Update text and image content for the view. - updateViewContent(); - - // Update colors and layout parameters for the view. - updateViewAppearance(); - - updateContentDescription(); - } - - public void setHost(final ConversationMessageViewHost host) { - mHost = host; - } - - /** - * Sets a delay loader instance to manage loading / resuming of image attachments. - */ - public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) { - Assert.notNull(mMessageImageView); - mMessageImageView.setDelayLoader(delayLoader); - mMultiAttachmentView.setImageViewDelayLoader(delayLoader); - } - - public ConversationMessageData getData() { - return mData; - } - - /** - * Returns whether we should show simplified visual style for the message view (i.e. hide the - * avatar and bubble arrow, reduce padding). - */ - private boolean shouldShowSimplifiedVisualStyle() { - return mData.getCanClusterWithPreviousMessage(); - } - - /** - * Returns whether we need to show message bubble arrow. We don't show arrow if the message - * contains media attachments or if shouldShowSimplifiedVisualStyle() is true. - */ - private boolean shouldShowMessageBubbleArrow() { - return !shouldShowSimplifiedVisualStyle() - && !(mData.hasAttachments() || mMessageHasYouTubeLink); - } - - /** - * Returns whether we need to show a message bubble for text content. - */ - private boolean shouldShowMessageTextBubble() { - if (mData.hasText()) { - return true; - } - final String subjectText = MmsUtils.cleanseMmsSubject(getResources(), - mData.getMmsSubject()); - if (!TextUtils.isEmpty(subjectText)) { - return true; - } - return false; - } - - private void updateViewContent() { - updateMessageContent(); - int titleResId = -1; - int statusResId = -1; - String statusText = null; - switch(mData.getStatus()) { - case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - titleResId = R.string.message_title_downloading; - statusResId = R.string.message_status_downloading; - break; - - case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_manual_download; - if (isSelected()) { - statusResId = R.string.message_status_download_action; - } else { - statusResId = R.string.message_status_download; - } - } - break; - - case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_download_failed; - statusResId = R.string.message_status_download_error; - } - break; - - case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_download_failed; - if (isSelected()) { - statusResId = R.string.message_status_download_action; - } else { - statusResId = R.string.message_status_download; - } - } - break; - - case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND: - case MessageData.BUGLE_STATUS_OUTGOING_SENDING: - statusResId = R.string.message_status_sending; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_RESENDING: - case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY: - statusResId = R.string.message_status_send_retrying; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER: - statusResId = R.string.message_status_send_failed_emergency_number; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_FAILED: - // don't show the error state unless we're the default sms app - if (PhoneUtils.getDefault().isDefaultSmsApp()) { - if (isSelected()) { - statusResId = R.string.message_status_resend; - } else { - statusResId = MmsUtils.mapRawStatusToErrorResourceId( - mData.getStatus(), mData.getRawTelephonyStatus()); - } - break; - } - // FALL THROUGH HERE - - case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE: - case MessageData.BUGLE_STATUS_INCOMING_COMPLETE: - default: - if (!mData.getCanClusterWithNextMessage()) { - statusText = mData.getFormattedReceivedTimeStamp(); - } - break; - } - - final boolean titleVisible = (titleResId >= 0); - if (titleVisible) { - final String titleText = getResources().getString(titleResId); - mTitleTextView.setText(titleText); - - final String mmsInfoText = getResources().getString( - R.string.mms_info, - Formatter.formatFileSize(getContext(), mData.getSmsMessageSize()), - DateUtils.formatDateTime( - getContext(), - mData.getMmsExpiry(), - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_NUMERIC_DATE | - DateUtils.FORMAT_NO_YEAR)); - mMmsInfoTextView.setText(mmsInfoText); - mMessageTitleLayout.setVisibility(View.VISIBLE); - } else { - mMessageTitleLayout.setVisibility(View.GONE); - } - - final String subjectText = MmsUtils.cleanseMmsSubject(getResources(), - mData.getMmsSubject()); - final boolean subjectVisible = !TextUtils.isEmpty(subjectText); - - final boolean senderNameVisible = !mOneOnOne && !mData.getCanClusterWithNextMessage() - && mData.getIsIncoming(); - if (senderNameVisible) { - mSenderNameTextView.setText(mData.getSenderDisplayName()); - mSenderNameTextView.setVisibility(View.VISIBLE); - } else { - mSenderNameTextView.setVisibility(View.GONE); - } - - if (statusResId >= 0) { - statusText = getResources().getString(statusResId); - } - - // We set the text even if the view will be GONE for accessibility - mStatusTextView.setText(statusText); - final boolean statusVisible = !TextUtils.isEmpty(statusText); - if (statusVisible) { - mStatusTextView.setVisibility(View.VISIBLE); - } else { - mStatusTextView.setVisibility(View.GONE); - } - - final boolean deliveredBadgeVisible = - mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED; - mDeliveredBadge.setVisibility(deliveredBadgeVisible ? View.VISIBLE : View.GONE); - - // Update the sim indicator. - final boolean showSimIconAsIncoming = mData.getIsIncoming() && - (!mData.hasAttachments() || shouldShowMessageTextBubble()); - final SubscriptionListEntry subscriptionEntry = - mHost.getSubscriptionEntryForSelfParticipant(mData.getSelfParticipantId(), - true /* excludeDefault */); - final boolean simNameVisible = subscriptionEntry != null && - !TextUtils.isEmpty(subscriptionEntry.displayName) && - !mData.getCanClusterWithNextMessage(); - if (simNameVisible) { - final String simNameText = mData.getIsIncoming() ? getResources().getString( - R.string.incoming_sim_name_text, subscriptionEntry.displayName) : - subscriptionEntry.displayName; - mSimNameView.setText(simNameText); - mSimNameView.setTextColor(showSimIconAsIncoming ? getResources().getColor( - R.color.timestamp_text_incoming) : subscriptionEntry.displayColor); - mSimNameView.setVisibility(VISIBLE); - } else { - mSimNameView.setText(null); - mSimNameView.setVisibility(GONE); - } - - final boolean metadataVisible = senderNameVisible || statusVisible - || deliveredBadgeVisible || simNameVisible; - mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE); - - final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible - || mData.hasText() || metadataVisible; - mMessageTextAndInfoView.setVisibility( - messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE); - - if (shouldShowSimplifiedVisualStyle()) { - mContactIconView.setVisibility(View.GONE); - mContactIconView.setImageResourceUri(null); - } else { - mContactIconView.setVisibility(View.VISIBLE); - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - mData.getSenderProfilePhotoUri(), - mData.getSenderFullName(), - mData.getSenderNormalizedDestination(), - mData.getSenderContactLookupKey()); - mContactIconView.setImageResourceUri(avatarUri, mData.getSenderContactId(), - mData.getSenderContactLookupKey(), mData.getSenderNormalizedDestination()); - } - } - - private void updateMessageContent() { - // We must update the text before the attachments since we search the text to see if we - // should make a preview youtube image in the attachments - updateMessageText(); - updateMessageAttachments(); - updateMessageSubject(); - mMessageBubble.bind(mData); - } - - private void updateMessageAttachments() { - // Bind video, audio, and VCard attachments. If there are multiple, they stack vertically. - bindAttachmentsOfSameType(sVideoFilter, - R.layout.message_video_attachment, mVideoViewBinder, VideoThumbnailView.class); - bindAttachmentsOfSameType(sAudioFilter, - R.layout.message_audio_attachment, mAudioViewBinder, AudioAttachmentView.class); - bindAttachmentsOfSameType(sVCardFilter, - R.layout.message_vcard_attachment, mVCardViewBinder, PersonItemView.class); - - // Bind image attachments. If there are multiple, they are shown in a collage view. - final List imageParts = mData.getAttachments(sImageFilter); - if (imageParts.size() > 1) { - Collections.sort(imageParts, sImageComparator); - mMultiAttachmentView.bindAttachments(imageParts, null, imageParts.size()); - mMultiAttachmentView.setVisibility(View.VISIBLE); - } else { - mMultiAttachmentView.setVisibility(View.GONE); - } - - // In the case that we have no image attachments and exactly one youtube link in a message - // then we will show a preview. - String youtubeThumbnailUrl = null; - String originalYoutubeLink = null; - if (mMessageTextHasLinks && imageParts.size() == 0) { - CharSequence messageTextWithSpans = mMessageTextView.getText(); - final URLSpan[] spans = ((Spanned) messageTextWithSpans).getSpans(0, - messageTextWithSpans.length(), URLSpan.class); - for (URLSpan span : spans) { - String url = span.getURL(); - String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url); - if (!TextUtils.isEmpty(youtubeLinkForUrl)) { - if (TextUtils.isEmpty(youtubeThumbnailUrl)) { - // Save the youtube link if we don't already have one - youtubeThumbnailUrl = youtubeLinkForUrl; - originalYoutubeLink = url; - } else { - // We already have a youtube link. This means we have two youtube links so - // we shall show none. - youtubeThumbnailUrl = null; - originalYoutubeLink = null; - break; - } - } - } - } - // We need to keep track if we have a youtube link in the message so that we will not show - // the arrow - mMessageHasYouTubeLink = !TextUtils.isEmpty(youtubeThumbnailUrl); - - // We will show the message image view if there is one attachment or one youtube link - if (imageParts.size() == 1 || mMessageHasYouTubeLink) { - // Get the display metrics for a hint for how large to pull the image data into - final WindowManager windowManager = (WindowManager) getContext(). - getSystemService(Context.WINDOW_SERVICE); - final DisplayMetrics displayMetrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(displayMetrics); - - final int iconSize = getResources() - .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size); - final int desiredWidth = displayMetrics.widthPixels - iconSize - iconSize; - - if (imageParts.size() == 1) { - final MessagePartData imagePart = imageParts.get(0); - // If the image is big, we want to scale it down to save memory since we're going to - // scale it down to fit into the bubble width. We don't constrain the height. - final ImageRequestDescriptor imageRequest = - new MessagePartImageRequestDescriptor(imagePart, - desiredWidth, - MessagePartData.UNSPECIFIED_SIZE, - false); - adjustImageViewBounds(imagePart); - mMessageImageView.setImageResourceId(imageRequest); - mMessageImageView.setTag(imagePart); - } else { - // Youtube Thumbnail image - final ImageRequestDescriptor imageRequest = - new UriImageRequestDescriptor(Uri.parse(youtubeThumbnailUrl), desiredWidth, - MessagePartData.UNSPECIFIED_SIZE, true /* allowCompression */, - true /* isStatic */, false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - mMessageImageView.setImageResourceId(imageRequest); - mMessageImageView.setTag(originalYoutubeLink); - } - mMessageImageView.setVisibility(View.VISIBLE); - } else { - mMessageImageView.setImageResourceId(null); - mMessageImageView.setVisibility(View.GONE); - } - - // Show the message attachments container if any of its children are visible - boolean attachmentsVisible = false; - for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) { - final View attachmentView = mMessageAttachmentsView.getChildAt(i); - if (attachmentView.getVisibility() == View.VISIBLE) { - attachmentsVisible = true; - break; - } - } - mMessageAttachmentsView.setVisibility(attachmentsVisible ? View.VISIBLE : View.GONE); - } - - private void bindAttachmentsOfSameType(final Predicate attachmentTypeFilter, - final int attachmentViewLayoutRes, final AttachmentViewBinder viewBinder, - final Class attachmentViewClass) { - final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - - // Iterate through all attachments of a particular type (video, audio, etc). - // Find the first attachment index that matches the given type if possible. - int attachmentViewIndex = -1; - View existingAttachmentView; - do { - existingAttachmentView = mMessageAttachmentsView.getChildAt(++attachmentViewIndex); - } while (existingAttachmentView != null && - !(attachmentViewClass.isInstance(existingAttachmentView))); - - for (final MessagePartData attachment : mData.getAttachments(attachmentTypeFilter)) { - View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex); - if (!attachmentViewClass.isInstance(attachmentView)) { - attachmentView = layoutInflater.inflate(attachmentViewLayoutRes, - mMessageAttachmentsView, false /* attachToRoot */); - attachmentView.setOnClickListener(this); - attachmentView.setOnLongClickListener(this); - mMessageAttachmentsView.addView(attachmentView, attachmentViewIndex); - } - viewBinder.bindView(attachmentView, attachment); - attachmentView.setTag(attachment); - attachmentView.setVisibility(View.VISIBLE); - attachmentViewIndex++; - } - // If there are unused views left over, unbind or remove them. - while (attachmentViewIndex < mMessageAttachmentsView.getChildCount()) { - final View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex); - if (attachmentViewClass.isInstance(attachmentView)) { - mMessageAttachmentsView.removeViewAt(attachmentViewIndex); - } else { - // No more views of this type; we're done. - break; - } - } - } - - private void updateMessageSubject() { - final String subjectText = MmsUtils.cleanseMmsSubject(getResources(), - mData.getMmsSubject()); - final boolean subjectVisible = !TextUtils.isEmpty(subjectText); - - if (subjectVisible) { - mSubjectText.setText(subjectText); - mSubjectView.setVisibility(View.VISIBLE); - } else { - mSubjectView.setVisibility(View.GONE); - } - } - - private void updateMessageText() { - final String text = mData.getText(); - if (!TextUtils.isEmpty(text)) { - mMessageTextView.setText(text); - // Linkify phone numbers, web urls, emails, and map addresses to allow users to - // click on them and take the default intent. - mMessageTextHasLinks = Linkify.addLinks(mMessageTextView, Linkify.ALL); - mMessageTextView.setVisibility(View.VISIBLE); - } else { - mMessageTextView.setVisibility(View.GONE); - mMessageTextHasLinks = false; - } - } - - private void updateViewAppearance() { - final Resources res = getResources(); - final ConversationDrawables drawableProvider = ConversationDrawables.get(); - final boolean incoming = mData.getIsIncoming(); - final boolean outgoing = !incoming; - final boolean showArrow = shouldShowMessageBubbleArrow(); - - final int messageTopPaddingClustered = - res.getDimensionPixelSize(R.dimen.message_padding_same_author); - final int messageTopPaddingDefault = - res.getDimensionPixelSize(R.dimen.message_padding_default); - final int arrowWidth = res.getDimensionPixelOffset(R.dimen.message_bubble_arrow_width); - final int messageTextMinHeightDefault = res.getDimensionPixelSize( - R.dimen.conversation_message_contact_icon_size); - final int messageTextLeftRightPadding = res.getDimensionPixelOffset( - R.dimen.message_text_left_right_padding); - final int textTopPaddingDefault = res.getDimensionPixelOffset( - R.dimen.message_text_top_padding); - final int textBottomPaddingDefault = res.getDimensionPixelOffset( - R.dimen.message_text_bottom_padding); - - // These values depend on whether the message has text, attachments, or both. - // We intentionally don't set defaults, so the compiler will tell us if we forget - // to set one of them, or if we set one more than once. - final int contentLeftPadding, contentRightPadding; - final Drawable textBackground; - final int textMinHeight; - final int textTopMargin; - final int textTopPadding, textBottomPadding; - final int textLeftPadding, textRightPadding; - - if (mData.hasAttachments()) { - if (shouldShowMessageTextBubble()) { - // Text and attachment(s) - contentLeftPadding = incoming ? arrowWidth : 0; - contentRightPadding = outgoing ? arrowWidth : 0; - textBackground = drawableProvider.getBubbleDrawable( - isSelected(), - incoming, - false /* needArrow */, - mData.hasIncomingErrorStatus()); - textMinHeight = messageTextMinHeightDefault; - textTopMargin = messageTopPaddingClustered; - textTopPadding = textTopPaddingDefault; - textBottomPadding = textBottomPaddingDefault; - textLeftPadding = messageTextLeftRightPadding; - textRightPadding = messageTextLeftRightPadding; - } else { - // Attachment(s) only - contentLeftPadding = incoming ? arrowWidth : 0; - contentRightPadding = outgoing ? arrowWidth : 0; - textBackground = null; - textMinHeight = 0; - textTopMargin = 0; - textTopPadding = 0; - textBottomPadding = 0; - textLeftPadding = 0; - textRightPadding = 0; - } - } else { - // Text only - contentLeftPadding = (!showArrow && incoming) ? arrowWidth : 0; - contentRightPadding = (!showArrow && outgoing) ? arrowWidth : 0; - textBackground = drawableProvider.getBubbleDrawable( - isSelected(), - incoming, - shouldShowMessageBubbleArrow(), - mData.hasIncomingErrorStatus()); - textMinHeight = messageTextMinHeightDefault; - textTopMargin = 0; - textTopPadding = textTopPaddingDefault; - textBottomPadding = textBottomPaddingDefault; - if (showArrow && incoming) { - textLeftPadding = messageTextLeftRightPadding + arrowWidth; - } else { - textLeftPadding = messageTextLeftRightPadding; - } - if (showArrow && outgoing) { - textRightPadding = messageTextLeftRightPadding + arrowWidth; - } else { - textRightPadding = messageTextLeftRightPadding; - } - } - - // These values do not depend on whether the message includes attachments - final int gravity = incoming ? (Gravity.START | Gravity.CENTER_VERTICAL) : - (Gravity.END | Gravity.CENTER_VERTICAL); - final int messageTopPadding = shouldShowSimplifiedVisualStyle() ? - messageTopPaddingClustered : messageTopPaddingDefault; - final int metadataTopPadding = res.getDimensionPixelOffset( - R.dimen.message_metadata_top_padding); - - // Update the message text/info views - ImageUtils.setBackgroundDrawableOnView(mMessageTextAndInfoView, textBackground); - mMessageTextAndInfoView.setMinimumHeight(textMinHeight); - final LinearLayout.LayoutParams textAndInfoLayoutParams = - (LinearLayout.LayoutParams) mMessageTextAndInfoView.getLayoutParams(); - textAndInfoLayoutParams.topMargin = textTopMargin; - - if (UiUtils.isRtlMode()) { - // Need to switch right and left padding in RtL mode - mMessageTextAndInfoView.setPadding(textRightPadding, textTopPadding, textLeftPadding, - textBottomPadding); - mMessageBubble.setPadding(contentRightPadding, 0, contentLeftPadding, 0); - } else { - mMessageTextAndInfoView.setPadding(textLeftPadding, textTopPadding, textRightPadding, - textBottomPadding); - mMessageBubble.setPadding(contentLeftPadding, 0, contentRightPadding, 0); - } - - // Update the message row and message bubble views - setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0); - mMessageBubble.setGravity(gravity); - updateMessageAttachmentsAppearance(gravity); - - mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0); - - updateTextAppearance(); - - requestLayout(); - } - - private void updateContentDescription() { - StringBuilder description = new StringBuilder(); - - Resources res = getResources(); - String separator = res.getString(R.string.enumeration_comma); - - // Sender information - boolean hasPlainTextMessage = !(TextUtils.isEmpty(mData.getText()) || - mMessageTextHasLinks); - if (mData.getIsIncoming()) { - int senderResId = hasPlainTextMessage - ? R.string.incoming_text_sender_content_description - : R.string.incoming_sender_content_description; - description.append(res.getString(senderResId, mData.getSenderDisplayName())); - } else { - int senderResId = hasPlainTextMessage - ? R.string.outgoing_text_sender_content_description - : R.string.outgoing_sender_content_description; - description.append(res.getString(senderResId)); - } - - if (mSubjectView.getVisibility() == View.VISIBLE) { - description.append(separator); - description.append(mSubjectText.getText()); - } - - if (mMessageTextView.getVisibility() == View.VISIBLE) { - // If the message has hyperlinks, we will let the user navigate to the text message so - // that the hyperlink can be clicked. Otherwise, the text message does not need to - // be reachable. - if (mMessageTextHasLinks) { - mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } else { - mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - description.append(separator); - description.append(mMessageTextView.getText()); - } - } - - if (mMessageTitleLayout.getVisibility() == View.VISIBLE) { - description.append(separator); - description.append(mTitleTextView.getText()); - - description.append(separator); - description.append(mMmsInfoTextView.getText()); - } - - if (mStatusTextView.getVisibility() == View.VISIBLE) { - description.append(separator); - description.append(mStatusTextView.getText()); - } - - if (mSimNameView.getVisibility() == View.VISIBLE) { - description.append(separator); - description.append(mSimNameView.getText()); - } - - if (mDeliveredBadge.getVisibility() == View.VISIBLE) { - description.append(separator); - description.append(res.getString(R.string.delivered_status_content_description)); - } - - setContentDescription(description); - } - - private void updateMessageAttachmentsAppearance(final int gravity) { - mMessageAttachmentsView.setGravity(gravity); - - // Tint image/video attachments when selected - final int selectedImageTint = getResources().getColor(R.color.message_image_selected_tint); - if (mMessageImageView.getVisibility() == View.VISIBLE) { - if (isSelected()) { - mMessageImageView.setColorFilter(selectedImageTint); - } else { - mMessageImageView.clearColorFilter(); - } - } - if (mMultiAttachmentView.getVisibility() == View.VISIBLE) { - if (isSelected()) { - mMultiAttachmentView.setColorFilter(selectedImageTint); - } else { - mMultiAttachmentView.clearColorFilter(); - } - } - for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) { - final View attachmentView = mMessageAttachmentsView.getChildAt(i); - if (attachmentView instanceof VideoThumbnailView - && attachmentView.getVisibility() == View.VISIBLE) { - final VideoThumbnailView videoView = (VideoThumbnailView) attachmentView; - if (isSelected()) { - videoView.setColorFilter(selectedImageTint); - } else { - videoView.clearColorFilter(); - } - } - } - - // If there are multiple attachment bubbles in a single message, add some separation. - final int multipleAttachmentPadding = - getResources().getDimensionPixelSize(R.dimen.message_padding_same_author); - - boolean previousVisibleView = false; - for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) { - final View attachmentView = mMessageAttachmentsView.getChildAt(i); - if (attachmentView.getVisibility() == View.VISIBLE) { - final int margin = previousVisibleView ? multipleAttachmentPadding : 0; - ((LinearLayout.LayoutParams) attachmentView.getLayoutParams()).topMargin = margin; - // updateViewAppearance calls requestLayout() at the end, so we don't need to here - previousVisibleView = true; - } - } - } - - private void updateTextAppearance() { - int messageColorResId; - int statusColorResId = -1; - int infoColorResId = -1; - int timestampColorResId; - int subjectLabelColorResId; - if (isSelected()) { - messageColorResId = R.color.message_text_color_incoming; - statusColorResId = R.color.message_action_status_text; - infoColorResId = R.color.message_action_info_text; - if (shouldShowMessageTextBubble()) { - timestampColorResId = R.color.message_action_timestamp_text; - subjectLabelColorResId = R.color.message_action_timestamp_text; - } else { - // If there's no text, the timestamp will be shown below the attachments, - // against the conversation view background. - timestampColorResId = R.color.timestamp_text_outgoing; - subjectLabelColorResId = R.color.timestamp_text_outgoing; - } - } else { - messageColorResId = (mData.getIsIncoming() ? - R.color.message_text_color_incoming : R.color.message_text_color_outgoing); - statusColorResId = messageColorResId; - infoColorResId = R.color.timestamp_text_incoming; - switch(mData.getStatus()) { - - case MessageData.BUGLE_STATUS_OUTGOING_FAILED: - case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER: - timestampColorResId = R.color.message_failed_timestamp_text; - subjectLabelColorResId = R.color.timestamp_text_outgoing; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND: - case MessageData.BUGLE_STATUS_OUTGOING_SENDING: - case MessageData.BUGLE_STATUS_OUTGOING_RESENDING: - case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY: - case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE: - case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED: - timestampColorResId = R.color.timestamp_text_outgoing; - subjectLabelColorResId = R.color.timestamp_text_outgoing; - break; - - case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE: - case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED: - messageColorResId = R.color.message_text_color_incoming_download_failed; - timestampColorResId = R.color.message_download_failed_timestamp_text; - subjectLabelColorResId = R.color.message_text_color_incoming_download_failed; - statusColorResId = R.color.message_download_failed_status_text; - infoColorResId = R.color.message_info_text_incoming_download_failed; - break; - - case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD: - timestampColorResId = R.color.message_text_color_incoming; - subjectLabelColorResId = R.color.message_text_color_incoming; - infoColorResId = R.color.timestamp_text_incoming; - break; - - case MessageData.BUGLE_STATUS_INCOMING_COMPLETE: - default: - timestampColorResId = R.color.timestamp_text_incoming; - subjectLabelColorResId = R.color.timestamp_text_incoming; - infoColorResId = -1; // Not used - break; - } - } - final int messageColor = getResources().getColor(messageColorResId); - mMessageTextView.setTextColor(messageColor); - mMessageTextView.setLinkTextColor(messageColor); - mSubjectText.setTextColor(messageColor); - if (statusColorResId >= 0) { - mTitleTextView.setTextColor(getResources().getColor(statusColorResId)); - } - if (infoColorResId >= 0) { - mMmsInfoTextView.setTextColor(getResources().getColor(infoColorResId)); - } - if (timestampColorResId == R.color.timestamp_text_incoming && - mData.hasAttachments() && !shouldShowMessageTextBubble()) { - timestampColorResId = R.color.timestamp_text_outgoing; - } - mStatusTextView.setTextColor(getResources().getColor(timestampColorResId)); - - mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId)); - mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId)); - } - - /** - * If we don't know the size of the image, we want to show it in a fixed-sized frame to - * avoid janks when the image is loaded and resized. Otherwise, we can set the imageview to - * take on normal layout params. - */ - private void adjustImageViewBounds(final MessagePartData imageAttachment) { - Assert.isTrue(ContentType.isImageType(imageAttachment.getContentType())); - final ViewGroup.LayoutParams layoutParams = mMessageImageView.getLayoutParams(); - if (imageAttachment.getWidth() == MessagePartData.UNSPECIFIED_SIZE || - imageAttachment.getHeight() == MessagePartData.UNSPECIFIED_SIZE) { - // We don't know the size of the image attachment, enable letterboxing on the image - // and show a fixed sized attachment. This should happen at most once per image since - // after the image is loaded we then save the image dimensions to the db so that the - // next time we can display the full size. - layoutParams.width = getResources() - .getDimensionPixelSize(R.dimen.image_attachment_fallback_width); - layoutParams.height = getResources() - .getDimensionPixelSize(R.dimen.image_attachment_fallback_height); - mMessageImageView.setScaleType(ScaleType.CENTER_CROP); - } else { - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - // ScaleType.CENTER_INSIDE and FIT_CENTER behave similarly for most images. However, - // FIT_CENTER works better for small images as it enlarges the image such that the - // minimum size ("android:minWidth" etc) is honored. - mMessageImageView.setScaleType(ScaleType.FIT_CENTER); - } - } - - @Override - public void onClick(final View view) { - final Object tag = view.getTag(); - if (tag instanceof MessagePartData) { - final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view); - onAttachmentClick((MessagePartData) tag, bounds, false /* longPress */); - } else if (tag instanceof String) { - // Currently the only object that would make a tag of a string is a youtube preview - // image - UIIntents.get().launchBrowserForUrl(getContext(), (String) tag); - } - } - - @Override - public boolean onLongClick(final View view) { - if (view == mMessageTextView) { - // Preemptively handle the long click event on message text so it's not handled by - // the link spans. - return performLongClick(); - } - - final Object tag = view.getTag(); - if (tag instanceof MessagePartData) { - final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view); - return onAttachmentClick((MessagePartData) tag, bounds, true /* longPress */); - } - - return false; - } - - @Override - public boolean onAttachmentClick(final MessagePartData attachment, - final Rect viewBoundsOnScreen, final boolean longPress) { - return mHost.onAttachmentClick(this, attachment, viewBoundsOnScreen, longPress); - } - - public ContactIconView getContactIconView() { - return mContactIconView; - } - - // Sort photos in MultiAttachLayout in the same order as the ConversationImagePartsView - static final Comparator sImageComparator = new Comparator(){ - @Override - public int compare(final MessagePartData x, final MessagePartData y) { - return x.getPartId().compareTo(y.getPartId()); - } - }; - - static final Predicate sVideoFilter = new Predicate() { - @Override - public boolean apply(final MessagePartData part) { - return part.isVideo(); - } - }; - - static final Predicate sAudioFilter = new Predicate() { - @Override - public boolean apply(final MessagePartData part) { - return part.isAudio(); - } - }; - - static final Predicate sVCardFilter = new Predicate() { - @Override - public boolean apply(final MessagePartData part) { - return part.isVCard(); - } - }; - - static final Predicate sImageFilter = new Predicate() { - @Override - public boolean apply(final MessagePartData part) { - return part.isImage(); - } - }; - - interface AttachmentViewBinder { - void bindView(View view, MessagePartData attachment); - void unbind(View view); - } - - final AttachmentViewBinder mVideoViewBinder = new AttachmentViewBinder() { - @Override - public void bindView(final View view, final MessagePartData attachment) { - ((VideoThumbnailView) view).setSource(attachment, mData.getIsIncoming()); - } - - @Override - public void unbind(final View view) { - ((VideoThumbnailView) view).setSource((Uri) null, mData.getIsIncoming()); - } - }; - - final AttachmentViewBinder mAudioViewBinder = new AttachmentViewBinder() { - @Override - public void bindView(final View view, final MessagePartData attachment) { - final AudioAttachmentView audioView = (AudioAttachmentView) view; - audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming()); - audioView.setBackground(ConversationDrawables.get().getBubbleDrawable( - isSelected(), mData.getIsIncoming(), false /* needArrow */, - mData.hasIncomingErrorStatus())); - } - - @Override - public void unbind(final View view) { - ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming()); - } - }; - - final AttachmentViewBinder mVCardViewBinder = new AttachmentViewBinder() { - @Override - public void bindView(final View view, final MessagePartData attachment) { - final PersonItemView personView = (PersonItemView) view; - personView.bind(DataModel.get().createVCardContactItemData(getContext(), - attachment)); - personView.setBackground(ConversationDrawables.get().getBubbleDrawable( - isSelected(), mData.getIsIncoming(), false /* needArrow */, - mData.hasIncomingErrorStatus())); - final int nameTextColorRes; - final int detailsTextColorRes; - if (isSelected()) { - nameTextColorRes = R.color.message_text_color_incoming; - detailsTextColorRes = R.color.message_text_color_incoming; - } else { - nameTextColorRes = mData.getIsIncoming() ? R.color.message_text_color_incoming - : R.color.message_text_color_outgoing; - detailsTextColorRes = mData.getIsIncoming() ? R.color.timestamp_text_incoming - : R.color.timestamp_text_outgoing; - } - personView.setNameTextColor(getResources().getColor(nameTextColorRes)); - personView.setDetailsTextColor(getResources().getColor(detailsTextColorRes)); - } - - @Override - public void unbind(final View view) { - ((PersonItemView) view).bind(null); - } - }; - - /** - * A helper class that allows us to handle long clicks on linkified message text view (i.e. to - * select the message) so it's not handled by the link spans to launch apps for the links. - */ - private static class IgnoreLinkLongClickHelper implements OnLongClickListener, OnTouchListener { - private boolean mIsLongClick; - private final OnLongClickListener mDelegateLongClickListener; - - /** - * Ignore long clicks on linkified texts for a given text view. - * @param textView the TextView to ignore long clicks on - * @param longClickListener a delegate OnLongClickListener to be called when the view is - * long clicked. - */ - public static void ignoreLinkLongClick(final TextView textView, - @Nullable final OnLongClickListener longClickListener) { - final IgnoreLinkLongClickHelper helper = - new IgnoreLinkLongClickHelper(longClickListener); - textView.setOnLongClickListener(helper); - textView.setOnTouchListener(helper); - } - - private IgnoreLinkLongClickHelper(@Nullable final OnLongClickListener longClickListener) { - mDelegateLongClickListener = longClickListener; - } - - @Override - public boolean onLongClick(final View v) { - // Record that this click is a long click. - mIsLongClick = true; - if (mDelegateLongClickListener != null) { - return mDelegateLongClickListener.onLongClick(v); - } - return false; - } - - @Override - public boolean onTouch(final View v, final MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_UP && mIsLongClick) { - // This touch event is a long click, preemptively handle this touch event so that - // the link span won't get a onClicked() callback. - mIsLongClick = false; - return true; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mIsLongClick = false; - } - return false; - } - } -} diff --git a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java b/src/com/android/messaging/ui/conversation/ConversationSimSelector.java deleted file mode 100644 index fc43a46..0000000 --- a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.support.v4.util.Pair; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.ui.conversation.SimSelectorView.SimSelectorViewListener; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.ThreadUtil; - -/** - * Manages showing/hiding the SIM selector in conversation. - */ -abstract class ConversationSimSelector extends ConversationInput { - private SimSelectorView mSimSelectorView; - private Pair mPendingShow; - private boolean mDataReady; - private String mSelectedSimText; - - public ConversationSimSelector(ConversationInputBase baseHost) { - super(baseHost, false); - } - - public void onSubscriptionListDataLoaded(final SubscriptionListData subscriptionListData) { - ensureSimSelectorView(); - mSimSelectorView.bind(subscriptionListData); - mDataReady = subscriptionListData != null && subscriptionListData.hasData(); - if (mPendingShow != null && mDataReady) { - Assert.isTrue(OsUtil.isAtLeastL_MR1()); - final boolean show = mPendingShow.first; - final boolean animate = mPendingShow.second; - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - // This will No-Op if we are no longer attached to the host. - mConversationInputBase.showHideInternal(ConversationSimSelector.this, - show, animate); - } - }); - mPendingShow = null; - } - } - - private void announcedSelectedSim() { - final Context context = Factory.get().getApplicationContext(); - if (AccessibilityUtil.isTouchExplorationEnabled(context) && - !TextUtils.isEmpty(mSelectedSimText)) { - AccessibilityUtil.announceForAccessibilityCompat( - mSimSelectorView, null, - context.getString(R.string.selected_sim_content_message, mSelectedSimText)); - } - } - - public void setSelected(final SubscriptionListEntry subEntry) { - mSelectedSimText = subEntry == null ? null : subEntry.displayName; - } - - @Override - public boolean show(boolean animate) { - announcedSelectedSim(); - return showHide(true, animate); - } - - @Override - public boolean hide(boolean animate) { - return showHide(false, animate); - } - - private boolean showHide(final boolean show, final boolean animate) { - if (!OsUtil.isAtLeastL_MR1()) { - return false; - } - - if (mDataReady) { - mSimSelectorView.showOrHide(show, animate); - return mSimSelectorView.isOpen() == show; - } else { - mPendingShow = Pair.create(show, animate); - return false; - } - } - - private void ensureSimSelectorView() { - if (mSimSelectorView == null) { - // Grab the SIM selector view from the host. This class assumes ownership of it. - mSimSelectorView = getSimSelectorView(); - mSimSelectorView.setItemLayoutId(getSimSelectorItemLayoutId()); - mSimSelectorView.setListener(new SimSelectorViewListener() { - - @Override - public void onSimSelectorVisibilityChanged(boolean visible) { - onVisibilityChanged(visible); - } - - @Override - public void onSimItemClicked(SubscriptionListEntry item) { - selectSim(item); - } - }); - } - } - - protected abstract SimSelectorView getSimSelectorView(); - protected abstract void selectSim(final SubscriptionListEntry item); - protected abstract int getSimSelectorItemLayoutId(); - -} diff --git a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java b/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java deleted file mode 100644 index e3ad601..0000000 --- a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.widget.EditText; - -import com.android.messaging.R; -import com.android.messaging.datamodel.ParticipantRefresh; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.UiUtils; - -/** - * The dialog for the user to enter the phone number of their sim. - */ -public class EnterSelfPhoneNumberDialog extends DialogFragment { - private EditText mEditText; - private int mSubId; - - public static EnterSelfPhoneNumberDialog newInstance(final int subId) { - final EnterSelfPhoneNumberDialog dialog = new EnterSelfPhoneNumberDialog(); - dialog.mSubId = subId; - return dialog; - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Context context = getActivity(); - final LayoutInflater inflater = LayoutInflater.from(context); - mEditText = (EditText) inflater.inflate(R.layout.enter_phone_number_view, null, false); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.enter_phone_number_title) - .setMessage(R.string.enter_phone_number_text) - .setView(mEditText) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - dismiss(); - } - }) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - final String newNumber = mEditText.getText().toString(); - dismiss(); - if (!TextUtils.isEmpty(newNumber)) { - savePhoneNumberInPrefs(newNumber); - // TODO: Remove this toast and just auto-send - // the message instead - UiUtils.showToast( - R.string - .toast_after_setting_default_sms_app_for_message_send); - } - } - }); - return builder.create(); - } - - private void savePhoneNumberInPrefs(final String newPhoneNumber) { - final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId); - subPrefs.putString(getString(R.string.mms_phone_number_pref_key), - newPhoneNumber); - // Update the self participants so the new phone number will be reflected - // everywhere in the UI. - ParticipantRefresh.refreshSelfParticipants(); - } -} diff --git a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java deleted file mode 100644 index 8af9f75..0000000 --- a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.LaunchConversationData; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; - -/** - * Launches ConversationActivity for sending a message to, or viewing messages from, a specific - * recipient. - *

- * (This activity should be marked noHistory="true" in AndroidManifest.xml) - */ -public class LaunchConversationActivity extends Activity implements - LaunchConversationData.LaunchConversationDataListener { - static final String SMS_BODY = "sms_body"; - static final String ADDRESS = "address"; - final Binding mBinding = BindingBase.createBinding(this); - String mSmsBody; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (UiUtils.redirectToPermissionCheckIfNeeded(this)) { - return; - } - - final Intent intent = getIntent(); - final String action = intent.getAction(); - if (Intent.ACTION_SENDTO.equals(action) || Intent.ACTION_VIEW.equals(action)) { - String[] recipients = UriUtil.parseRecipientsFromSmsMmsUri(intent.getData()); - final boolean haveAddress = !TextUtils.isEmpty(intent.getStringExtra(ADDRESS)); - final boolean haveEmail = !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL)); - if (recipients == null && (haveAddress || haveEmail)) { - if (haveAddress) { - recipients = new String[] { intent.getStringExtra(ADDRESS) }; - } else { - recipients = new String[] { intent.getStringExtra(Intent.EXTRA_EMAIL) }; - } - } - mSmsBody = intent.getStringExtra(SMS_BODY); - if (TextUtils.isEmpty(mSmsBody)) { - // Used by intents sent from the web YouTube (and perhaps others). - mSmsBody = getBody(intent.getData()); - if (TextUtils.isEmpty(mSmsBody)) { - // If that fails, try yet another method apps use to share text - if (ContentType.TEXT_PLAIN.equals(intent.getType())) { - mSmsBody = intent.getStringExtra(Intent.EXTRA_TEXT); - } - } - } - if (recipients != null) { - mBinding.bind(DataModel.get().createLaunchConversationData(this)); - mBinding.getData().getOrCreateConversation(mBinding, recipients); - } else { - // No recipients were specified in the intent. - // Start a new conversation with contact picker. The new conversation will be - // primed with the (optional) message in mSmsBody. - onGetOrCreateNewConversation(null); - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, "Unsupported conversation intent action : " + action); - } - // As of M, activities without a visible window must finish before onResume completes. - finish(); - } - - private String getBody(final Uri uri) { - if (uri == null) { - return null; - } - String urlStr = uri.getSchemeSpecificPart(); - if (!urlStr.contains("?")) { - return null; - } - urlStr = urlStr.substring(urlStr.indexOf('?') + 1); - final String[] params = urlStr.split("&"); - for (final String p : params) { - if (p.startsWith("body=")) { - try { - return URLDecoder.decode(p.substring(5), "UTF-8"); - } catch (final UnsupportedEncodingException e) { - // Invalid URL, ignore - } - } - } - return null; - } - - @Override - public void onGetOrCreateNewConversation(final String conversationId) { - final Context context = Factory.get().getApplicationContext(); - UIIntents.get().launchConversationActivityWithParentStack(context, conversationId, - mSmsBody); - } - - @Override - public void onGetOrCreateNewConversationFailed() { - UiUtils.showToastAtBottom(R.string.conversation_creation_failure); - } -} diff --git a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java b/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java deleted file mode 100644 index 4c22970..0000000 --- a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.LinearLayout; - -import com.android.messaging.R; - -public class MessageBubbleBackground extends LinearLayout { - private final int mSnapWidthPixels; - - public MessageBubbleBackground(Context context, AttributeSet attrs) { - super(context, attrs); - mSnapWidthPixels = context.getResources().getDimensionPixelSize( - R.dimen.conversation_bubble_width_snap); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - final int widthPadding = getPaddingLeft() + getPaddingRight(); - int bubbleWidth = getMeasuredWidth() - widthPadding; - final int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - widthPadding; - // Round up to next snapWidthPixels - bubbleWidth = Math.min(maxWidth, - (int) (Math.ceil(bubbleWidth / (float) mSnapWidthPixels) * mSnapWidthPixels)); - super.onMeasure( - MeasureSpec.makeMeasureSpec(bubbleWidth + widthPadding, MeasureSpec.EXACTLY), - heightMeasureSpec); - } -} diff --git a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java b/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java deleted file mode 100644 index 89b9148..0000000 --- a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.res.Resources; -import android.net.Uri; -import android.text.TextUtils; -import android.text.format.Formatter; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.ConversationParticipantsData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.mmslib.pdu.PduHeaders; -import com.android.messaging.sms.DatabaseMessages.MmsMessage; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.Assert; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.Dates; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.SafeAsyncTask; - -import java.util.List; - -public class MessageDetailsDialog { - private static final String RECIPIENT_SEPARATOR = ", "; - - // All methods are static, no creating this class - private MessageDetailsDialog() { - } - - public static void show(final Context context, final ConversationMessageData data, - final ConversationParticipantsData participants, final ParticipantData self) { - if (DebugUtils.isDebugEnabled()) { - new SafeAsyncTask() { - @Override - protected String doInBackgroundTimed(Void... params) { - return getMessageDetails(context, data, participants, self); - } - - @Override - protected void onPostExecute(String messageDetails) { - showDialog(context, messageDetails); - } - }.executeOnThreadPool(null, null, null); - } else { - String messageDetails = getMessageDetails(context, data, participants, self); - showDialog(context, messageDetails); - } - } - - private static String getMessageDetails(final Context context, - final ConversationMessageData data, - final ConversationParticipantsData participants, final ParticipantData self) { - String messageDetails = null; - if (data.getIsSms()) { - messageDetails = getSmsMessageDetails(data, participants, self); - } else { - // TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently? - messageDetails = getMmsMessageDetails(context, data, participants, self); - } - - return messageDetails; - } - - private static void showDialog(final Context context, String messageDetails) { - if (!TextUtils.isEmpty(messageDetails)) { - new AlertDialog.Builder(context) - .setTitle(R.string.message_details_title) - .setMessage(messageDetails) - .setCancelable(true) - .show(); - } - } - - /** - * Return a string, separated by newlines, that contains a number of labels and values - * for this sms message. The string will be displayed in a modal dialog. - * @return string list of various message properties - */ - private static String getSmsMessageDetails(final ConversationMessageData data, - final ConversationParticipantsData participants, final ParticipantData self) { - final Resources res = Factory.get().getApplicationContext().getResources(); - final StringBuilder details = new StringBuilder(); - - // Type: Text message - details.append(res.getString(R.string.message_type_label)); - details.append(res.getString(R.string.text_message)); - - // From: +1425xxxxxxx - // or To: +1425xxxxxxx - final String rawSender = data.getSenderNormalizedDestination(); - if (!TextUtils.isEmpty(rawSender)) { - details.append('\n'); - details.append(res.getString(R.string.from_label)); - details.append(rawSender); - } - final String rawRecipients = getRecipientParticipantString(participants, - data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId()); - if (!TextUtils.isEmpty(rawRecipients)) { - details.append('\n'); - details.append(res.getString(R.string.to_address_label)); - details.append(rawRecipients); - } - - // Sent: Mon 11:42AM - if (data.getIsIncoming()) { - if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) { - details.append('\n'); - details.append(res.getString(R.string.sent_label)); - details.append( - Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString()); - } - } - - // Sent: Mon 11:43AM - // or Received: Mon 11:43AM - appendSentOrReceivedTimestamp(res, details, data); - - appendSimInfo(res, self, details); - - if (DebugUtils.isDebugEnabled()) { - appendDebugInfo(details, data); - } - - return details.toString(); - } - - /** - * Return a string, separated by newlines, that contains a number of labels and values - * for this mms message. The string will be displayed in a modal dialog. - * @return string list of various message properties - */ - private static String getMmsMessageDetails(Context context, final ConversationMessageData data, - final ConversationParticipantsData participants, final ParticipantData self) { - final Resources res = Factory.get().getApplicationContext().getResources(); - // TODO: when we support non-auto-download of mms messages, we'll have to handle - // the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display - // something different. See the Messaging app's MessageUtils.getNotificationIndDetails() - - final StringBuilder details = new StringBuilder(); - - // Type: Multimedia message. - details.append(res.getString(R.string.message_type_label)); - details.append(res.getString(R.string.multimedia_message)); - - // From: +1425xxxxxxx - final String rawSender = data.getSenderNormalizedDestination(); - details.append('\n'); - details.append(res.getString(R.string.from_label)); - details.append(!TextUtils.isEmpty(rawSender) ? rawSender : - res.getString(R.string.hidden_sender_address)); - - // To: +1425xxxxxxx - final String rawRecipients = getRecipientParticipantString(participants, - data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId()); - if (!TextUtils.isEmpty(rawRecipients)) { - details.append('\n'); - details.append(res.getString(R.string.to_address_label)); - details.append(rawRecipients); - } - - // Sent: Tue 3:05PM - // or Received: Tue 3:05PM - appendSentOrReceivedTimestamp(res, details, data); - - // Subject: You're awesome - details.append('\n'); - details.append(res.getString(R.string.subject_label)); - if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) { - details.append(data.getMmsSubject()); - } - - // Priority: High/Normal/Low - details.append('\n'); - details.append(res.getString(R.string.priority_label)); - details.append(getPriorityDescription(res, data.getSmsPriority())); - - // Message size: 30 KB - if (data.getSmsMessageSize() > 0) { - details.append('\n'); - details.append(res.getString(R.string.message_size_label)); - details.append(Formatter.formatFileSize(context, data.getSmsMessageSize())); - } - - appendSimInfo(res, self, details); - - if (DebugUtils.isDebugEnabled()) { - appendDebugInfo(details, data); - } - - return details.toString(); - } - - private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details, - ConversationMessageData data) { - int labelId = -1; - if (data.getIsIncoming()) { - labelId = R.string.received_label; - } else if (data.getIsSendComplete()) { - labelId = R.string.sent_label; - } - if (labelId >= 0) { - details.append('\n'); - details.append(res.getString(labelId)); - details.append( - Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString()); - } - } - - @DoesNotRunOnMainThread - private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) { - // We grab the thread id from the database, so this needs to run in the background - Assert.isNotMainThread(); - details.append("\n\n"); - details.append("DEBUG"); - - details.append('\n'); - details.append("Message id: "); - details.append(data.getMessageId()); - - final String telephonyUri = data.getSmsMessageUri(); - details.append('\n'); - details.append("Telephony uri: "); - details.append(telephonyUri); - - final String conversationId = data.getConversationId(); - - if (conversationId == null) { - return; - } - - details.append('\n'); - details.append("Conversation id: "); - details.append(conversationId); - - final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(), - conversationId); - - details.append('\n'); - details.append("Conversation telephony thread id: "); - details.append(threadId); - - MmsMessage mms = null; - - if (data.getIsMms()) { - if (telephonyUri == null) { - return; - } - mms = MmsUtils.loadMms(Uri.parse(telephonyUri)); - if (mms == null) { - return; - } - - // We log the thread id again to check that they are internally consistent - final long mmsThreadId = mms.mThreadId; - details.append('\n'); - details.append("Telephony thread id: "); - details.append(mmsThreadId); - - // Log the MMS content location - final String mmsContentLocation = mms.mContentLocation; - details.append('\n'); - details.append("Content location URL: "); - details.append(mmsContentLocation); - } - - final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId); - if (recipientsString != null) { - details.append('\n'); - details.append("Thread recipient ids: "); - details.append(recipientsString); - } - - final List recipients = MmsUtils.getRecipientsByThread(threadId); - if (recipients != null) { - details.append('\n'); - details.append("Thread recipients: "); - details.append(recipients.toString()); - - if (mms != null) { - final String from = MmsUtils.getMmsSender(recipients, mms.getUri()); - details.append('\n'); - details.append("Sender: "); - details.append(from); - } - } - } - - private static String getRecipientParticipantString( - final ConversationParticipantsData participants, final String senderId, - final boolean addSelf, final String selfId) { - final StringBuilder recipients = new StringBuilder(); - for (final ParticipantData participant : participants) { - if (TextUtils.equals(participant.getId(), senderId)) { - // Don't add sender - continue; - } - if (participant.isSelf() && - (!participant.getId().equals(selfId) || !addSelf)) { - // For self participants, don't add the one that's not relevant to this message - // or if we are asked not to add self - continue; - } - final String phoneNumber = participant.getNormalizedDestination(); - // Don't add empty number. This should not happen. But if that happens - // we should not add it. - if (!TextUtils.isEmpty(phoneNumber)) { - if (recipients.length() > 0) { - recipients.append(RECIPIENT_SEPARATOR); - } - recipients.append(phoneNumber); - } - } - return recipients.toString(); - } - - /** - * Convert the numeric mms priority into a human-readable string - * @param res - * @param priorityValue coded PduHeader priority - * @return string representation of the priority - */ - private static String getPriorityDescription(final Resources res, final int priorityValue) { - switch(priorityValue) { - case PduHeaders.PRIORITY_HIGH: - return res.getString(R.string.priority_high); - case PduHeaders.PRIORITY_LOW: - return res.getString(R.string.priority_low); - case PduHeaders.PRIORITY_NORMAL: - default: - return res.getString(R.string.priority_normal); - } - } - - private static void appendSimInfo(final Resources res, - final ParticipantData self, final StringBuilder outString) { - if (!OsUtil.isAtLeastL_MR1() - || self == null - || PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) { - return; - } - // The appended SIM info would look like: - // SIM: SUB 01 - // or SIM: SIM 1 - // or SIM: Unknown - Assert.isTrue(self.isSelf()); - outString.append('\n'); - outString.append(res.getString(R.string.sim_label)); - if (self.isActiveSubscription() && !self.isDefaultSelf()) { - final String subscriptionName = self.getSubscriptionName(); - if (TextUtils.isEmpty(subscriptionName)) { - outString.append(res.getString(R.string.sim_slot_identifier, - self.getDisplaySlotId())); - } else { - outString.append(subscriptionName); - } - } - } -} diff --git a/src/com/android/messaging/ui/conversation/SimIconView.java b/src/com/android/messaging/ui/conversation/SimIconView.java deleted file mode 100644 index e2e446c..0000000 --- a/src/com/android/messaging/ui/conversation/SimIconView.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.graphics.Outline; -import android.net.Uri; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; - -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.util.Assert; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.OsUtil; - -/** - * Shows SIM avatar icon in the SIM switcher / Self-send button. - */ -public class SimIconView extends ContactIconView { - public SimIconView(Context context, AttributeSet attrs) { - super(context, attrs); - if (OsUtil.isAtLeastL()) { - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View v, Outline outline) { - outline.setOval(0, 0, v.getWidth(), v.getHeight()); - } - }); - } - } - - @Override - protected void maybeInitializeOnClickListener() { - // TODO: SIM icon view shouldn't consume or handle clicks, but it should if - // this is the send button for the only SIM in the device or if MSIM is not supported. - } -} diff --git a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java b/src/com/android/messaging/ui/conversation/SimSelectorItemView.java deleted file mode 100644 index 3058d31..0000000 --- a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.util.Assert; - -/** - * Shows a view for a SIM in the SIM selector. - */ -public class SimSelectorItemView extends LinearLayout { - public interface HostInterface { - void onSimItemClicked(SubscriptionListEntry item); - } - - private SubscriptionListEntry mData; - private TextView mNameTextView; - private TextView mDetailsTextView; - private SimIconView mSimIconView; - private HostInterface mHost; - - public SimSelectorItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - mNameTextView = (TextView) findViewById(R.id.name); - mDetailsTextView = (TextView) findViewById(R.id.details); - mSimIconView = (SimIconView) findViewById(R.id.sim_icon); - setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mHost.onSimItemClicked(mData); - } - }); - } - - public void bind(final SubscriptionListEntry simEntry) { - Assert.notNull(simEntry); - mData = simEntry; - updateViewAppearance(); - } - - public void setHostInterface(final HostInterface host) { - mHost = host; - } - - private void updateViewAppearance() { - Assert.notNull(mData); - final String displayName = mData.displayName; - if (TextUtils.isEmpty(displayName)) { - mNameTextView.setVisibility(GONE); - } else { - mNameTextView.setVisibility(VISIBLE); - mNameTextView.setText(displayName); - } - - final String details = mData.displayDestination; - if (TextUtils.isEmpty(details)) { - mDetailsTextView.setVisibility(GONE); - } else { - mDetailsTextView.setVisibility(VISIBLE); - mDetailsTextView.setText(details); - } - - mSimIconView.setImageResourceUri(mData.iconUri); - } -} diff --git a/src/com/android/messaging/ui/conversation/SimSelectorView.java b/src/com/android/messaging/ui/conversation/SimSelectorView.java deleted file mode 100644 index b07ff19..0000000 --- a/src/com/android/messaging/ui/conversation/SimSelectorView.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.ArrayAdapter; -import android.widget.FrameLayout; -import android.widget.ListView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Displays a SIM selector above the compose message view and overlays the message list. - */ -public class SimSelectorView extends FrameLayout implements SimSelectorItemView.HostInterface { - public interface SimSelectorViewListener { - void onSimItemClicked(SubscriptionListEntry item); - void onSimSelectorVisibilityChanged(boolean visible); - } - - private ListView mSimListView; - private final SimSelectorAdapter mAdapter; - private boolean mShow; - private SimSelectorViewListener mListener; - private int mItemLayoutId; - - public SimSelectorView(Context context, AttributeSet attrs) { - super(context, attrs); - mAdapter = new SimSelectorAdapter(getContext()); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mSimListView = (ListView) findViewById(R.id.sim_list); - mSimListView.setAdapter(mAdapter); - - // Clicking anywhere outside the switcher list should dismiss. - setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - showOrHide(false, true); - } - }); - } - - public void bind(final SubscriptionListData data) { - mAdapter.bindData(data.getActiveSubscriptionEntriesExcludingDefault()); - } - - public void setItemLayoutId(final int layoutId) { - mItemLayoutId = layoutId; - } - - public void setListener(final SimSelectorViewListener listener) { - mListener = listener; - } - - public void toggleVisibility() { - showOrHide(!mShow, true); - } - - public void showOrHide(final boolean show, final boolean animate) { - final boolean oldShow = mShow; - mShow = show && mAdapter.getCount() > 1; - if (oldShow != mShow) { - if (mListener != null) { - mListener.onSimSelectorVisibilityChanged(mShow); - } - - if (animate) { - // Fade in the background pane. - setVisibility(VISIBLE); - setAlpha(mShow ? 0.0f : 1.0f); - animate().alpha(mShow ? 1.0f : 0.0f) - .setDuration(UiUtils.REVEAL_ANIMATION_DURATION) - .withEndAction(new Runnable() { - @Override - public void run() { - setAlpha(1.0f); - setVisibility(mShow ? VISIBLE : GONE); - } - }); - } else { - setVisibility(mShow ? VISIBLE : GONE); - } - - // Slide in the SIM selector list via a translate animation. - mSimListView.setVisibility(mShow ? VISIBLE : GONE); - if (animate) { - mSimListView.clearAnimation(); - final TranslateAnimation translateAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, mShow ? 1.0f : 0.0f, - Animation.RELATIVE_TO_SELF, mShow ? 0.0f : 1.0f); - translateAnimation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR); - translateAnimation.setDuration(UiUtils.REVEAL_ANIMATION_DURATION); - mSimListView.startAnimation(translateAnimation); - } - } - } - - /** - * An adapter that takes a list of SubscriptionListEntry and displays them as a list of - * available SIMs in the SIM selector. - */ - private class SimSelectorAdapter extends ArrayAdapter { - public SimSelectorAdapter(final Context context) { - super(context, R.layout.sim_selector_item_view, new ArrayList()); - } - - public void bindData(final List newList) { - clear(); - addAll(newList); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - SimSelectorItemView itemView; - if (convertView != null && convertView instanceof SimSelectorItemView) { - itemView = (SimSelectorItemView) convertView; - } else { - final LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - itemView = (SimSelectorItemView) inflater.inflate(mItemLayoutId, - parent, false); - itemView.setHostInterface(SimSelectorView.this); - } - itemView.bind(getItem(position)); - return itemView; - } - } - - @Override - public void onSimItemClicked(SubscriptionListEntry item) { - mListener.onSimItemClicked(item); - showOrHide(false, true); - } - - public boolean isOpen() { - return mShow; - } -} diff --git a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java deleted file mode 100644 index dbbbb15..0000000 --- a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.datamodel.action.DeleteConversationAction; -import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction; -import com.android.messaging.datamodel.action.UpdateConversationOptionsAction; -import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.SnackBar; -import com.android.messaging.ui.SnackBarInteraction; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.contact.AddContactsConfirmationDialog; -import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost; -import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.Trace; -import com.android.messaging.util.UiUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.annotation.Nullable; - -/** - * Base class for many Conversation List activities. This will handle the common actions of multi - * select and common launching of intents. - */ -public abstract class AbstractConversationListActivity extends BugleActionBarActivity - implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener { - - private static final int REQUEST_SET_DEFAULT_SMS_APP = 1; - - protected ConversationListFragment mConversationListFragment; - - @Override - public void onAttachFragment(final Fragment fragment) { - Trace.beginSection("AbstractConversationListActivity.onAttachFragment"); - // Fragment could be debug dialog - if (fragment instanceof ConversationListFragment) { - mConversationListFragment = (ConversationListFragment) fragment; - mConversationListFragment.setHost(this); - } - Trace.endSection(); - } - - @Override - public void onBackPressed() { - // If action mode is active dismiss it - if (getActionMode() != null) { - dismissActionMode(); - return; - } - super.onBackPressed(); - } - - protected void startMultiSelectActionMode() { - startActionMode(new MultiSelectActionModeCallback(this)); - } - - protected void exitMultiSelectState() { - mConversationListFragment.showFab(); - dismissActionMode(); - mConversationListFragment.updateUi(); - } - - protected boolean isInConversationListSelectMode() { - return getActionModeCallback() instanceof MultiSelectActionModeCallback; - } - - @Override - public boolean isSelectionMode() { - return isInConversationListSelectMode(); - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - } - - @Override - public void onActionBarDelete(final Collection conversations) { - if (!PhoneUtils.getDefault().isDefaultSmsApp()) { - // TODO: figure out a good way to combine this with the implementation in - // ConversationFragment doing similar things - final Activity activity = this; - UiUtils.showSnackBarWithCustomAction(this, - getWindow().getDecorView().getRootView(), - getString(R.string.requires_default_sms_app), - SnackBar.Action.createCustomAction(new Runnable() { - @Override - public void run() { - final Intent intent = - UIIntents.get().getChangeDefaultSmsAppIntent(activity); - startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP); - } - }, - getString(R.string.requires_default_sms_change_button)), - null /* interactions */, - null /* placement */); - return; - } - - new AlertDialog.Builder(this) - .setTitle(getResources().getQuantityString( - R.plurals.delete_conversations_confirmation_dialog_title, - conversations.size())) - .setPositiveButton(R.string.delete_conversation_confirmation_button, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - for (final SelectedConversation conversation : conversations) { - DeleteConversationAction.deleteConversation( - conversation.conversationId, - conversation.timestamp); - } - exitMultiSelectState(); - } - }) - .setNegativeButton(R.string.delete_conversation_decline_button, null) - .show(); - } - - @Override - public void onActionBarArchive(final Iterable conversations, - final boolean isToArchive) { - final ArrayList conversationIds = new ArrayList(); - for (final SelectedConversation conversation : conversations) { - final String conversationId = conversation.conversationId; - conversationIds.add(conversationId); - if (isToArchive) { - UpdateConversationArchiveStatusAction.archiveConversation(conversationId); - } else { - UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId); - } - } - - final Runnable undoRunnable = new Runnable() { - @Override - public void run() { - for (final String conversationId : conversationIds) { - if (isToArchive) { - UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId); - } else { - UpdateConversationArchiveStatusAction.archiveConversation(conversationId); - } - } - } - }; - - final int textId = - isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message; - final String message = getResources().getString(textId, conversationIds.size()); - UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable, - SnackBar.Action.SNACK_BAR_UNDO, - mConversationListFragment.getSnackBarInteractions()); - exitMultiSelectState(); - } - - @Override - public void onActionBarNotification(final Iterable conversations, - final boolean isNotificationOn) { - for (final SelectedConversation conversation : conversations) { - UpdateConversationOptionsAction.enableConversationNotifications( - conversation.conversationId, isNotificationOn); - } - - final int textId = isNotificationOn ? - R.string.notification_on_toast_message : R.string.notification_off_toast_message; - final String message = getResources().getString(textId, 1); - UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, - null /* undoRunnable */, - SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions()); - exitMultiSelectState(); - } - - @Override - public void onActionBarAddContact(final SelectedConversation conversation) { - final Uri avatarUri; - if (conversation.icon != null) { - avatarUri = Uri.parse(conversation.icon); - } else { - avatarUri = null; - } - final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog( - this, avatarUri, conversation.otherParticipantNormalizedDestination); - dialog.show(); - exitMultiSelectState(); - } - - @Override - public void onActionBarBlock(final SelectedConversation conversation) { - final Resources res = getResources(); - new AlertDialog.Builder(this) - .setTitle(res.getString(R.string.block_confirmation_title, - conversation.otherParticipantNormalizedDestination)) - .setMessage(res.getString(R.string.block_confirmation_message)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface arg0, final int arg1) { - final Context context = AbstractConversationListActivity.this; - final View listView = findViewById(android.R.id.list); - final List interactions = - mConversationListFragment.getSnackBarInteractions(); - final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener - undoListener = - new UpdateDestinationBlockedActionSnackBar( - context, listView, null /* undoRunnable */, - interactions); - final Runnable undoRunnable = new Runnable() { - @Override - public void run() { - UpdateDestinationBlockedAction.updateDestinationBlocked( - conversation.otherParticipantNormalizedDestination, false, - conversation.conversationId, - undoListener); - } - }; - final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener - listener = new UpdateDestinationBlockedActionSnackBar( - context, listView, undoRunnable, interactions); - UpdateDestinationBlockedAction.updateDestinationBlocked( - conversation.otherParticipantNormalizedDestination, true, - conversation.conversationId, - listener); - exitMultiSelectState(); - } - }) - .create() - .show(); - } - - @Override - public void onConversationClick(final ConversationListData listData, - final ConversationListItemData conversationListItemData, - final boolean isLongClick, - final ConversationListItemView conversationView) { - if (isLongClick && !isInConversationListSelectMode()) { - startMultiSelectActionMode(); - } - - if (isInConversationListSelectMode()) { - final MultiSelectActionModeCallback multiSelectActionMode = - (MultiSelectActionModeCallback) getActionModeCallback(); - multiSelectActionMode.toggleSelect(listData, conversationListItemData); - mConversationListFragment.updateUi(); - } else { - final String conversationId = conversationListItemData.getConversationId(); - Bundle sceneTransitionAnimationOptions = null; - boolean hasCustomTransitions = false; - - UIIntents.get().launchConversationActivity( - this, conversationId, null, - sceneTransitionAnimationOptions, - hasCustomTransitions); - } - } - - @Override - public void onCreateConversationClick() { - UIIntents.get().launchCreateNewConversationActivity(this, null); - } - - - @Override - public boolean isConversationSelected(final String conversationId) { - return isInConversationListSelectMode() && - ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected( - conversationId); - } - - public void onActionBarDebug() { - DebugUtils.showDebugOptions(this); - } - - private static class UpdateDestinationBlockedActionSnackBar - implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener { - private final Context mContext; - private final View mParentView; - private final Runnable mUndoRunnable; - private final List mInteractions; - - UpdateDestinationBlockedActionSnackBar(final Context context, - @NonNull final View parentView, @Nullable final Runnable undoRunnable, - @Nullable List interactions) { - mContext = context; - mParentView = parentView; - mUndoRunnable = undoRunnable; - mInteractions = interactions; - } - - @Override - public void onUpdateDestinationBlockedAction( - final UpdateDestinationBlockedAction action, - final boolean success, final boolean block, - final String destination) { - if (success) { - final int messageId = block ? R.string.blocked_toast_message - : R.string.unblocked_toast_message; - final String message = mContext.getResources().getString(messageId, 1); - UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable, - SnackBar.Action.SNACK_BAR_UNDO, mInteractions); - } - } - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java deleted file mode 100644 index 366c7d3..0000000 --- a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.util.DebugUtils; - -public class ArchivedConversationListActivity extends AbstractConversationListActivity { - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final ConversationListFragment fragment = - ConversationListFragment.createArchivedConversationListFragment(); - getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit(); - invalidateActionBar(); - } - - protected void updateActionBar(ActionBar actionBar) { - actionBar.setTitle(getString(R.string.archived_activity_title)); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setBackgroundDrawable(new ColorDrawable( - getResources().getColor( - R.color.archived_conversation_action_bar_background_color_dark))); - actionBar.show(); - super.updateActionBar(actionBar); - } - - @Override - public void onBackPressed() { - if (isInConversationListSelectMode()) { - exitMultiSelectState(); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (super.onCreateOptionsMenu(menu)) { - return true; - } - getMenuInflater().inflate(R.menu.archived_conversation_list_menu, menu); - final MenuItem item = menu.findItem(R.id.action_debug_options); - if (item != null) { - final boolean enableDebugItems = DebugUtils.isDebugEnabled(); - item.setVisible(enableDebugItems).setEnabled(enableDebugItems); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch(menuItem.getItemId()) { - case R.id.action_debug_options: - onActionBarDebug(); - return true; - case android.R.id.home: - onActionBarHome(); - return true; - default: - return super.onOptionsItemSelected(menuItem); - } - } - - @Override - public void onActionBarHome() { - onBackPressed(); - } - - @Override - public boolean isSwipeAnimatable() { - return false; - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java deleted file mode 100644 index f8abe81..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.Trace; - -public class ConversationListActivity extends AbstractConversationListActivity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - Trace.beginSection("ConversationListActivity.onCreate"); - super.onCreate(savedInstanceState); - setContentView(R.layout.conversation_list_activity); - Trace.endSection(); - invalidateActionBar(); - } - - @Override - protected void updateActionBar(final ActionBar actionBar) { - actionBar.setTitle(getString(R.string.app_name)); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); - actionBar.setBackgroundDrawable(new ColorDrawable( - getResources().getColor(R.color.action_bar_background_color))); - actionBar.show(); - super.updateActionBar(actionBar); - } - - @Override - public void onResume() { - super.onResume(); - // Invalidate the menu as items that are based on settings may have changed - // while not in the app (e.g. Talkback enabled/disable affects new conversation - // button) - supportInvalidateOptionsMenu(); - } - - @Override - public void onBackPressed() { - if (isInConversationListSelectMode()) { - exitMultiSelectState(); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - if (super.onCreateOptionsMenu(menu)) { - return true; - } - getMenuInflater().inflate(R.menu.conversation_list_fragment_menu, menu); - final MenuItem item = menu.findItem(R.id.action_debug_options); - if (item != null) { - final boolean enableDebugItems = DebugUtils.isDebugEnabled(); - item.setVisible(enableDebugItems).setEnabled(enableDebugItems); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem menuItem) { - switch(menuItem.getItemId()) { - case R.id.action_start_new_conversation: - onActionBarStartNewConversation(); - return true; - case R.id.action_settings: - onActionBarSettings(); - return true; - case R.id.action_debug_options: - onActionBarDebug(); - return true; - case R.id.action_show_archived: - onActionBarArchived(); - return true; - case R.id.action_show_blocked_contacts: - onActionBarBlockedParticipants(); - return true; - } - return super.onOptionsItemSelected(menuItem); - } - - @Override - public void onActionBarHome() { - exitMultiSelectState(); - } - - public void onActionBarStartNewConversation() { - UIIntents.get().launchCreateNewConversationActivity(this, null); - } - - public void onActionBarSettings() { - UIIntents.get().launchSettingsActivity(this); - } - - public void onActionBarBlockedParticipants() { - UIIntents.get().launchBlockedParticipantsActivity(this); - } - - public void onActionBarArchived() { - UIIntents.get().launchArchivedConversationsActivity(this); - } - - @Override - public boolean isSwipeAnimatable() { - return !isInConversationListSelectMode(); - } - - @Override - public void onWindowFocusChanged(final boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - final ConversationListFragment conversationListFragment = - (ConversationListFragment) getFragmentManager().findFragmentById( - R.id.conversation_list_fragment); - // When the screen is turned on, the last used activity gets resumed, but it gets - // window focus only after the lock screen is unlocked. - if (hasFocus && conversationListFragment != null) { - conversationListFragment.setScrolledToNewestConversationIfNeeded(); - } - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java b/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java deleted file mode 100644 index 629c4ae..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.ui.CursorRecyclerAdapter; -import com.android.messaging.ui.conversationlist.ConversationListItemView.HostInterface; - -/** - * Provides an interface to expose Conversation List Cursor data to a UI widget like a ListView. - */ -public class ConversationListAdapter - extends CursorRecyclerAdapter { - - private final ConversationListItemView.HostInterface mClivHostInterface; - - public ConversationListAdapter(final Context context, final Cursor cursor, - final ConversationListItemView.HostInterface clivHostInterface) { - super(context, cursor, 0); - mClivHostInterface = clivHostInterface; - setHasStableIds(true); - } - - /** - * @see com.android.messaging.ui.CursorRecyclerAdapter#bindViewHolder( - * android.support.v7.widget.RecyclerView.ViewHolder, android.content.Context, - * android.database.Cursor) - */ - @Override - public void bindViewHolder(final ConversationListViewHolder holder, final Context context, - final Cursor cursor) { - final ConversationListItemView conversationListItemView = holder.mView; - conversationListItemView.bind(cursor, mClivHostInterface); - } - - @Override - public ConversationListViewHolder createViewHolder(final Context context, - final ViewGroup parent, final int viewType) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - final ConversationListItemView itemView = - (ConversationListItemView) layoutInflater.inflate( - R.layout.conversation_list_item_view, null); - return new ConversationListViewHolder(itemView); - } - - /** - * ViewHolder that holds a ConversationListItemView. - */ - public static class ConversationListViewHolder extends RecyclerView.ViewHolder { - final ConversationListItemView mView; - - public ConversationListViewHolder(final ConversationListItemView itemView) { - super(itemView); - mView = itemView; - } - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java b/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java deleted file mode 100644 index 2f868d4..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewGroupCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.ViewPropertyAnimator; -import android.view.accessibility.AccessibilityManager; -import android.widget.AbsListView; -import android.widget.ImageView; - -import com.android.messaging.R; -import com.android.messaging.annotation.VisibleForAnimation; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.ui.BugleAnimationTags; -import com.android.messaging.ui.ListEmptyView; -import com.android.messaging.ui.SnackBarInteraction; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.List; - -/** - * Shows a list of conversations. - */ -public class ConversationListFragment extends Fragment implements ConversationListDataListener, - ConversationListItemView.HostInterface { - private static final String BUNDLE_ARCHIVED_MODE = "archived_mode"; - private static final String BUNDLE_FORWARD_MESSAGE_MODE = "forward_message_mode"; - private static final boolean VERBOSE = false; - - private MenuItem mShowBlockedMenuItem; - private boolean mArchiveMode; - private boolean mBlockedAvailable; - private boolean mForwardMessageMode; - - public interface ConversationListFragmentHost { - public void onConversationClick(final ConversationListData listData, - final ConversationListItemData conversationListItemData, - final boolean isLongClick, - final ConversationListItemView conversationView); - public void onCreateConversationClick(); - public boolean isConversationSelected(final String conversationId); - public boolean isSwipeAnimatable(); - public boolean isSelectionMode(); - public boolean hasWindowFocus(); - } - - private ConversationListFragmentHost mHost; - private RecyclerView mRecyclerView; - private ImageView mStartNewConversationButton; - private ListEmptyView mEmptyListMessageView; - private ConversationListAdapter mAdapter; - - // Saved Instance State Data - only for temporal data which is nice to maintain but not - // critical for correctness. - private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY = - "conversationListViewState"; - private Parcelable mListState; - - @VisibleForTesting - final Binding mListBinding = BindingBase.createBinding(this); - - public static ConversationListFragment createArchivedConversationListFragment() { - return createConversationListFragment(BUNDLE_ARCHIVED_MODE); - } - - public static ConversationListFragment createForwardMessageConversationListFragment() { - return createConversationListFragment(BUNDLE_FORWARD_MESSAGE_MODE); - } - - public static ConversationListFragment createConversationListFragment(String modeKeyName) { - final ConversationListFragment fragment = new ConversationListFragment(); - final Bundle bundle = new Bundle(); - bundle.putBoolean(modeKeyName, true); - fragment.setArguments(bundle); - return fragment; - } - - /** - * {@inheritDoc} from Fragment - */ - @Override - public void onCreate(final Bundle bundle) { - super.onCreate(bundle); - mListBinding.getData().init(getLoaderManager(), mListBinding); - mAdapter = new ConversationListAdapter(getActivity(), null, this); - } - - @Override - public void onResume() { - super.onResume(); - - Assert.notNull(mHost); - setScrolledToNewestConversationIfNeeded(); - - updateUi(); - } - - public void setScrolledToNewestConversationIfNeeded() { - if (!mArchiveMode - && !mForwardMessageMode - && isScrolledToFirstConversation() - && mHost.hasWindowFocus()) { - mListBinding.getData().setScrolledToNewestConversation(true); - } - } - - private boolean isScrolledToFirstConversation() { - int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()) - .findFirstCompletelyVisibleItemPosition(); - return firstItemPosition == 0; - } - - /** - * {@inheritDoc} from Fragment - */ - @Override - public void onDestroy() { - super.onDestroy(); - mListBinding.unbind(); - mHost = null; - } - - /** - * {@inheritDoc} from Fragment - */ - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment, - container, false); - mRecyclerView = (RecyclerView) rootView.findViewById(android.R.id.list); - mEmptyListMessageView = (ListEmptyView) rootView.findViewById(R.id.no_conversations_view); - mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list); - // The default behavior for default layout param generation by LinearLayoutManager is to - // provide width and height of WRAP_CONTENT, but this is not desirable for - // ConversationListFragment; the view in each row should be a width of MATCH_PARENT so that - // the entire row is tappable. - final Activity activity = getActivity(); - final LinearLayoutManager manager = new LinearLayoutManager(activity) { - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - }; - mRecyclerView.setLayoutManager(manager); - mRecyclerView.setHasFixedSize(true); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { - int mCurrentState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; - - @Override - public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) { - if (mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL - || mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { - ImeUtil.get().hideImeKeyboard(getActivity(), mRecyclerView); - } - - if (isScrolledToFirstConversation()) { - setScrolledToNewestConversationIfNeeded(); - } else { - mListBinding.getData().setScrolledToNewestConversation(false); - } - } - - @Override - public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) { - mCurrentState = newState; - } - }); - mRecyclerView.addOnItemTouchListener(new ConversationListSwipeHelper(mRecyclerView)); - - if (savedInstanceState != null) { - mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY); - } - - mStartNewConversationButton = (ImageView) rootView.findViewById( - R.id.start_new_conversation_button); - if (mArchiveMode) { - mStartNewConversationButton.setVisibility(View.GONE); - } else { - mStartNewConversationButton.setVisibility(View.VISIBLE); - mStartNewConversationButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View clickView) { - mHost.onCreateConversationClick(); - } - }); - } - ViewCompat.setTransitionName(mStartNewConversationButton, BugleAnimationTags.TAG_FABICON); - - // The root view has a non-null background, which by default is deemed by the framework - // to be a "transition group," where all child views are animated together during an - // activity transition. However, we want each individual items in the recycler view to - // show explode animation themselves, so we explicitly tag the root view to be a non-group. - ViewGroupCompat.setTransitionGroup(rootView, false); - - setHasOptionsMenu(true); - return rootView; - } - - @Override - public void onAttach(final Activity activity) { - super.onAttach(activity); - if (VERBOSE) { - LogUtil.v(LogUtil.BUGLE_TAG, "Attaching List"); - } - final Bundle arguments = getArguments(); - if (arguments != null) { - mArchiveMode = arguments.getBoolean(BUNDLE_ARCHIVED_MODE, false); - mForwardMessageMode = arguments.getBoolean(BUNDLE_FORWARD_MESSAGE_MODE, false); - } - mListBinding.bind(DataModel.get().createConversationListData(activity, this, mArchiveMode)); - } - - - @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - if (mListState != null) { - outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState); - } - } - - @Override - public void onPause() { - super.onPause(); - mListState = mRecyclerView.getLayoutManager().onSaveInstanceState(); - mListBinding.getData().setScrolledToNewestConversation(false); - } - - /** - * Call this immediately after attaching the fragment - */ - public void setHost(final ConversationListFragmentHost host) { - Assert.isNull(mHost); - mHost = host; - } - - @Override - public void onConversationListCursorUpdated(final ConversationListData data, - final Cursor cursor) { - mListBinding.ensureBound(data); - final Cursor oldCursor = mAdapter.swapCursor(cursor); - updateEmptyListUi(cursor == null || cursor.getCount() == 0); - if (mListState != null && cursor != null && oldCursor == null) { - mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState); - } - } - - @Override - public void setBlockedParticipantsAvailable(final boolean blockedAvailable) { - mBlockedAvailable = blockedAvailable; - if (mShowBlockedMenuItem != null) { - mShowBlockedMenuItem.setVisible(blockedAvailable); - } - } - - public void updateUi() { - mAdapter.notifyDataSetChanged(); - } - - @Override - public void onPrepareOptionsMenu(final Menu menu) { - super.onPrepareOptionsMenu(menu); - final MenuItem startNewConversationMenuItem = - menu.findItem(R.id.action_start_new_conversation); - if (startNewConversationMenuItem != null) { - // It is recommended for the Floating Action button functionality to be duplicated as a - // menu - AccessibilityManager accessibilityManager = (AccessibilityManager) - getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE); - startNewConversationMenuItem.setVisible(accessibilityManager - .isTouchExplorationEnabled()); - } - - final MenuItem archive = menu.findItem(R.id.action_show_archived); - if (archive != null) { - archive.setVisible(true); - } - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (!isAdded()) { - // Guard against being called before we're added to the activity - return; - } - - mShowBlockedMenuItem = menu.findItem(R.id.action_show_blocked_contacts); - if (mShowBlockedMenuItem != null) { - mShowBlockedMenuItem.setVisible(mBlockedAvailable); - } - } - - /** - * {@inheritDoc} from ConversationListItemView.HostInterface - */ - @Override - public void onConversationClicked(final ConversationListItemData conversationListItemData, - final boolean isLongClick, final ConversationListItemView conversationView) { - final ConversationListData listData = mListBinding.getData(); - mHost.onConversationClick(listData, conversationListItemData, isLongClick, - conversationView); - } - - /** - * {@inheritDoc} from ConversationListItemView.HostInterface - */ - @Override - public boolean isConversationSelected(final String conversationId) { - return mHost.isConversationSelected(conversationId); - } - - @Override - public boolean isSwipeAnimatable() { - return mHost.isSwipeAnimatable(); - } - - // Show and hide empty list UI as needed with appropriate text based on view specifics - private void updateEmptyListUi(final boolean isEmpty) { - if (isEmpty) { - int emptyListText; - if (!mListBinding.getData().getHasFirstSyncCompleted()) { - emptyListText = R.string.conversation_list_first_sync_text; - } else if (mArchiveMode) { - emptyListText = R.string.archived_conversation_list_empty_text; - } else { - emptyListText = R.string.conversation_list_empty_text; - } - mEmptyListMessageView.setTextHint(emptyListText); - mEmptyListMessageView.setVisibility(View.VISIBLE); - mEmptyListMessageView.setIsImageVisible(true); - mEmptyListMessageView.setIsVerticallyCentered(true); - } else { - mEmptyListMessageView.setVisibility(View.GONE); - } - } - - @Override - public List getSnackBarInteractions() { - final List interactions = new ArrayList(1); - final SnackBarInteraction fabInteraction = - new SnackBarInteraction.BasicSnackBarInteraction(mStartNewConversationButton); - interactions.add(fabInteraction); - return interactions; - } - - private ViewPropertyAnimator getNormalizedFabAnimator() { - return mStartNewConversationButton.animate() - .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR) - .setDuration(getActivity().getResources().getInteger( - R.integer.fab_animation_duration_ms)); - } - - public ViewPropertyAnimator dismissFab() { - // To prevent clicking while animating. - mStartNewConversationButton.setEnabled(false); - final MarginLayoutParams lp = - (MarginLayoutParams) mStartNewConversationButton.getLayoutParams(); - final float fabWidthWithLeftRightMargin = mStartNewConversationButton.getWidth() - + lp.leftMargin + lp.rightMargin; - final int direction = AccessibilityUtil.isLayoutRtl(mStartNewConversationButton) ? -1 : 1; - return getNormalizedFabAnimator().translationX(direction * fabWidthWithLeftRightMargin); - } - - public ViewPropertyAnimator showFab() { - return getNormalizedFabAnimator().translationX(0).withEndAction(new Runnable() { - @Override - public void run() { - // Re-enable clicks after the animation. - mStartNewConversationButton.setEnabled(true); - } - }); - } - - public View getHeroElementForTransition() { - return mArchiveMode ? null : mStartNewConversationButton; - } - - @VisibleForAnimation - public RecyclerView getRecyclerView() { - return mRecyclerView; - } - - @Override - public void startFullScreenPhotoViewer( - final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri) { - UIIntents.get().launchFullScreenPhotoViewer( - getActivity(), initialPhoto, initialPhotoBounds, photosUri); - } - - @Override - public void startFullScreenVideoViewer(final Uri videoUri) { - UIIntents.get().launchFullScreenVideoViewer(getActivity(), videoUri); - } - - @Override - public boolean isSelectionMode() { - return mHost != null && mHost.isSelectionMode(); - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java deleted file mode 100644 index 7525182..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java +++ /dev/null @@ -1,643 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.net.Uri; -import android.support.v4.text.BidiFormatter; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLayoutChangeListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.annotation.VisibleForAnimation; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.AudioAttachmentView; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.ui.SnackBar; -import com.android.messaging.ui.SnackBarInteraction; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; -import com.android.messaging.util.Typefaces; -import com.android.messaging.util.UiUtils; -import com.android.messaging.util.UriUtil; - -import java.util.List; - -/** - * The view for a single entry in a conversation list. - */ -public class ConversationListItemView extends FrameLayout implements OnClickListener, - OnLongClickListener, OnLayoutChangeListener { - static final int UNREAD_SNIPPET_LINE_COUNT = 3; - static final int NO_UNREAD_SNIPPET_LINE_COUNT = 1; - private int mListItemReadColor; - private int mListItemUnreadColor; - private Typeface mListItemReadTypeface; - private Typeface mListItemUnreadTypeface; - private static String sPlusOneString; - private static String sPlusNString; - - public interface HostInterface { - boolean isConversationSelected(final String conversationId); - void onConversationClicked(final ConversationListItemData conversationListItemData, - boolean isLongClick, final ConversationListItemView conversationView); - boolean isSwipeAnimatable(); - List getSnackBarInteractions(); - void startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds, - final Uri photosUri); - void startFullScreenVideoViewer(final Uri videoUri); - boolean isSelectionMode(); - } - - private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - final String previewType = mData.getShowDraft() ? - mData.getDraftPreviewContentType() : mData.getPreviewContentType(); - Assert.isTrue(ContentType.isImageType(previewType) || - ContentType.isVideoType(previewType)); - - final Uri previewUri = mData.getShowDraft() ? - mData.getDraftPreviewUri() : mData.getPreviewUri(); - if (ContentType.isImageType(previewType)) { - final Uri imagesUri = mData.getShowDraft() ? - MessagingContentProvider.buildDraftImagesUri(mData.getConversationId()) : - MessagingContentProvider - .buildConversationImagesUri(mData.getConversationId()); - final Rect previewImageBounds = UiUtils.getMeasuredBoundsOnScreen(v); - mHostInterface.startFullScreenPhotoViewer( - previewUri, previewImageBounds, imagesUri); - } else { - mHostInterface.startFullScreenVideoViewer(previewUri); - } - } - }; - - private final ConversationListItemData mData; - - private int mAnimatingCount; - private ViewGroup mSwipeableContainer; - private ViewGroup mCrossSwipeBackground; - private ViewGroup mSwipeableContent; - private TextView mConversationNameView; - private TextView mSnippetTextView; - private TextView mSubjectTextView; - private TextView mTimestampTextView; - private ContactIconView mContactIconView; - private ImageView mContactCheckmarkView; - private ImageView mNotificationBellView; - private ImageView mFailedStatusIconView; - private ImageView mCrossSwipeArchiveLeftImageView; - private ImageView mCrossSwipeArchiveRightImageView; - private AsyncImageView mImagePreviewView; - private AudioAttachmentView mAudioAttachmentView; - private HostInterface mHostInterface; - - public ConversationListItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = new ConversationListItemData(); - final Resources res = context.getResources(); - } - - @Override - protected void onFinishInflate() { - mSwipeableContainer = (ViewGroup) findViewById(R.id.swipeableContainer); - mCrossSwipeBackground = (ViewGroup) findViewById(R.id.crossSwipeBackground); - mSwipeableContent = (ViewGroup) findViewById(R.id.swipeableContent); - mConversationNameView = (TextView) findViewById(R.id.conversation_name); - mSnippetTextView = (TextView) findViewById(R.id.conversation_snippet); - mSubjectTextView = (TextView) findViewById(R.id.conversation_subject); - mTimestampTextView = (TextView) findViewById(R.id.conversation_timestamp); - mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon); - mContactCheckmarkView = (ImageView) findViewById(R.id.conversation_checkmark); - mNotificationBellView = (ImageView) findViewById(R.id.conversation_notification_bell); - mFailedStatusIconView = (ImageView) findViewById(R.id.conversation_failed_status_icon); - mCrossSwipeArchiveLeftImageView = (ImageView) findViewById(R.id.crossSwipeArchiveIconLeft); - mCrossSwipeArchiveRightImageView = - (ImageView) findViewById(R.id.crossSwipeArchiveIconRight); - mImagePreviewView = (AsyncImageView) findViewById(R.id.conversation_image_preview); - mAudioAttachmentView = (AudioAttachmentView) findViewById(R.id.audio_attachment_view); - mConversationNameView.addOnLayoutChangeListener(this); - mSnippetTextView.addOnLayoutChangeListener(this); - - final Resources resources = getContext().getResources(); - mListItemReadColor = resources.getColor(R.color.conversation_list_item_read); - mListItemUnreadColor = resources.getColor(R.color.conversation_list_item_unread); - - mListItemReadTypeface = Typefaces.getRobotoNormal(); - mListItemUnreadTypeface = Typefaces.getRobotoBold(); - - if (OsUtil.isAtLeastL()) { - setTransitionGroup(true); - } - } - - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, - final int bottom, final int oldLeft, final int oldTop, final int oldRight, - final int oldBottom) { - if (v == mConversationNameView) { - setConversationName(); - } else if (v == mSnippetTextView) { - setSnippet(); - } else if (v == mSubjectTextView) { - setSubject(); - } - } - - private void setConversationName() { - if (mData.getIsRead() || mData.getShowDraft()) { - mConversationNameView.setTextColor(mListItemReadColor); - mConversationNameView.setTypeface(mListItemReadTypeface); - } else { - mConversationNameView.setTextColor(mListItemUnreadColor); - mConversationNameView.setTypeface(mListItemUnreadTypeface); - } - - final String conversationName = mData.getName(); - - // For group conversations, ellipsize the group members that do not fit - final CharSequence ellipsizedName = UiUtils.commaEllipsize( - conversationName, - mConversationNameView.getPaint(), - mConversationNameView.getMeasuredWidth(), - getPlusOneString(), - getPlusNString()); - // RTL : To format conversation name if it happens to be phone number. - final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - final String bidiFormattedName = bidiFormatter.unicodeWrap( - ellipsizedName.toString(), - TextDirectionHeuristicsCompat.LTR); - - mConversationNameView.setText(bidiFormattedName); - } - - private static String getPlusOneString() { - if (sPlusOneString == null) { - sPlusOneString = Factory.get().getApplicationContext().getResources() - .getString(R.string.plus_one); - } - return sPlusOneString; - } - - private static String getPlusNString() { - if (sPlusNString == null) { - sPlusNString = Factory.get().getApplicationContext().getResources() - .getString(R.string.plus_n); - } - return sPlusNString; - } - - private void setSubject() { - final String subjectText = mData.getShowDraft() ? - mData.getDraftSubject() : - MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject()); - if (!TextUtils.isEmpty(subjectText)) { - final String subjectPrepend = getResources().getString(R.string.subject_label); - mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText)); - mSubjectTextView.setVisibility(VISIBLE); - } else { - mSubjectTextView.setVisibility(GONE); - } - } - - private void setSnippet() { - mSnippetTextView.setText(getSnippetText()); - } - - // Resource Ids of content descriptions prefixes for different message status. - private static final int [][][] sPrimaryContentDescriptions = { - // 1:1 conversation - { - // Incoming message - { - R.string.one_on_one_incoming_failed_message_prefix, - R.string.one_on_one_incoming_successful_message_prefix - }, - // Outgoing message - { - R.string.one_on_one_outgoing_failed_message_prefix, - R.string.one_on_one_outgoing_successful_message_prefix, - R.string.one_on_one_outgoing_draft_message_prefix, - R.string.one_on_one_outgoing_sending_message_prefix, - } - }, - - // Group conversation - { - // Incoming message - { - R.string.group_incoming_failed_message_prefix, - R.string.group_incoming_successful_message_prefix, - }, - // Outgoing message - { - R.string.group_outgoing_failed_message_prefix, - R.string.group_outgoing_successful_message_prefix, - R.string.group_outgoing_draft_message_prefix, - R.string.group_outgoing_sending_message_prefix, - } - } - }; - - // Resource Id of the secondary part of the content description for an edge case of a message - // which is in both draft status and failed status. - private static final int sSecondaryContentDescription = - R.string.failed_message_content_description; - - // 1:1 versus group - private static final int CONV_TYPE_ONE_ON_ONE_INDEX = 0; - private static final int CONV_TYPE_ONE_GROUP_INDEX = 1; - // Direction - private static final int DIRECTION_INCOMING_INDEX = 0; - private static final int DIRECTION_OUTGOING_INDEX = 1; - // Message status - private static final int MESSAGE_STATUS_FAILED_INDEX = 0; - private static final int MESSAGE_STATUS_SUCCESSFUL_INDEX = 1; - private static final int MESSAGE_STATUS_DRAFT_INDEX = 2; - private static final int MESSAGE_STATUS_SENDING_INDEX = 3; - - private static final int WIDTH_FOR_ACCESSIBLE_CONVERSATION_NAME = 600; - - public static String buildContentDescription(final Resources resources, - final ConversationListItemData data, final TextPaint conversationNameViewPaint) { - int messageStatusIndex; - boolean outgoingSnippet = data.getIsMessageTypeOutgoing() || data.getShowDraft(); - if (outgoingSnippet) { - if (data.getShowDraft()) { - messageStatusIndex = MESSAGE_STATUS_DRAFT_INDEX; - } else if (data.getIsSendRequested()) { - messageStatusIndex = MESSAGE_STATUS_SENDING_INDEX; - } else { - messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX - : MESSAGE_STATUS_SUCCESSFUL_INDEX; - } - } else { - messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX - : MESSAGE_STATUS_SUCCESSFUL_INDEX; - } - - int resId = sPrimaryContentDescriptions - [data.getIsGroup() ? CONV_TYPE_ONE_GROUP_INDEX : CONV_TYPE_ONE_ON_ONE_INDEX] - [outgoingSnippet ? DIRECTION_OUTGOING_INDEX : DIRECTION_INCOMING_INDEX] - [messageStatusIndex]; - - final String snippetText = data.getShowDraft() ? - data.getDraftSnippetText() : data.getSnippetText(); - - final String conversationName = data.getName(); - String senderOrConvName = outgoingSnippet ? conversationName : data.getSnippetSenderName(); - - String primaryContentDescription = resources.getString(resId, senderOrConvName, - snippetText == null ? "" : snippetText, - data.getFormattedTimestamp(), - // This is used only for incoming group messages - conversationName); - String contentDescription = primaryContentDescription; - - // An edge case : for an outgoing message, it might be in both draft status and - // failed status. - if (outgoingSnippet && data.getShowDraft() && data.getIsFailedStatus()) { - StringBuilder contentDescriptionBuilder = new StringBuilder(); - contentDescriptionBuilder.append(primaryContentDescription); - - String secondaryContentDescription = - resources.getString(sSecondaryContentDescription); - contentDescriptionBuilder.append(" "); - contentDescriptionBuilder.append(secondaryContentDescription); - contentDescription = contentDescriptionBuilder.toString(); - } - return contentDescription; - } - - /** - * Fills in the data associated with this view. - * - * @param cursor The cursor from a ConversationList that this view is in, pointing to its - * entry. - */ - public void bind(final Cursor cursor, final HostInterface hostInterface) { - // Update our UI model - mHostInterface = hostInterface; - mData.bind(cursor); - - resetAnimatingState(); - - mSwipeableContainer.setOnClickListener(this); - mSwipeableContainer.setOnLongClickListener(this); - - final Resources resources = getContext().getResources(); - - int color; - final int maxLines; - final Typeface typeface; - final int typefaceStyle = mData.getShowDraft() ? Typeface.ITALIC : Typeface.NORMAL; - final String snippetText = getSnippetText(); - - if (mData.getIsRead() || mData.getShowDraft()) { - maxLines = TextUtils.isEmpty(snippetText) ? 0 : NO_UNREAD_SNIPPET_LINE_COUNT; - color = mListItemReadColor; - typeface = mListItemReadTypeface; - } else { - maxLines = TextUtils.isEmpty(snippetText) ? 0 : UNREAD_SNIPPET_LINE_COUNT; - color = mListItemUnreadColor; - typeface = mListItemUnreadTypeface; - } - - mSnippetTextView.setMaxLines(maxLines); - mSnippetTextView.setTextColor(color); - mSnippetTextView.setTypeface(typeface, typefaceStyle); - mSubjectTextView.setTextColor(color); - mSubjectTextView.setTypeface(typeface, typefaceStyle); - - setSnippet(); - setConversationName(); - setSubject(); - setContentDescription(buildContentDescription(resources, mData, - mConversationNameView.getPaint())); - - final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp(); - // don't show the error state unless we're the default sms app - if (mData.getIsFailedStatus() && isDefaultSmsApp) { - mTimestampTextView.setTextColor(resources.getColor(R.color.conversation_list_error)); - mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle); - int failureMessageId = R.string.message_status_download_failed; - if (mData.getIsMessageTypeOutgoing()) { - failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(mData.getMessageStatus(), - mData.getMessageRawTelephonyStatus()); - } - mTimestampTextView.setText(resources.getString(failureMessageId)); - } else if (mData.getShowDraft() - || mData.getMessageStatus() == MessageData.BUGLE_STATUS_OUTGOING_DRAFT - // also check for unknown status which we get because sometimes the conversation - // row is left with a latest_message_id of a no longer existing message and - // therefore the join values come back as null (or in this case zero). - || mData.getMessageStatus() == MessageData.BUGLE_STATUS_UNKNOWN) { - mTimestampTextView.setTextColor(mListItemReadColor); - mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle); - mTimestampTextView.setText(resources.getString( - R.string.conversation_list_item_view_draft_message)); - } else { - mTimestampTextView.setTextColor(mListItemReadColor); - mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle); - final String formattedTimestamp = mData.getFormattedTimestamp(); - if (mData.getIsSendRequested()) { - mTimestampTextView.setText(R.string.message_status_sending); - } else { - mTimestampTextView.setText(formattedTimestamp); - } - } - - final boolean isSelected = mHostInterface.isConversationSelected(mData.getConversationId()); - setSelected(isSelected); - Uri iconUri = null; - int contactIconVisibility = GONE; - int checkmarkVisiblity = GONE; - int failStatusVisiblity = GONE; - if (isSelected) { - checkmarkVisiblity = VISIBLE; - } else { - contactIconVisibility = VISIBLE; - // Only show the fail icon if it is not a group conversation. - // And also require that we be the default sms app. - if (mData.getIsFailedStatus() && !mData.getIsGroup() && isDefaultSmsApp) { - failStatusVisiblity = VISIBLE; - } - } - if (mData.getIcon() != null) { - iconUri = Uri.parse(mData.getIcon()); - } - mContactIconView.setImageResourceUri(iconUri, mData.getParticipantContactId(), - mData.getParticipantLookupKey(), mData.getOtherParticipantNormalizedDestination()); - mContactIconView.setVisibility(contactIconVisibility); - mContactIconView.setOnLongClickListener(this); - mContactIconView.setClickable(!mHostInterface.isSelectionMode()); - mContactIconView.setLongClickable(!mHostInterface.isSelectionMode()); - - mContactCheckmarkView.setVisibility(checkmarkVisiblity); - mFailedStatusIconView.setVisibility(failStatusVisiblity); - - final Uri previewUri = mData.getShowDraft() ? - mData.getDraftPreviewUri() : mData.getPreviewUri(); - final String previewContentType = mData.getShowDraft() ? - mData.getDraftPreviewContentType() : mData.getPreviewContentType(); - OnClickListener previewClickListener = null; - Uri previewImageUri = null; - int previewImageVisibility = GONE; - int audioPreviewVisiblity = GONE; - if (previewUri != null && !TextUtils.isEmpty(previewContentType)) { - if (ContentType.isAudioType(previewContentType)) { - mAudioAttachmentView.bind(previewUri, false); - audioPreviewVisiblity = VISIBLE; - } else if (ContentType.isVideoType(previewContentType)) { - previewImageUri = UriUtil.getUriForResourceId( - getContext(), R.drawable.ic_preview_play); - previewClickListener = fullScreenPreviewClickListener; - previewImageVisibility = VISIBLE; - } else if (ContentType.isImageType(previewContentType)) { - previewImageUri = previewUri; - previewClickListener = fullScreenPreviewClickListener; - previewImageVisibility = VISIBLE; - } - } - - final int imageSize = resources.getDimensionPixelSize( - R.dimen.conversation_list_image_preview_size); - mImagePreviewView.setImageResourceId( - new UriImageRequestDescriptor(previewImageUri, imageSize, imageSize, - true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */)); - mImagePreviewView.setOnLongClickListener(this); - mImagePreviewView.setVisibility(previewImageVisibility); - mImagePreviewView.setOnClickListener(previewClickListener); - mAudioAttachmentView.setOnLongClickListener(this); - mAudioAttachmentView.setVisibility(audioPreviewVisiblity); - - final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE; - mNotificationBellView.setVisibility(notificationBellVisiblity); - } - - public boolean isSwipeAnimatable() { - return mHostInterface.isSwipeAnimatable(); - } - - @VisibleForAnimation - public float getSwipeTranslationX() { - return mSwipeableContainer.getTranslationX(); - } - - @VisibleForAnimation - public void setSwipeTranslationX(final float translationX) { - mSwipeableContainer.setTranslationX(translationX); - if (translationX == 0) { - mCrossSwipeBackground.setVisibility(View.GONE); - mCrossSwipeArchiveLeftImageView.setVisibility(GONE); - mCrossSwipeArchiveRightImageView.setVisibility(GONE); - - mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT); - } else { - mCrossSwipeBackground.setVisibility(View.VISIBLE); - if (translationX > 0) { - mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE); - mCrossSwipeArchiveRightImageView.setVisibility(GONE); - } else { - mCrossSwipeArchiveLeftImageView.setVisibility(GONE); - mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE); - } - mSwipeableContainer.setBackgroundResource(R.drawable.swipe_shadow_drag); - } - } - - public void onSwipeComplete() { - final String conversationId = mData.getConversationId(); - UpdateConversationArchiveStatusAction.archiveConversation(conversationId); - - final Runnable undoRunnable = new Runnable() { - @Override - public void run() { - UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId); - } - }; - final String message = getResources().getString(R.string.archived_toast_message, 1); - UiUtils.showSnackBar(getContext(), getRootView(), message, undoRunnable, - SnackBar.Action.SNACK_BAR_UNDO, - mHostInterface.getSnackBarInteractions()); - } - - private void setShortAndLongClickable(final boolean clickable) { - setClickable(clickable); - setLongClickable(clickable); - } - - private void resetAnimatingState() { - mAnimatingCount = 0; - setShortAndLongClickable(true); - setSwipeTranslationX(0); - } - - /** - * Notifies this view that it is undergoing animation. This view should disable its click - * targets. - * - * The animating counter is used to reset the swipe controller when the counter becomes 0. A - * positive counter also makes the view not clickable. - */ - public final void setAnimating(final boolean animating) { - final int oldAnimatingCount = mAnimatingCount; - if (animating) { - mAnimatingCount++; - } else { - mAnimatingCount--; - if (mAnimatingCount < 0) { - mAnimatingCount = 0; - } - } - - if (mAnimatingCount == 0) { - // New count is 0. All animations ended. - setShortAndLongClickable(true); - } else if (oldAnimatingCount == 0) { - // New count is > 0. Waiting for some animations to end. - setShortAndLongClickable(false); - } - } - - public boolean isAnimating() { - return mAnimatingCount > 0; - } - - /** - * {@inheritDoc} from OnClickListener - */ - @Override - public void onClick(final View v) { - processClick(v, false); - } - - /** - * {@inheritDoc} from OnLongClickListener - */ - @Override - public boolean onLongClick(final View v) { - return processClick(v, true); - } - - private boolean processClick(final View v, final boolean isLongClick) { - Assert.isTrue(v == mSwipeableContainer || v == mContactIconView || v == mImagePreviewView); - Assert.notNull(mData.getName()); - - if (mHostInterface != null) { - mHostInterface.onConversationClicked(mData, isLongClick, this); - return true; - } - return false; - } - - public View getSwipeableContent() { - return mSwipeableContent; - } - - public View getContactIconView() { - return mContactIconView; - } - - private String getSnippetText() { - String snippetText = mData.getShowDraft() ? - mData.getDraftSnippetText() : mData.getSnippetText(); - final String previewContentType = mData.getShowDraft() ? - mData.getDraftPreviewContentType() : mData.getPreviewContentType(); - if (TextUtils.isEmpty(snippetText)) { - Resources resources = getResources(); - // Use the attachment type as a snippet so the preview doesn't look odd - if (ContentType.isAudioType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_audio_clip); - } else if (ContentType.isImageType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_picture); - } else if (ContentType.isVideoType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_video); - } else if (ContentType.isVCardType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_vcard); - } - } - return snippetText; - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java deleted file mode 100644 index 4988259..0000000 --- a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.Resources; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.OnItemTouchListener; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; - -import com.android.messaging.R; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; - -/** - * Animation and touch helper class for Conversation List swipe. - */ -public class ConversationListSwipeHelper implements OnItemTouchListener { - private static final int UNIT_SECONDS = 1000; - private static final boolean ANIMATING = true; - - private static final float ERROR_FACTOR_MULTIPLIER = 1.2f; - private static final float PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.4f; - private static final float FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.05f; - - private static final int SWIPE_DIRECTION_NONE = 0; - private static final int SWIPE_DIRECTION_LEFT = 1; - private static final int SWIPE_DIRECTION_RIGHT = 2; - - private final RecyclerView mRecyclerView; - private final long mDefaultRestoreAnimationDuration; - private final long mDefaultDismissAnimationDuration; - private final long mMaxTranslationAnimationDuration; - private final int mTouchSlop; - private final int mMinimumFlingVelocity; - private final int mMaximumFlingVelocity; - - /* Valid throughout a single gesture. */ - private VelocityTracker mVelocityTracker; - private float mInitialX; - private float mInitialY; - private boolean mIsSwiping; - private ConversationListItemView mListItemView; - - public ConversationListSwipeHelper(final RecyclerView recyclerView) { - mRecyclerView = recyclerView; - - final Context context = mRecyclerView.getContext(); - final Resources res = context.getResources(); - mDefaultRestoreAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - mDefaultDismissAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - mMaxTranslationAnimationDuration = res.getInteger(R.integer.swipe_duration_ms); - - final ViewConfiguration viewConfiguration = ViewConfiguration.get(context); - mTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); - mMaximumFlingVelocity = Math.min( - viewConfiguration.getScaledMaximumFlingVelocity(), - res.getInteger(R.integer.swipe_max_fling_velocity_px_per_s)); - mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); - } - - @Override - public boolean onInterceptTouchEvent(final RecyclerView recyclerView, final MotionEvent event) { - if (event.getPointerCount() > 1) { - // Ignore subsequent pointers. - return false; - } - - // We are not yet tracking a swipe gesture. Begin detection by spying on - // touch events bubbling down to our children. - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - if (!hasGestureSwipeTarget()) { - onGestureStart(); - - mVelocityTracker.addMovement(event); - mInitialX = event.getX(); - mInitialY = event.getY(); - - final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY); - final ConversationListItemView child = (ConversationListItemView) viewAtPoint; - if (viewAtPoint instanceof ConversationListItemView && - child != null && child.isSwipeAnimatable()) { - // Begin detecting swipe on the target for the rest of the gesture. - mListItemView = child; - if (mListItemView.isAnimating()) { - mListItemView = null; - } - } else { - mListItemView = null; - } - } - break; - case MotionEvent.ACTION_MOVE: - if (hasValidGestureSwipeTarget()) { - mVelocityTracker.addMovement(event); - - final int historicalCount = event.getHistorySize(); - // First consume the historical events, then consume the current ones. - for (int i = 0; i < historicalCount + 1; i++) { - float currX; - float currY; - if (i < historicalCount) { - currX = event.getHistoricalX(i); - currY = event.getHistoricalY(i); - } else { - currX = event.getX(); - currY = event.getY(); - } - final float deltaX = currX - mInitialX; - final float deltaY = currY - mInitialY; - final float absDeltaX = Math.abs(deltaX); - final float absDeltaY = Math.abs(deltaY); - - if (!mIsSwiping && absDeltaY > mTouchSlop - && absDeltaY > (ERROR_FACTOR_MULTIPLIER * absDeltaX)) { - // Stop detecting swipe for the remainder of this gesture. - onGestureEnd(); - return false; - } - - if (absDeltaX > mTouchSlop) { - // Swipe detected. Return true so we can handle the gesture in - // onTouchEvent. - mIsSwiping = true; - - // We don't want to suddenly jump the slop distance. - mInitialX = event.getX(); - mInitialY = event.getY(); - - onSwipeGestureStart(mListItemView); - return true; - } - } - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (hasGestureSwipeTarget()) { - onGestureEnd(); - } - break; - } - - // Start intercepting touch events from children if we detect a swipe. - return mIsSwiping; - } - - @Override - public void onTouchEvent(final RecyclerView recyclerView, final MotionEvent event) { - // We should only be here if we intercepted the touch due to swipe. - Assert.isTrue(mIsSwiping); - - // We are now tracking a swipe gesture. - mVelocityTracker.addMovement(event); - - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_MOVE: - if (hasValidGestureSwipeTarget()) { - mListItemView.setSwipeTranslationX(event.getX() - mInitialX); - } - break; - case MotionEvent.ACTION_UP: - if (hasValidGestureSwipeTarget()) { - final float maxVelocity = mMaximumFlingVelocity; - mVelocityTracker.computeCurrentVelocity(UNIT_SECONDS, maxVelocity); - final float velocityX = getLastComputedXVelocity(); - - final float translationX = mListItemView.getSwipeTranslationX(); - - int swipeDirection = SWIPE_DIRECTION_NONE; - if (translationX != 0) { - swipeDirection = - translationX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - } else if (velocityX != 0) { - swipeDirection = - velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - } - - final boolean fastEnough = isTargetSwipedFastEnough(); - final boolean farEnough = isTargetSwipedFarEnough(); - - final boolean shouldDismiss = (fastEnough || farEnough); - - if (shouldDismiss) { - if (fastEnough) { - animateDismiss(mListItemView, velocityX); - } else { - animateDismiss(mListItemView, swipeDirection); - } - } else { - animateRestore(mListItemView, velocityX); - } - - onSwipeGestureEnd(mListItemView, - shouldDismiss ? swipeDirection : SWIPE_DIRECTION_NONE); - } else { - onGestureEnd(); - } - break; - case MotionEvent.ACTION_CANCEL: - if (hasValidGestureSwipeTarget()) { - animateRestore(mListItemView, 0f); - onSwipeGestureEnd(mListItemView, SWIPE_DIRECTION_NONE); - } else { - onGestureEnd(); - } - break; - } - } - - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - } - - /** - * We have started to intercept a series of touch events. - */ - private void onGestureStart() { - mIsSwiping = false; - // Work around bug in RecyclerView that sends two identical ACTION_DOWN - // events to #onInterceptTouchEvent. - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.clear(); - } - - /** - * The series of touch events has been detected as a swipe. - * - * Now that the gesture is a swipe, we will begin translating the view of the - * given viewHolder. - */ - private void onSwipeGestureStart(final ConversationListItemView itemView) { - mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true); - setHardwareAnimatingLayerType(itemView, ANIMATING); - itemView.setAnimating(true); - } - - /** - * The current swipe gesture is complete. - */ - private void onSwipeGestureEnd(final ConversationListItemView itemView, - final int swipeDirection) { - if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) { - itemView.onSwipeComplete(); - } - - // Balances out onSwipeGestureStart. - itemView.setAnimating(false); - - onGestureEnd(); - } - - /** - * The series of touch events has ended in an {@link MotionEvent#ACTION_UP} - * or {@link MotionEvent#ACTION_CANCEL}. - */ - private void onGestureEnd() { - mVelocityTracker.recycle(); - mVelocityTracker = null; - mIsSwiping = false; - mListItemView = null; - } - - /** - * A swipe animation has started. - */ - private void onSwipeAnimationStart(final ConversationListItemView itemView) { - // Disallow interactions. - itemView.setAnimating(true); - ViewCompat.setHasTransientState(itemView, true); - setHardwareAnimatingLayerType(itemView, ANIMATING); - } - - /** - * The swipe animation has ended. - */ - private void onSwipeAnimationEnd(final ConversationListItemView itemView) { - // Restore interactions. - itemView.setAnimating(false); - ViewCompat.setHasTransientState(itemView, false); - setHardwareAnimatingLayerType(itemView, !ANIMATING); - } - - /** - * Animate the dismissal of the given item. The given velocityX is taken into consideration for - * the animation duration. Whether the item is dismissed to the left or right is dependent on - * the given velocityX. - */ - private void animateDismiss(final ConversationListItemView itemView, final float velocityX) { - Assert.isTrue(velocityX != 0); - final int direction = velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT; - animateDismiss(itemView, direction, velocityX); - } - - /** - * Animate the dismissal of the given item. The velocityX is assumed to be 0. - */ - private void animateDismiss(final ConversationListItemView itemView, final int swipeDirection) { - animateDismiss(itemView, swipeDirection, 0f); - } - - /** - * Animate the dismissal of the given item. - */ - private void animateDismiss(final ConversationListItemView itemView, - final int swipeDirection, final float velocityX) { - Assert.isTrue(swipeDirection != SWIPE_DIRECTION_NONE); - - onSwipeAnimationStart(itemView); - - final float animateTo = (swipeDirection == SWIPE_DIRECTION_RIGHT) ? - mRecyclerView.getWidth() : -mRecyclerView.getWidth(); - final long duration; - if (velocityX != 0) { - final float deltaX = animateTo - itemView.getSwipeTranslationX(); - duration = calculateTranslationDuration(deltaX, velocityX); - } else { - duration = mDefaultDismissAnimationDuration; - } - - final ObjectAnimator animator = getSwipeTranslationXAnimator( - itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - onSwipeAnimationEnd(itemView); - } - }); - animator.start(); - } - - /** - * Animate the bounce back of the given item. - */ - private void animateRestore(final ConversationListItemView itemView, - final float velocityX) { - onSwipeAnimationStart(itemView); - - final float translationX = itemView.getSwipeTranslationX(); - final long duration; - if (velocityX != 0 // Has velocity. - && velocityX > 0 != translationX > 0) { // Right direction. - duration = calculateTranslationDuration(translationX, velocityX); - } else { - duration = mDefaultRestoreAnimationDuration; - } - final ObjectAnimator animator = getSwipeTranslationXAnimator( - itemView, 0f, duration, UiUtils.DEFAULT_INTERPOLATOR); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final Animator animation) { - onSwipeAnimationEnd(itemView); - } - }); - animator.start(); - } - - /** - * Create and start an animator that animates the given view's translationX - * from its current value to the value given by animateTo. - */ - private ObjectAnimator getSwipeTranslationXAnimator(final ConversationListItemView itemView, - final float animateTo, final long duration, final TimeInterpolator interpolator) { - final ObjectAnimator animator = - ObjectAnimator.ofFloat(itemView, "swipeTranslationX", animateTo); - animator.setDuration(duration); - animator.setInterpolator(interpolator); - return animator; - } - - /** - * Determine if the swipe has enough velocity to be dismissed. - */ - private boolean isTargetSwipedFastEnough() { - final float velocityX = getLastComputedXVelocity(); - final float velocityY = mVelocityTracker.getYVelocity(); - final float minVelocity = mMinimumFlingVelocity; - final float translationX = mListItemView.getSwipeTranslationX(); - final float width = mListItemView.getWidth(); - return (Math.abs(velocityX) > minVelocity) // Fast enough. - && (Math.abs(velocityX) > Math.abs(velocityY)) // Not unintentional. - && (velocityX > 0) == (translationX > 0) // Right direction. - && Math.abs(translationX) > - FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement. - } - - /** - * Only used during a swipe gesture. Determine if the swipe has enough distance to be - * dismissed. - */ - private boolean isTargetSwipedFarEnough() { - final float velocityX = getLastComputedXVelocity(); - - final float translationX = mListItemView.getSwipeTranslationX(); - final float width = mListItemView.getWidth(); - - return (velocityX >= 0) == (translationX > 0) // Right direction. - && Math.abs(translationX) > - PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement. - } - - private long calculateTranslationDuration(final float deltaPosition, final float velocity) { - Assert.isTrue(velocity != 0); - final float durationInSeconds = Math.abs(deltaPosition / velocity); - return Math.min((int) (durationInSeconds * UNIT_SECONDS), mMaxTranslationAnimationDuration); - } - - private boolean hasGestureSwipeTarget() { - return mListItemView != null; - } - - private boolean hasValidGestureSwipeTarget() { - return hasGestureSwipeTarget() && mListItemView.getParent() == mRecyclerView; - } - - /** - * Enable a hardware layer for the it view and build that layer. - */ - private void setHardwareAnimatingLayerType(final ConversationListItemView itemView, - final boolean animating) { - if (animating) { - itemView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - if (itemView.getWindowToken() != null) { - itemView.buildLayer(); - } - } else { - itemView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - - private float getLastComputedXVelocity() { - return mVelocityTracker.getXVelocity(); - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java b/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java deleted file mode 100644 index 61e3640..0000000 --- a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.app.Fragment; -import android.os.Bundle; - -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.ui.BaseBugleActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost; -import com.android.messaging.util.Assert; - -/** - * An activity that lets the user forward a SMS/MMS message by picking from a conversation in the - * conversation list. - */ -public class ForwardMessageActivity extends BaseBugleActivity - implements ConversationListFragmentHost { - private MessageData mDraftMessage; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final ConversationListFragment fragment = - ConversationListFragment.createForwardMessageConversationListFragment(); - getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit(); - mDraftMessage = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - Assert.isTrue(fragment instanceof ConversationListFragment); - final ConversationListFragment clf = (ConversationListFragment) fragment; - clf.setHost(this); - } - - @Override - public void onConversationClick(final ConversationListData listData, - final ConversationListItemData conversationListItemData, - final boolean isLongClick, final ConversationListItemView converastionView) { - UIIntents.get().launchConversationActivity( - this, conversationListItemData.getConversationId(), mDraftMessage); - } - - @Override - public void onCreateConversationClick() { - UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage); - } - - @Override - public boolean isConversationSelected(final String conversationId) { - return false; - } - - @Override - public boolean isSwipeAnimatable() { - return false; - } - - @Override - public boolean isSelectionMode() { - return false; - } -} diff --git a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java b/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java deleted file mode 100644 index bfeec51..0000000 --- a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.support.v4.util.ArrayMap; -import android.text.TextUtils; -import android.view.ActionMode; -import android.view.ActionMode.Callback; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.util.Assert; - -import java.util.Collection; -import java.util.HashSet; - -public class MultiSelectActionModeCallback implements Callback { - private HashSet mBlockedSet; - - public interface Listener { - void onActionBarDelete(Collection conversations); - void onActionBarArchive(Iterable conversations, - boolean isToArchive); - void onActionBarNotification(Iterable conversations, - boolean isNotificationOn); - void onActionBarAddContact(final SelectedConversation conversation); - void onActionBarBlock(final SelectedConversation conversation); - void onActionBarHome(); - } - - static class SelectedConversation { - public final String conversationId; - public final long timestamp; - public final String icon; - public final String otherParticipantNormalizedDestination; - public final CharSequence participantLookupKey; - public final boolean isGroup; - public final boolean isArchived; - public final boolean notificationEnabled; - public SelectedConversation(ConversationListItemData data) { - conversationId = data.getConversationId(); - timestamp = data.getTimestamp(); - icon = data.getIcon(); - otherParticipantNormalizedDestination = data.getOtherParticipantNormalizedDestination(); - participantLookupKey = data.getParticipantLookupKey(); - isGroup = data.getIsGroup(); - isArchived = data.getIsArchived(); - notificationEnabled = data.getNotificationEnabled(); - } - } - - private final ArrayMap mSelectedConversations; - - private Listener mListener; - private MenuItem mArchiveMenuItem; - private MenuItem mUnarchiveMenuItem; - private MenuItem mAddContactMenuItem; - private MenuItem mBlockMenuItem; - private MenuItem mNotificationOnMenuItem; - private MenuItem mNotificationOffMenuItem; - private boolean mHasInflated; - - public MultiSelectActionModeCallback(final Listener listener) { - mListener = listener; - mSelectedConversations = new ArrayMap<>(); - - } - - @Override - public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - actionMode.getMenuInflater().inflate(R.menu.conversation_list_fragment_select_menu, menu); - mArchiveMenuItem = menu.findItem(R.id.action_archive); - mUnarchiveMenuItem = menu.findItem(R.id.action_unarchive); - mAddContactMenuItem = menu.findItem(R.id.action_add_contact); - mBlockMenuItem = menu.findItem(R.id.action_block); - mNotificationOffMenuItem = menu.findItem(R.id.action_notification_off); - mNotificationOnMenuItem = menu.findItem(R.id.action_notification_on); - mHasInflated = true; - updateActionIconsVisiblity(); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { - switch(menuItem.getItemId()) { - case R.id.action_delete: - mListener.onActionBarDelete(mSelectedConversations.values()); - return true; - case R.id.action_archive: - mListener.onActionBarArchive(mSelectedConversations.values(), true); - return true; - case R.id.action_unarchive: - mListener.onActionBarArchive(mSelectedConversations.values(), false); - return true; - case R.id.action_notification_off: - mListener.onActionBarNotification(mSelectedConversations.values(), false); - return true; - case R.id.action_notification_on: - mListener.onActionBarNotification(mSelectedConversations.values(), true); - return true; - case R.id.action_add_contact: - Assert.isTrue(mSelectedConversations.size() == 1); - mListener.onActionBarAddContact(mSelectedConversations.valueAt(0)); - return true; - case R.id.action_block: - Assert.isTrue(mSelectedConversations.size() == 1); - mListener.onActionBarBlock(mSelectedConversations.valueAt(0)); - return true; - case android.R.id.home: - mListener.onActionBarHome(); - return true; - default: - return false; - } - } - - @Override - public void onDestroyActionMode(ActionMode actionMode) { - mListener = null; - mSelectedConversations.clear(); - mHasInflated = false; - } - - public void toggleSelect(final ConversationListData listData, - final ConversationListItemData conversationListItemData) { - Assert.notNull(conversationListItemData); - mBlockedSet = listData.getBlockedParticipants(); - final String id = conversationListItemData.getConversationId(); - if (mSelectedConversations.containsKey(id)) { - mSelectedConversations.remove(id); - } else { - mSelectedConversations.put(id, new SelectedConversation(conversationListItemData)); - } - - if (mSelectedConversations.isEmpty()) { - mListener.onActionBarHome(); - } else { - updateActionIconsVisiblity(); - } - } - - public boolean isSelected(final String selectedId) { - return mSelectedConversations.containsKey(selectedId); - } - - private void updateActionIconsVisiblity() { - if (!mHasInflated) { - return; - } - - if (mSelectedConversations.size() == 1) { - final SelectedConversation conversation = mSelectedConversations.valueAt(0); - // The look up key is a key given to us by contacts app, so if we have a look up key, - // we know that the participant is already in contacts. - final boolean isInContacts = !TextUtils.isEmpty(conversation.participantLookupKey); - mAddContactMenuItem.setVisible(!conversation.isGroup && !isInContacts); - // ParticipantNormalizedDestination is always null for group conversations. - final String otherParticipant = conversation.otherParticipantNormalizedDestination; - mBlockMenuItem.setVisible(otherParticipant != null - && !mBlockedSet.contains(otherParticipant)); - } else { - mBlockMenuItem.setVisible(false); - mAddContactMenuItem.setVisible(false); - } - - boolean hasCurrentlyArchived = false; - boolean hasCurrentlyUnarchived = false; - boolean hasCurrentlyOnNotification = false; - boolean hasCurrentlyOffNotification = false; - final Iterable conversations = mSelectedConversations.values(); - for (final SelectedConversation conversation : conversations) { - if (conversation.notificationEnabled) { - hasCurrentlyOnNotification = true; - } else { - hasCurrentlyOffNotification = true; - } - - if (conversation.isArchived) { - hasCurrentlyArchived = true; - } else { - hasCurrentlyUnarchived = true; - } - - // If we found at least one of each example we don't need to keep looping. - if (hasCurrentlyOffNotification && hasCurrentlyOnNotification && - hasCurrentlyArchived && hasCurrentlyUnarchived) { - break; - } - } - // If we have notification off conversations we show on button, if we have notification on - // conversation we show off button. We can show both if we have a mixture. - mNotificationOffMenuItem.setVisible(hasCurrentlyOnNotification); - mNotificationOnMenuItem.setVisible(hasCurrentlyOffNotification); - - mArchiveMenuItem.setVisible(hasCurrentlyUnarchived); - mUnarchiveMenuItem.setVisible(hasCurrentlyArchived); - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java deleted file mode 100644 index ef7fcef..0000000 --- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.app.Fragment; -import android.content.ContentResolver; -import android.content.Intent; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.ui.BaseBugleActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaMetadataRetrieverWrapper; - -import java.io.IOException; -import java.util.ArrayList; - -public class ShareIntentActivity extends BaseBugleActivity implements - ShareIntentFragment.HostInterface { - - private MessageData mDraftMessage; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Intent intent = getIntent(); - if (Intent.ACTION_SEND.equals(intent.getAction()) && - (!TextUtils.isEmpty(intent.getStringExtra("address")) || - !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL)))) { - // This is really more like a SENDTO intent because a destination is supplied. - // It's coming through the SEND intent because that's the intent that is used - // when invoking the chooser with Intent.createChooser(). - final Intent convIntent = UIIntents.get().getLaunchConversationActivityIntent(this); - // Copy the important items from the original intent to the new intent. - convIntent.putExtras(intent); - convIntent.setAction(Intent.ACTION_SENDTO); - convIntent.setDataAndType(intent.getData(), intent.getType()); - // We have to fire off the intent and finish before trying to show the fragment, - // otherwise we get some flashing. - startActivity(convIntent); - finish(); - return; - } - new ShareIntentFragment().show(getFragmentManager(), "ShareIntentFragment"); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - final Intent intent = getIntent(); - final String action = intent.getAction(); - if (Intent.ACTION_SEND.equals(action)) { - final Uri contentUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); - final String contentType = extractContentType(contentUri, intent.getType()); - if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) { - LogUtil.d(LogUtil.BUGLE_TAG, String.format( - "onAttachFragment: contentUri=%s, intent.getType()=%s, inferredType=%s", - contentUri, intent.getType(), contentType)); - } - if (ContentType.TEXT_PLAIN.equals(contentType)) { - final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); - if (sharedText != null) { - mDraftMessage = MessageData.createSharedMessage(sharedText); - } else { - mDraftMessage = null; - } - } else if (ContentType.isImageType(contentType) || - ContentType.isVCardType(contentType) || - ContentType.isAudioType(contentType) || - ContentType.isVideoType(contentType)) { - if (contentUri != null) { - mDraftMessage = MessageData.createSharedMessage(null); - addSharedImagePartToDraft(contentType, contentUri); - } else { - mDraftMessage = null; - } - } else { - // Unsupported content type. - Assert.fail("Unsupported shared content type for " + contentUri + ": " + contentType - + " (" + intent.getType() + ")"); - } - } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - final String contentType = intent.getType(); - if (ContentType.isImageType(contentType)) { - // Handle sharing multiple images. - final ArrayList imageUris = intent.getParcelableArrayListExtra( - Intent.EXTRA_STREAM); - if (imageUris != null && imageUris.size() > 0) { - mDraftMessage = MessageData.createSharedMessage(null); - for (final Uri imageUri : imageUris) { - final String actualContentType = extractContentType(imageUri, contentType); - addSharedImagePartToDraft(actualContentType, imageUri); - } - } else { - mDraftMessage = null; - } - } else { - // Unsupported content type. - Assert.fail("Unsupported shared content type: " + contentType); - } - } else { - // Unsupported action. - Assert.fail("Unsupported action type for sharing: " + action); - } - } - - private static String extractContentType(final Uri uri, final String contentType) { - if (uri == null) { - return contentType; - } - // First try looking at file extension. This is less reliable in some ways but it's - // recommended by - // https://developer.android.com/training/secure-file-sharing/retrieve-info.html - // Some implementations of MediaMetadataRetriever get things horribly - // wrong for common formats such as jpeg (reports as video/ffmpeg) - final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - final String typeFromExtension = resolver.getType(uri); - if (typeFromExtension != null) { - return typeFromExtension; - } - final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper(); - try { - retriever.setDataSource(uri); - final String extractedType = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_MIMETYPE); - if (extractedType != null) { - return extractedType; - } - } catch (final IOException e) { - LogUtil.i(LogUtil.BUGLE_TAG, "Could not determine type of " + uri, e); - } finally { - retriever.release(); - } - return contentType; - } - - private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) { - mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType, - imageUri)); - } - - @Override - public void onConversationClick(final ConversationListItemData conversationListItemData) { - UIIntents.get().launchConversationActivity( - this, conversationListItemData.getConversationId(), mDraftMessage); - finish(); - } - - @Override - public void onCreateConversationClick() { - UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage); - finish(); - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java b/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java deleted file mode 100644 index e894145..0000000 --- a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.ui.CursorRecyclerAdapter; -import com.android.messaging.ui.PersonItemView; -import com.android.messaging.ui.PersonItemView.PersonItemViewListener; -import com.android.messaging.util.PhoneUtils; - -/** - * Turn conversation rows into PeopleItemViews - */ -public class ShareIntentAdapter - extends CursorRecyclerAdapter { - - public interface HostInterface { - void onConversationClicked(final ConversationListItemData conversationListItemData); - } - - private final HostInterface mHostInterface; - - public ShareIntentAdapter(final Context context, final Cursor cursor, - final HostInterface hostInterface) { - super(context, cursor, 0); - mHostInterface = hostInterface; - setHasStableIds(true); - } - - @Override - public void bindViewHolder(final ShareIntentViewHolder holder, final Context context, - final Cursor cursor) { - holder.bind(cursor); - } - - @Override - public ShareIntentViewHolder createViewHolder(final Context context, - final ViewGroup parent, final int viewType) { - final PersonItemView itemView = (PersonItemView) LayoutInflater.from(context).inflate( - R.layout.people_list_item_view, null); - return new ShareIntentViewHolder(itemView); - } - - /** - * Holds a PersonItemView and keeps it synced with a ConversationListItemData. - */ - public class ShareIntentViewHolder extends RecyclerView.ViewHolder implements - PersonItemView.PersonItemViewListener { - private final ConversationListItemData mData = new ConversationListItemData(); - private final PersonItemData mItemData = new PersonItemData() { - @Override - public Uri getAvatarUri() { - return mData.getIcon() == null ? null : Uri.parse(mData.getIcon()); - } - - @Override - public String getDisplayName() { - return mData.getName(); - } - - @Override - public String getDetails() { - final String conversationName = mData.getName(); - final String conversationPhone = PhoneUtils.getDefault().formatForDisplay( - mData.getOtherParticipantNormalizedDestination()); - if (conversationPhone == null || conversationPhone.equals(conversationName)) { - return null; - } - return conversationPhone; - } - - @Override - public Intent getClickIntent() { - return null; - } - - @Override - public long getContactId() { - return ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED; - } - - @Override - public String getLookupKey() { - return null; - } - - @Override - public String getNormalizedDestination() { - return null; - } - }; - - public ShareIntentViewHolder(final PersonItemView itemView) { - super(itemView); - itemView.setListener(this); - } - - public void bind(Cursor cursor) { - mData.bind(cursor); - ((PersonItemView) itemView).bind(mItemData); - } - - @Override - public void onPersonClicked(PersonItemData data) { - mHostInterface.onConversationClicked(mData); - } - - @Override - public boolean onPersonLongClicked(PersonItemData data) { - return false; - } - } -} diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java b/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java deleted file mode 100644 index bc549ea..0000000 --- a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationlist; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; -import com.android.messaging.ui.ListEmptyView; -import com.android.messaging.datamodel.DataModel; - -/** - * Allow user to pick conversation to which an incoming attachment will be shared. - */ -public class ShareIntentFragment extends DialogFragment implements ConversationListDataListener, - ShareIntentAdapter.HostInterface { - public static final String HIDE_NEW_CONVERSATION_BUTTON_KEY = "hide_conv_button_key"; - - public interface HostInterface { - public void onConversationClick(final ConversationListItemData conversationListItemData); - public void onCreateConversationClick(); - } - - private final Binding mListBinding = BindingBase.createBinding(this); - private RecyclerView mRecyclerView; - private ListEmptyView mEmptyListMessageView; - private ShareIntentAdapter mAdapter; - private HostInterface mHost; - private boolean mDismissed; - - /** - * {@inheritDoc} from Fragment - */ - @Override - public Dialog onCreateDialog(final Bundle bundle) { - final Activity activity = getActivity(); - final LayoutInflater inflater = activity.getLayoutInflater(); - View view = inflater.inflate(R.layout.share_intent_conversation_list_view, null); - mEmptyListMessageView = (ListEmptyView) view.findViewById(R.id.no_conversations_view); - mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list); - // The default behavior for default layout param generation by LinearLayoutManager is to - // provide width and height of WRAP_CONTENT, but this is not desirable for - // ShareIntentFragment; the view in each row should be a width of MATCH_PARENT so that - // the entire row is tappable. - final LinearLayoutManager manager = new LinearLayoutManager(activity) { - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - }; - mListBinding.getData().init(getLoaderManager(), mListBinding); - mAdapter = new ShareIntentAdapter(activity, null, this); - mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list); - mRecyclerView.setLayoutManager(manager); - mRecyclerView.setHasFixedSize(true); - mRecyclerView.setAdapter(mAdapter); - final Builder dialogBuilder = new AlertDialog.Builder(activity) - .setView(view) - .setTitle(R.string.share_intent_activity_label); - - final Bundle arguments = getArguments(); - if (arguments == null || !arguments.getBoolean(HIDE_NEW_CONVERSATION_BUTTON_KEY)) { - dialogBuilder.setPositiveButton(R.string.share_new_message, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mDismissed = true; - mHost.onCreateConversationClick(); - } - }); - } - return dialogBuilder.setNegativeButton(R.string.share_cancel, null) - .create(); - } - - @Override - public void onDismiss(DialogInterface dialog) { - if (!mDismissed) { - final Activity activity = getActivity(); - if (activity != null) { - activity.finish(); - } - } - } - - /** - * {@inheritDoc} from Fragment - */ - @Override - public void onDestroy() { - super.onDestroy(); - mListBinding.unbind(); - } - - @Override - public void onAttach(final Activity activity) { - super.onAttach(activity); - if (activity instanceof HostInterface) { - mHost = (HostInterface) activity; - } - mListBinding.bind(DataModel.get().createConversationListData(activity, this, false)); - } - - @Override - public void onConversationListCursorUpdated(final ConversationListData data, - final Cursor cursor) { - mListBinding.ensureBound(data); - mAdapter.swapCursor(cursor); - updateEmptyListUi(cursor == null || cursor.getCount() == 0); - } - - /** - * {@inheritDoc} from SharIntentItemView.HostInterface - */ - @Override - public void onConversationClicked(final ConversationListItemData conversationListItemData) { - mHost.onConversationClick(conversationListItemData); - } - - // Show and hide empty list UI as needed with appropriate text based on view specifics - private void updateEmptyListUi(final boolean isEmpty) { - if (isEmpty) { - mEmptyListMessageView.setTextHint(R.string.conversation_list_empty_text); - mEmptyListMessageView.setVisibility(View.VISIBLE); - } else { - mEmptyListMessageView.setVisibility(View.GONE); - } - } - - @Override - public void setBlockedParticipantsAvailable(boolean blockedAvailable) { - } -} diff --git a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java b/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java deleted file mode 100644 index d727001..0000000 --- a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationsettings; - -import android.app.AlertDialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.util.AccessibilityUtil; - -public class CopyContactDetailDialog implements DialogInterface.OnClickListener { - - private final Context mContext; - private final String mContactDetail; - - public CopyContactDetailDialog(final Context context, final String contactDetail) { - mContext = context; - mContactDetail = contactDetail; - } - - public void show() { - new AlertDialog.Builder(mContext) - .setView(createBodyView()) - .setTitle(R.string.copy_to_clipboard_dialog_title) - .setPositiveButton(R.string.copy_to_clipboard, this) - .show(); - } - - @Override - public void onClick(final DialogInterface dialog, final int which) { - final ClipboardManager clipboard = - (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setPrimaryClip(ClipData.newPlainText(null /* label */, mContactDetail)); - } - - private View createBodyView() { - LayoutInflater inflater = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - TextView textView = (TextView) inflater.inflate(R.layout.copy_contact_dialog_view, null, - false); - textView.setText(mContactDetail); - final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber( - mContext.getResources(), mContactDetail); - textView.setContentDescription(vocalizedDisplayName); - return textView; - } -} diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java deleted file mode 100644 index f017328..0000000 --- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationsettings; - -import android.app.Fragment; -import android.os.Bundle; -import android.view.MenuItem; - -import com.android.messaging.R; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; - -/** - * Shows a list of participants in a conversation. - */ -public class PeopleAndOptionsActivity extends BugleActionBarActivity { - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.people_and_options_activity); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public void onAttachFragment(final Fragment fragment) { - if (fragment instanceof PeopleAndOptionsFragment) { - final String conversationId = - getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - Assert.notNull(conversationId); - final PeopleAndOptionsFragment peopleAndOptionsFragment = - (PeopleAndOptionsFragment) fragment; - peopleAndOptionsFragment.setConversationId(conversationId); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // Treat the home press as back press so that when we go back to - // ConversationActivity, it doesn't lose its original intent (conversation id etc.) - onBackPressed(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java deleted file mode 100644 index b86d952..0000000 --- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationsettings; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.media.RingtoneManager; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.Settings; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantListItemData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; -import com.android.messaging.datamodel.data.PeopleOptionsItemData; -import com.android.messaging.datamodel.data.PersonItemData; -import com.android.messaging.ui.CompositeAdapter; -import com.android.messaging.ui.PersonItemView; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.conversation.ConversationActivity; -import com.android.messaging.util.Assert; - -import java.util.ArrayList; -import java.util.List; - -/** - * Shows a list of participants of a conversation and displays options. - */ -public class PeopleAndOptionsFragment extends Fragment - implements PeopleAndOptionsDataListener, PeopleOptionsItemView.HostInterface { - private ListView mListView; - private OptionsListAdapter mOptionsListAdapter; - private PeopleListAdapter mPeopleListAdapter; - private final Binding mBinding = - BindingBase.createBinding(this); - - private static final int REQUEST_CODE_RINGTONE_PICKER = 1000; - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mBinding.getData().init(getLoaderManager(), mBinding); - } - - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.people_and_options_fragment, container, false); - mListView = (ListView) view.findViewById(android.R.id.list); - mPeopleListAdapter = new PeopleListAdapter(getActivity()); - mOptionsListAdapter = new OptionsListAdapter(); - final CompositeAdapter compositeAdapter = new CompositeAdapter(getActivity()); - compositeAdapter.addPartition(new PeopleAndOptionsPartition(mOptionsListAdapter, - R.string.general_settings_title, false)); - compositeAdapter.addPartition(new PeopleAndOptionsPartition(mPeopleListAdapter, - R.string.participant_list_title, true)); - mListView.setAdapter(compositeAdapter); - return view; - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_RINGTONE_PICKER) { - final Parcelable pick = data.getParcelableExtra( - RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - final String pickedUri = pick == null ? "" : pick.toString(); - mBinding.getData().setConversationNotificationSound(mBinding, pickedUri); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - mBinding.unbind(); - } - - public void setConversationId(final String conversationId) { - Assert.isTrue(getView() == null); - Assert.notNull(conversationId); - mBinding.bind(DataModel.get().createPeopleAndOptionsData(conversationId, getActivity(), - this)); - } - - @Override - public void onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor) { - Assert.isTrue(cursor == null || cursor.getCount() == 1); - mBinding.ensureBound(data); - mOptionsListAdapter.swapCursor(cursor); - } - - @Override - public void onParticipantsListLoaded(final PeopleAndOptionsData data, - final List participants) { - mBinding.ensureBound(data); - mPeopleListAdapter.updateParticipants(participants); - final ParticipantData otherParticipant = participants.size() == 1 ? - participants.get(0) : null; - mOptionsListAdapter.setOtherParticipant(otherParticipant); - } - - @Override - public void onOptionsItemViewClicked(final PeopleOptionsItemData item, - final boolean isChecked) { - switch (item.getItemId()) { - case PeopleOptionsItemData.SETTING_NOTIFICATION_ENABLED: - mBinding.getData().enableConversationNotifications(mBinding, isChecked); - break; - - case PeopleOptionsItemData.SETTING_NOTIFICATION_SOUND_URI: - final Intent ringtonePickerIntent = UIIntents.get().getRingtonePickerIntent( - getString(R.string.notification_sound_pref_title), - item.getRingtoneUri(), Settings.System.DEFAULT_NOTIFICATION_URI, - RingtoneManager.TYPE_NOTIFICATION); - startActivityForResult(ringtonePickerIntent, REQUEST_CODE_RINGTONE_PICKER); - break; - - case PeopleOptionsItemData.SETTING_NOTIFICATION_VIBRATION: - mBinding.getData().enableConversationNotificationVibration(mBinding, - isChecked); - break; - - case PeopleOptionsItemData.SETTING_BLOCKED: - if (item.getOtherParticipant().isBlocked()) { - mBinding.getData().setDestinationBlocked(mBinding, false); - break; - } - final Resources res = getResources(); - final Activity activity = getActivity(); - new AlertDialog.Builder(activity) - .setTitle(res.getString(R.string.block_confirmation_title, - item.getOtherParticipant().getDisplayDestination())) - .setMessage(res.getString(R.string.block_confirmation_message)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface arg0, int arg1) { - mBinding.getData().setDestinationBlocked(mBinding, true); - activity.setResult(ConversationActivity.FINISH_RESULT_CODE); - activity.finish(); - } - }) - .create() - .show(); - break; - } - } - - /** - * A simple adapter that takes a conversation metadata cursor and binds - * PeopleAndOptionsItemViews to individual COLUMNS of the first cursor record. (Note - * that this is not a CursorAdapter because it treats individual columns of the cursor as - * separate options to display for the conversation, e.g. notification settings). - */ - private class OptionsListAdapter extends BaseAdapter { - private Cursor mOptionsCursor; - private ParticipantData mOtherParticipantData; - - public Cursor swapCursor(final Cursor newCursor) { - final Cursor oldCursor = mOptionsCursor; - if (newCursor != oldCursor) { - mOptionsCursor = newCursor; - notifyDataSetChanged(); - } - return oldCursor; - } - - public void setOtherParticipant(final ParticipantData participantData) { - if (mOtherParticipantData != participantData) { - mOtherParticipantData = participantData; - notifyDataSetChanged(); - } - } - - @Override - public int getCount() { - int count = PeopleOptionsItemData.SETTINGS_COUNT; - if (mOtherParticipantData == null) { - count--; - } - return mOptionsCursor == null ? 0 : count; - } - - @Override - public Object getItem(final int position) { - return null; - } - - @Override - public long getItemId(final int position) { - return 0; - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - final PeopleOptionsItemView itemView; - if (convertView != null && convertView instanceof PeopleOptionsItemView) { - itemView = (PeopleOptionsItemView) convertView; - } else { - final LayoutInflater inflater = (LayoutInflater) getActivity() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - itemView = (PeopleOptionsItemView) - inflater.inflate(R.layout.people_options_item_view, parent, false); - } - mOptionsCursor.moveToFirst(); - itemView.bind(mOptionsCursor, position, mOtherParticipantData, - PeopleAndOptionsFragment.this); - return itemView; - } - } - - /** - * An adapter that takes a list of ParticipantData and displays them as a list of - * ParticipantListItemViews. - */ - private class PeopleListAdapter extends ArrayAdapter { - public PeopleListAdapter(final Context context) { - super(context, R.layout.people_list_item_view, new ArrayList()); - } - - public void updateParticipants(final List newList) { - clear(); - addAll(newList); - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - PersonItemView itemView; - final ParticipantData item = getItem(position); - if (convertView != null && convertView instanceof PersonItemView) { - itemView = (PersonItemView) convertView; - } else { - final LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - itemView = (PersonItemView) inflater.inflate(R.layout.people_list_item_view, parent, - false); - } - final ParticipantListItemData itemData = - DataModel.get().createParticipantListItemData(item); - itemView.bind(itemData); - - // Any click on the row should have the same effect as clicking the avatar icon - final PersonItemView itemViewClosure = itemView; - itemView.setListener(new PersonItemView.PersonItemViewListener() { - @Override - public void onPersonClicked(final PersonItemData data) { - itemViewClosure.performClickOnAvatar(); - } - - @Override - public boolean onPersonLongClicked(final PersonItemData data) { - if (mBinding.isBound()) { - final CopyContactDetailDialog dialog = new CopyContactDetailDialog( - getContext(), data.getDetails()); - dialog.show(); - return true; - } - return false; - } - }); - return itemView; - } - } - - /** - * Represents a partition/section in the People & Options list (e.g. "general options" and - * "people in this conversation" sections). - */ - private class PeopleAndOptionsPartition extends CompositeAdapter.Partition { - private final int mHeaderResId; - private final boolean mNeedDivider; - - public PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId, - final boolean needDivider) { - super(true /* showIfEmpty */, true /* hasHeader */, adapter); - mHeaderResId = headerResId; - mNeedDivider = needDivider; - } - - @Override - public View getHeaderView(final View convertView, final ViewGroup parentView) { - View view = null; - if (convertView != null && convertView.getId() == R.id.people_and_options_header) { - view = convertView; - } else { - view = LayoutInflater.from(getActivity()).inflate( - R.layout.people_and_options_section_header, parentView, false); - } - final TextView text = (TextView) view.findViewById(R.id.header_text); - final View divider = view.findViewById(R.id.divider); - text.setText(mHeaderResId); - divider.setVisibility(mNeedDivider ? View.VISIBLE : View.GONE); - return view; - } - } -} diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java b/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java deleted file mode 100644 index 42ecfeb..0000000 --- a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversationsettings; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.SwitchCompat; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.PeopleOptionsItemData; -import com.android.messaging.util.Assert; - -/** - * The view for a single entry in the options section of people & options activity. - */ -public class PeopleOptionsItemView extends LinearLayout { - /** - * Implemented by the host of this view that handles options click event. - */ - public interface HostInterface { - void onOptionsItemViewClicked(PeopleOptionsItemData item, boolean isChecked); - } - - private TextView mTitle; - private TextView mSubtitle; - private SwitchCompat mSwitch; - private final PeopleOptionsItemData mData; - private HostInterface mHostInterface; - - public PeopleOptionsItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = DataModel.get().createPeopleOptionsItemData(context); - } - - @Override - protected void onFinishInflate () { - mTitle = (TextView) findViewById(R.id.title); - mSubtitle = (TextView) findViewById(R.id.subtitle); - mSwitch = (SwitchCompat) findViewById(R.id.switch_button); - setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - mHostInterface.onOptionsItemViewClicked(mData, !mData.getChecked()); - } - }); - } - - public void bind(final Cursor cursor, final int columnIndex, ParticipantData otherParticipant, - final HostInterface hostInterface) { - Assert.isTrue(columnIndex < PeopleOptionsItemData.SETTINGS_COUNT && columnIndex >= 0); - mData.bind(cursor, otherParticipant, columnIndex); - mHostInterface = hostInterface; - - mTitle.setText(mData.getTitle()); - final String subtitle = mData.getSubtitle(); - if (TextUtils.isEmpty(subtitle)) { - mSubtitle.setVisibility(GONE); - } else { - mSubtitle.setVisibility(VISIBLE); - mSubtitle.setText(subtitle); - } - - if (mData.getCheckable()) { - mSwitch.setVisibility(VISIBLE); - mSwitch.setChecked(mData.getChecked()); - } else { - mSwitch.setVisibility(GONE); - } - - final boolean enabled = mData.getEnabled(); - if (enabled != isEnabled()) { - mTitle.setEnabled(enabled); - mSubtitle.setEnabled(enabled); - mSwitch.setEnabled(enabled); - setAlpha(enabled ? 1.0f : 0.5f); - setEnabled(enabled); - } - } -} diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java b/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java deleted file mode 100644 index 485dcf7..0000000 --- a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.debug; - -import android.os.Bundle; - -import com.android.messaging.R; -import com.android.messaging.ui.BaseBugleActivity; - -/** - * Show list of all MmsConfig key/value pairs and allow editing. - */ -public class DebugMmsConfigActivity extends BaseBugleActivity { - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.debug_mmsconfig_activity); - } -} diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java b/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java deleted file mode 100644 index 7c54db5..0000000 --- a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.debug; - -import android.app.Fragment; -import android.content.Context; -import android.os.Bundle; -import android.telephony.SubscriptionInfo; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.Spinner; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.ui.debug.DebugMmsConfigItemView.MmsConfigItemListener; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Show list of all MmsConfig key/value pairs and allow editing. - */ -public class DebugMmsConfigFragment extends Fragment { - @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View fragmentView = inflater.inflate(R.layout.mms_config_debug_fragment, container, - false); - final ListView listView = (ListView) fragmentView.findViewById(android.R.id.list); - final Spinner spinner = (Spinner) fragmentView.findViewById(R.id.sim_selector); - final Integer[] subIdArray = getActiveSubIds(); - ArrayAdapter spinnerAdapter = new ArrayAdapter(getActivity(), - android.R.layout.simple_spinner_item, subIdArray); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(spinnerAdapter); - spinner.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - listView.setAdapter(new MmsConfigAdapter(getActivity(), subIdArray[position])); - - final int[] mccmnc = PhoneUtils.get(subIdArray[position]).getMccMnc(); - // Set the title with the mcc/mnc - final TextView title = (TextView) fragmentView.findViewById(R.id.sim_title); - title.setText("(" + mccmnc[0] + "/" + mccmnc[1] + ") " + - getActivity().getString(R.string.debug_sub_id_spinner_text)); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - return fragmentView; - } - - public static Integer[] getActiveSubIds() { - if (!OsUtil.isAtLeastL_MR1()) { - return new Integer[] { ParticipantData.DEFAULT_SELF_SUB_ID }; - } - final List subRecords = - PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList(); - if (subRecords == null) { - return new Integer[0]; - } - final Integer[] retArray = new Integer[subRecords.size()]; - for (int i = 0; i < subRecords.size(); i++) { - retArray[i] = subRecords.get(i).getSubscriptionId(); - } - return retArray; - } - - private class MmsConfigAdapter extends BaseAdapter implements - DebugMmsConfigItemView.MmsConfigItemListener { - private final LayoutInflater mInflater; - private final List mKeys; - private final MmsConfig mMmsConfig; - - public MmsConfigAdapter(Context context, int subId) { - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mMmsConfig = MmsConfig.get(subId); - mKeys = new ArrayList<>(mMmsConfig.keySet()); - Collections.sort(mKeys); - } - - @Override - public View getView(final int position, final View convertView, final ViewGroup parent) { - final DebugMmsConfigItemView view; - if (convertView != null && convertView instanceof DebugMmsConfigItemView) { - view = (DebugMmsConfigItemView) convertView; - } else { - view = (DebugMmsConfigItemView) mInflater.inflate( - R.layout.debug_mmsconfig_item_view, parent, false); - } - final String key = mKeys.get(position); - view.bind(key, - MmsConfig.getKeyType(key), - String.valueOf(mMmsConfig.getValue(key)), - this); - return view; - } - - @Override - public void onValueChanged(String key, String keyType, String value) { - mMmsConfig.update(key, value, keyType); - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return mKeys.size(); - } - - @Override - public Object getItem(int position) { - return mKeys.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - } -} diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java b/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java deleted file mode 100644 index 7b899c0..0000000 --- a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.debug; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnShowListener; -import android.text.InputType; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.inputmethod.InputMethodManager; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.Switch; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.util.LogUtil; - -public class DebugMmsConfigItemView extends LinearLayout implements OnClickListener, - OnCheckedChangeListener, DialogInterface.OnClickListener { - - public interface MmsConfigItemListener { - void onValueChanged(String key, String keyType, String value); - } - - private TextView mTitle; - private TextView mTextValue; - private Switch mSwitch; - private String mKey; - private String mKeyType; - private MmsConfigItemListener mListener; - private EditText mEditText; - - public DebugMmsConfigItemView(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } - - @Override - protected void onFinishInflate () { - mTitle = (TextView) findViewById(R.id.title); - mTextValue = (TextView) findViewById(R.id.text_value); - mSwitch = (Switch) findViewById(R.id.switch_button); - setOnClickListener(this); - mSwitch.setOnCheckedChangeListener(this); - } - - public void bind(final String key, final String keyType, final String value, - final MmsConfigItemListener listener) { - mListener = listener; - mKey = key; - mKeyType = keyType; - mTitle.setText(key); - - switch (keyType) { - case MmsConfig.KEY_TYPE_BOOL: - mSwitch.setVisibility(View.VISIBLE); - mTextValue.setVisibility(View.GONE); - mSwitch.setChecked(Boolean.valueOf(value)); - break; - case MmsConfig.KEY_TYPE_STRING: - case MmsConfig.KEY_TYPE_INT: - mTextValue.setVisibility(View.VISIBLE); - mSwitch.setVisibility(View.GONE); - mTextValue.setText(value); - break; - default: - mTextValue.setVisibility(View.GONE); - mSwitch.setVisibility(View.GONE); - LogUtil.e(LogUtil.BUGLE_TAG, "Unexpected keytype: " + keyType); - break; - } - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mListener.onValueChanged(mKey, mKeyType, String.valueOf(isChecked)); - } - - @Override - public void onClick(View v) { - if (MmsConfig.KEY_TYPE_BOOL.equals(mKeyType)) { - return; - } - final Context context = getContext(); - mEditText = new EditText(context); - mEditText.setText(mTextValue.getText()); - mEditText.setFocusable(true); - if (MmsConfig.KEY_TYPE_INT.equals(mKeyType)) { - mEditText.setInputType(InputType.TYPE_CLASS_PHONE); - } else { - mEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - } - final AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle(mKey) - .setView(mEditText) - .setPositiveButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, null) - .create(); - dialog.setOnShowListener(new OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - mEditText.requestFocus(); - mEditText.selectAll(); - ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)) - .toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); - } - }); - dialog.show(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - mListener.onValueChanged(mKey, mKeyType, mEditText.getText().toString()); - } -} diff --git a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java b/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java deleted file mode 100644 index 1aa8be3..0000000 --- a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.debug; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.telephony.SmsMessage; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.messaging.R; -import com.android.messaging.datamodel.action.ReceiveMmsMessageAction; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.receiver.SmsReceiver; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.util.DebugUtils; -import com.android.messaging.util.LogUtil; - -/** - * Class that displays UI for choosing SMS/MMS dump files for debugging - */ -public class DebugSmsMmsFromDumpFileDialogFragment extends DialogFragment { - public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - public static final String KEY_DUMP_FILES = "dump_files"; - public static final String KEY_ACTION = "action"; - - public static final String ACTION_LOAD = "load"; - public static final String ACTION_EMAIL = "email"; - - private String[] mDumpFiles; - private String mAction; - - public static DebugSmsMmsFromDumpFileDialogFragment newInstance(final String[] dumpFiles, - final String action) { - final DebugSmsMmsFromDumpFileDialogFragment frag = - new DebugSmsMmsFromDumpFileDialogFragment(); - final Bundle args = new Bundle(); - args.putSerializable(KEY_DUMP_FILES, dumpFiles); - args.putString(KEY_ACTION, action); - frag.setArguments(args); - return frag; - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Bundle args = getArguments(); - mDumpFiles = (String[]) args.getSerializable(KEY_DUMP_FILES); - mAction = args.getString(KEY_ACTION); - - final LayoutInflater inflater = getActivity().getLayoutInflater(); - final View layout = inflater.inflate( - R.layout.debug_sms_mms_from_dump_file_dialog, null/*root*/); - final ListView list = (ListView) layout.findViewById(R.id.dump_file_list); - list.setAdapter(new DumpFileListAdapter(getActivity(), mDumpFiles)); - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - final Resources resources = getResources(); - if (ACTION_LOAD.equals(mAction)) { - builder.setTitle(resources.getString( - R.string.load_sms_mms_from_dump_file_dialog_title)); - } else if (ACTION_EMAIL.equals(mAction)) { - builder.setTitle(resources.getString( - R.string.email_sms_mms_from_dump_file_dialog_title)); - } - builder.setView(layout); - return builder.create(); - } - - private class DumpFileListAdapter extends ArrayAdapter { - public DumpFileListAdapter(final Context context, final String[] dumpFiles) { - super(context, R.layout.sms_mms_dump_file_list_item, dumpFiles); - } - - @Override - public View getView(final int position, final View view, final ViewGroup parent) { - TextView actionItemView; - if (view == null || !(view instanceof TextView)) { - final LayoutInflater inflater = LayoutInflater.from(getContext()); - actionItemView = (TextView) inflater.inflate( - R.layout.sms_mms_dump_file_list_item, parent, false); - } else { - actionItemView = (TextView) view; - } - - final String file = getItem(position); - actionItemView.setText(file); - actionItemView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View view) { - dismiss(); - if (ACTION_LOAD.equals(mAction)) { - receiveFromDumpFile(file); - } else if (ACTION_EMAIL.equals(mAction)) { - emailDumpFile(file); - } - } - }); - return actionItemView; - } - } - - /** - * Load MMS/SMS from the dump file - */ - private void receiveFromDumpFile(final String dumpFileName) { - if (dumpFileName.startsWith(MmsUtils.SMS_DUMP_PREFIX)) { - final SmsMessage[] messages = DebugUtils.retreiveSmsFromDumpFile(dumpFileName); - if (messages != null) { - SmsReceiver.deliverSmsMessages(getActivity(), ParticipantData.DEFAULT_SELF_SUB_ID, - 0, messages); - } else { - LogUtil.e(LogUtil.BUGLE_TAG, - "receiveFromDumpFile: invalid sms dump file " + dumpFileName); - } - } else if (dumpFileName.startsWith(MmsUtils.MMS_DUMP_PREFIX)) { - final byte[] data = MmsUtils.createDebugNotificationInd(dumpFileName); - if (data != null) { - final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction( - ParticipantData.DEFAULT_SELF_SUB_ID, data); - action.start(); - } else { - LogUtil.e(LogUtil.BUGLE_TAG, - "receiveFromDumpFile: invalid mms dump file " + dumpFileName); - } - } else { - LogUtil.e(LogUtil.BUGLE_TAG, - "receiveFromDumpFile: invalid dump file name " + dumpFileName); - } - } - - /** - * Launch email app to send the dump file - */ - private void emailDumpFile(final String file) { - final Resources resources = getResources(); - final String fileLocation = "file://" - + Environment.getExternalStorageDirectory() + "/" + file; - final Intent sharingIntent = new Intent(Intent.ACTION_SEND); - sharingIntent.setType(APPLICATION_OCTET_STREAM); - sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileLocation)); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, - resources.getString(R.string.email_sms_mms_dump_file_subject)); - getActivity().startActivity(Intent.createChooser(sharingIntent, - resources.getString(R.string.email_sms_mms_dump_file_chooser_title))); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java b/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java deleted file mode 100644 index a211058..0000000 --- a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import com.google.common.base.Preconditions; - -import javax.annotation.concurrent.ThreadSafe; - -/** - * Keeps track of the speech level as last observed by the recognition - * engine as microphone data flows through it. Can be polled by the UI to - * animate its views. - */ -@ThreadSafe -public class AudioLevelSource { - private volatile int mSpeechLevel; - private volatile Listener mListener; - - public static final int LEVEL_UNKNOWN = -1; - - public interface Listener { - void onSpeechLevel(int speechLevel); - } - - public void setSpeechLevel(int speechLevel) { - Preconditions.checkArgument(speechLevel >= 0 && speechLevel <= 100 || - speechLevel == LEVEL_UNKNOWN); - mSpeechLevel = speechLevel; - maybeNotify(); - } - - public int getSpeechLevel() { - return mSpeechLevel; - } - - public void reset() { - setSpeechLevel(LEVEL_UNKNOWN); - } - - public boolean isValid() { - return mSpeechLevel > 0; - } - - private void maybeNotify() { - final Listener l = mListener; - if (l != null) { - l.onSpeechLevel(mSpeechLevel); - } - } - - public synchronized void setListener(Listener listener) { - mListener = listener; - } - - public synchronized void clearListener(Listener listener) { - if (mListener == listener) { - mListener = null; - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java deleted file mode 100644 index 5d79293..0000000 --- a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.Manifest; -import android.content.pm.PackageManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.util.OsUtil; - -/** - * Chooser which allows the user to record audio - */ -class AudioMediaChooser extends MediaChooser implements - AudioRecordView.HostInterface { - private View mEnabledView; - private View mMissingPermissionView; - - AudioMediaChooser(final MediaPicker mediaPicker) { - super(mediaPicker); - } - - @Override - public int getSupportedMediaTypes() { - return MediaPicker.MEDIA_TYPE_AUDIO; - } - - @Override - public int getIconResource() { - return R.drawable.ic_audio_light; - } - - @Override - public int getIconDescriptionResource() { - return R.string.mediapicker_audioChooserDescription; - } - - @Override - public void onAudioRecorded(final MessagePartData item) { - mMediaPicker.dispatchItemsSelected(item, true); - } - - @Override - public void setThemeColor(final int color) { - if (mView != null) { - ((AudioRecordView) mView).setThemeColor(color); - } - } - - @Override - protected View createView(final ViewGroup container) { - final LayoutInflater inflater = getLayoutInflater(); - final AudioRecordView view = (AudioRecordView) inflater.inflate( - R.layout.mediapicker_audio_chooser, - container /* root */, - false /* attachToRoot */); - view.setHostInterface(this); - view.setThemeColor(mMediaPicker.getConversationThemeColor()); - mEnabledView = view.findViewById(R.id.mediapicker_enabled); - mMissingPermissionView = view.findViewById(R.id.missing_permission_view); - return view; - } - - @Override - int getActionBarTitleResId() { - return R.string.mediapicker_audio_title; - } - - @Override - public boolean isHandlingTouch() { - // Whenever the user is in the process of recording audio, we want to allow the user - // to move the finger within the panel without interpreting that as dragging the media - // picker panel. - return ((AudioRecordView) mView).shouldHandleTouch(); - } - - @Override - public void stopTouchHandling() { - ((AudioRecordView) mView).stopTouchHandling(); - } - - @Override - public void onPause() { - super.onPause(); - if (mView != null) { - ((AudioRecordView) mView).onPause(); - } - } - - @Override - protected void setSelected(final boolean selected) { - super.setSelected(selected); - if (selected && !OsUtil.hasRecordAudioPermission()) { - requestRecordAudioPermission(); - } - } - - private void requestRecordAudioPermission() { - mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, - MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE); - } - - @Override - protected void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { - if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) { - final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; - mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE); - mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE); - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java b/src/com/android/messaging/ui/mediapicker/AudioRecordView.java deleted file mode 100644 index fba493f..0000000 --- a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.media.MediaRecorder; -import android.net.Uri; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; -import com.android.messaging.datamodel.data.MediaPickerMessagePartData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.MediaUtil; -import com.android.messaging.util.MediaUtil.OnCompletionListener; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.ThreadUtil; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -/** - * Hosts an audio recorder with tap and hold to record functionality. - */ -public class AudioRecordView extends FrameLayout implements - MediaRecorder.OnErrorListener, - MediaRecorder.OnInfoListener { - /** - * An interface that communicates with the hosted AudioRecordView. - */ - public interface HostInterface extends DraftMessageSubscriptionDataProvider { - void onAudioRecorded(final MessagePartData item); - } - - /** The initial state, the user may press and hold to start recording */ - private static final int MODE_IDLE = 1; - - /** The user has pressed the record button and we are playing the sound indicating the - * start of recording session. Don't record yet since we don't want the beeping sound - * to get into the recording. */ - private static final int MODE_STARTING = 2; - - /** When the user is actively recording */ - private static final int MODE_RECORDING = 3; - - /** When the user has finished recording, we need to record for some additional time. */ - private static final int MODE_STOPPING = 4; - - // Bug: 16020175: The framework's MediaRecorder would cut off the ending portion of the - // recorded audio by about half a second. To mitigate this issue, we continue the recording - // for some extra time before stopping it. - private static final int AUDIO_RECORD_ENDING_BUFFER_MILLIS = 500; - - /** - * The minimum duration of any recording. Below this threshold, it will be treated as if the - * user clicked the record button and inform the user to tap and hold to record. - */ - private static final int AUDIO_RECORD_MINIMUM_DURATION_MILLIS = 300; - - // For accessibility, the touchable record button is bigger than the record button visual. - private ImageView mRecordButtonVisual; - private View mRecordButton; - private SoundLevels mSoundLevels; - private TextView mHintTextView; - private PausableChronometer mTimerTextView; - private LevelTrackingMediaRecorder mMediaRecorder; - private long mAudioRecordStartTimeMillis; - - private int mCurrentMode = MODE_IDLE; - private HostInterface mHostInterface; - private int mThemeColor; - - public AudioRecordView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mMediaRecorder = new LevelTrackingMediaRecorder(); - } - - public void setHostInterface(final HostInterface hostInterface) { - mHostInterface = hostInterface; - } - - @VisibleForTesting - public void testSetMediaRecorder(final LevelTrackingMediaRecorder recorder) { - mMediaRecorder = recorder; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mSoundLevels = (SoundLevels) findViewById(R.id.sound_levels); - mRecordButtonVisual = (ImageView) findViewById(R.id.record_button_visual); - mRecordButton = findViewById(R.id.record_button); - mHintTextView = (TextView) findViewById(R.id.hint_text); - mTimerTextView = (PausableChronometer) findViewById(R.id.timer_text); - mSoundLevels.setLevelSource(mMediaRecorder.getLevelSource()); - mRecordButton.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(final View v, final MotionEvent event) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - onRecordButtonTouchDown(); - - // Don't let the record button handle the down event to let it fall through - // so that we can handle it for the entire panel in onTouchEvent(). This is - // done so that: 1) the user taps on the record button to start recording - // 2) the entire panel owns the touch event so we'd keep recording even - // if the user moves outside the button region. - return false; - } - return false; - } - }); - } - - @Override - public boolean onTouchEvent(final MotionEvent event) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - return shouldHandleTouch(); - - case MotionEvent.ACTION_MOVE: - return true; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - return onRecordButtonTouchUp(); - } - return super.onTouchEvent(event); - } - - public void onPause() { - // The conversation draft cannot take any updates when it's paused. Therefore, forcibly - // stop recording on pause. - stopRecording(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - stopRecording(); - } - - private boolean isRecording() { - return mMediaRecorder.isRecording() && mCurrentMode == MODE_RECORDING; - } - - public boolean shouldHandleTouch() { - return mCurrentMode != MODE_IDLE; - } - - public void stopTouchHandling() { - setMode(MODE_IDLE); - stopRecording(); - } - - private void setMode(final int mode) { - if (mCurrentMode != mode) { - mCurrentMode = mode; - updateVisualState(); - } - } - - private void updateVisualState() { - switch (mCurrentMode) { - case MODE_IDLE: - mHintTextView.setVisibility(VISIBLE); - mHintTextView.setTypeface(null, Typeface.NORMAL); - mTimerTextView.setVisibility(GONE); - mSoundLevels.setEnabled(false); - mTimerTextView.stop(); - break; - - case MODE_RECORDING: - case MODE_STOPPING: - mHintTextView.setVisibility(GONE); - mTimerTextView.setVisibility(VISIBLE); - mSoundLevels.setEnabled(true); - mTimerTextView.restart(); - break; - - case MODE_STARTING: - break; // No-Op. - - default: - Assert.fail("invalid mode for AudioRecordView!"); - break; - } - updateRecordButtonAppearance(); - } - - public void setThemeColor(final int color) { - mThemeColor = color; - updateRecordButtonAppearance(); - } - - private void updateRecordButtonAppearance() { - final Drawable foregroundDrawable = getResources().getDrawable(R.drawable.ic_mp_audio_mic); - final GradientDrawable backgroundDrawable = ((GradientDrawable) getResources() - .getDrawable(R.drawable.audio_record_control_button_background)); - if (isRecording()) { - foregroundDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); - backgroundDrawable.setColor(mThemeColor); - } else { - foregroundDrawable.setColorFilter(mThemeColor, PorterDuff.Mode.SRC_ATOP); - backgroundDrawable.setColor(Color.WHITE); - } - mRecordButtonVisual.setImageDrawable(foregroundDrawable); - mRecordButtonVisual.setBackground(backgroundDrawable); - } - - @VisibleForTesting - boolean onRecordButtonTouchDown() { - if (!mMediaRecorder.isRecording() && mCurrentMode == MODE_IDLE) { - setMode(MODE_STARTING); - playAudioStartSound(new OnCompletionListener() { - @Override - public void onCompletion() { - // Double-check the current mode before recording since the user may have - // lifted finger from the button before the beeping sound is played through. - final int maxSize = MmsConfig.get(mHostInterface.getConversationSelfSubId()) - .getMaxMessageSize(); - if (mCurrentMode == MODE_STARTING && - mMediaRecorder.startRecording(AudioRecordView.this, - AudioRecordView.this, maxSize)) { - setMode(MODE_RECORDING); - } - } - }); - mAudioRecordStartTimeMillis = System.currentTimeMillis(); - return true; - } - return false; - } - - @VisibleForTesting - boolean onRecordButtonTouchUp() { - if (System.currentTimeMillis() - mAudioRecordStartTimeMillis < - AUDIO_RECORD_MINIMUM_DURATION_MILLIS) { - // The recording is too short, bolden the hint text to instruct the user to - // "tap+hold" to record audio. - final Uri outputUri = stopRecording(); - if (outputUri != null) { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - Factory.get().getApplicationContext().getContentResolver().delete( - outputUri, null, null); - } - }); - } - setMode(MODE_IDLE); - mHintTextView.setTypeface(null, Typeface.BOLD); - } else if (isRecording()) { - // Record for some extra time to ensure the ending part is saved. - setMode(MODE_STOPPING); - ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() { - @Override - public void run() { - onFinishedRecording(); - } - }, AUDIO_RECORD_ENDING_BUFFER_MILLIS); - } else { - setMode(MODE_IDLE); - } - return true; - } - - private Uri stopRecording() { - if (mMediaRecorder.isRecording()) { - return mMediaRecorder.stopRecording(); - } - return null; - } - - @Override // From MediaRecorder.OnInfoListener - public void onInfo(final MediaRecorder mr, final int what, final int extra) { - if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { - // Max size reached. Finish recording immediately. - LogUtil.i(LogUtil.BUGLE_TAG, "Max size reached while recording audio"); - onFinishedRecording(); - } else { - // These are unknown errors. - onErrorWhileRecording(what, extra); - } - } - - @Override // From MediaRecorder.OnErrorListener - public void onError(final MediaRecorder mr, final int what, final int extra) { - onErrorWhileRecording(what, extra); - } - - private void onErrorWhileRecording(final int what, final int extra) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error occurred during audio recording what=" + what + - ", extra=" + extra); - UiUtils.showToastAtBottom(R.string.audio_recording_error); - setMode(MODE_IDLE); - stopRecording(); - } - - private void onFinishedRecording() { - final Uri outputUri = stopRecording(); - if (outputUri != null) { - final Rect startRect = new Rect(); - mRecordButtonVisual.getGlobalVisibleRect(startRect); - final MediaPickerMessagePartData audioItem = - new MediaPickerMessagePartData(startRect, - ContentType.AUDIO_3GPP, outputUri, 0, 0); - mHostInterface.onAudioRecorded(audioItem); - } - playAudioEndSound(); - setMode(MODE_IDLE); - } - - private void playAudioStartSound(final OnCompletionListener completionListener) { - MediaUtil.get().playSound(getContext(), R.raw.audio_initiate, completionListener); - } - - private void playAudioEndSound() { - MediaUtil.get().playSound(getContext(), R.raw.audio_end, null); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java deleted file mode 100644 index 166ebd7..0000000 --- a/src/com/android/messaging/ui/mediapicker/CameraManager.java +++ /dev/null @@ -1,1200 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.OrientationEventListener; -import android.view.Surface; -import android.view.View; -import android.view.WindowManager; - -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.sms.MmsConfig; -import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager; -import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BugleGservicesKeys; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Class which manages interactions with the camera, but does not do any UI. This class is - * designed to be a singleton to ensure there is one component managing the camera and releasing - * the native resources. - * In order to acquire a camera, a caller must: - *

    - *
  • Call selectCamera to select front or back camera - *
  • Call setSurface to control where the preview is shown - *
  • Call openCamera to request the camera start preview - *
- * Callers should call onPause and onResume to ensure that the camera is release while the activity - * is not active. - * This class is not thread safe. It should only be called from one thread (the UI thread or test - * thread) - */ -class CameraManager implements FocusOverlayManager.Listener { - /** - * Wrapper around the framework camera API to allow mocking different hardware scenarios while - * unit testing - */ - interface CameraWrapper { - int getNumberOfCameras(); - void getCameraInfo(int index, CameraInfo cameraInfo); - Camera open(int cameraId); - /** Add a wrapper for release because a final method cannot be mocked */ - void release(Camera camera); - } - - /** - * Callbacks for the camera manager listener - */ - interface CameraManagerListener { - void onCameraError(int errorCode, Exception e); - void onCameraChanged(); - } - - /** - * Callback when taking image or video - */ - interface MediaCallback { - static final int MEDIA_CAMERA_CHANGED = 1; - static final int MEDIA_NO_DATA = 2; - - void onMediaReady(Uri uriToMedia, String contentType, int width, int height); - void onMediaFailed(Exception exception); - void onMediaInfo(int what); - } - - // Error codes - static final int ERROR_OPENING_CAMERA = 1; - static final int ERROR_SHOWING_PREVIEW = 2; - static final int ERROR_INITIALIZING_VIDEO = 3; - static final int ERROR_STORAGE_FAILURE = 4; - static final int ERROR_RECORDING_VIDEO = 5; - static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6; - static final int ERROR_TAKING_PICTURE = 7; - - private static final String TAG = LogUtil.BUGLE_TAG; - private static final int NO_CAMERA_SELECTED = -1; - - private static CameraManager sInstance; - - /** Default camera wrapper which directs calls to the framework APIs */ - private static CameraWrapper sCameraWrapper = new CameraWrapper() { - @Override - public int getNumberOfCameras() { - return Camera.getNumberOfCameras(); - } - - @Override - public void getCameraInfo(final int index, final CameraInfo cameraInfo) { - Camera.getCameraInfo(index, cameraInfo); - } - - @Override - public Camera open(final int cameraId) { - return Camera.open(cameraId); - } - - @Override - public void release(final Camera camera) { - camera.release(); - } - }; - - /** The CameraInfo for the currently selected camera */ - private final CameraInfo mCameraInfo; - - /** - * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet - */ - private int mCameraIndex; - - /** True if the device has front and back cameras */ - private final boolean mHasFrontAndBackCamera; - - /** True if the camera should be open (may not yet be actually open) */ - private boolean mOpenRequested; - - /** True if the camera is requested to be in video mode */ - private boolean mVideoModeRequested; - - /** The media recorder for video mode */ - private MmsVideoRecorder mMediaRecorder; - - /** Callback to call with video recording updates */ - private MediaCallback mVideoCallback; - - /** The preview view to show the preview on */ - private CameraPreview mCameraPreview; - - /** The helper classs to handle orientation changes */ - private OrientationHandler mOrientationHandler; - - /** Tracks whether the preview has hardware acceleration */ - private boolean mIsHardwareAccelerationSupported; - - /** - * The task for opening the camera, so it doesn't block the UI thread - * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't - * need to be on the UI thread - * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may - * need to create a dedicated thread, or synchronize the threads in the thread pool - */ - private AsyncTask mOpenCameraTask; - - /** - * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if - * no open task is pending - */ - private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED; - - /** The instance of the currently opened camera */ - private Camera mCamera; - - /** The rotation of the screen relative to the camera's natural orientation */ - private int mRotation; - - /** The callback to notify when errors or other events occur */ - private CameraManagerListener mListener; - - /** True if the camera is currently in the process of taking an image */ - private boolean mTakingPicture; - - /** Provides subscription-related data to access per-subscription configurations. */ - private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider; - - /** Manages auto focus visual and behavior */ - private final FocusOverlayManager mFocusOverlayManager; - - private CameraManager() { - mCameraInfo = new CameraInfo(); - mCameraIndex = NO_CAMERA_SELECTED; - - // Check to see if a front and back camera exist - boolean hasFrontCamera = false; - boolean hasBackCamera = false; - final CameraInfo cameraInfo = new CameraInfo(); - final int cameraCount = sCameraWrapper.getNumberOfCameras(); - try { - for (int i = 0; i < cameraCount; i++) { - sCameraWrapper.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { - hasFrontCamera = true; - } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { - hasBackCamera = true; - } - if (hasFrontCamera && hasBackCamera) { - break; - } - } - } catch (final RuntimeException e) { - LogUtil.e(TAG, "Unable to load camera info", e); - } - mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera; - mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper()); - - // Assume the best until we are proven otherwise - mIsHardwareAccelerationSupported = true; - } - - /** Gets the singleton instance */ - static CameraManager get() { - if (sInstance == null) { - sInstance = new CameraManager(); - } - return sInstance; - } - - /** Allows tests to inject a custom camera wrapper */ - @VisibleForTesting - static void setCameraWrapper(final CameraWrapper cameraWrapper) { - sCameraWrapper = cameraWrapper; - sInstance = null; - } - - /** - * Sets the surface to use to display the preview - * This must only be called AFTER the CameraPreview has a texture ready - * @param preview The preview surface view - */ - void setSurface(final CameraPreview preview) { - if (preview == mCameraPreview) { - return; - } - - if (preview != null) { - Assert.isTrue(preview.isValid()); - preview.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent motionEvent) { - if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) == - MotionEvent.ACTION_UP) { - mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight()); - mFocusOverlayManager.onSingleTapUp( - (int) motionEvent.getX() + view.getLeft(), - (int) motionEvent.getY() + view.getTop()); - } - return true; - } - }); - } - mCameraPreview = preview; - tryShowPreview(); - } - - void setRenderOverlay(final RenderOverlay renderOverlay) { - mFocusOverlayManager.setFocusRenderer(renderOverlay != null ? - renderOverlay.getPieRenderer() : null); - } - - /** Convenience function to swap between front and back facing cameras */ - void swapCamera() { - Assert.isTrue(mCameraIndex >= 0); - selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ? - CameraInfo.CAMERA_FACING_BACK : - CameraInfo.CAMERA_FACING_FRONT); - } - - /** - * Selects the first camera facing the desired direction, or the first camera if there is no - * camera in the desired direction - * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants - * @return True if a camera was selected, or false if selecting a camera failed - */ - boolean selectCamera(final int desiredFacing) { - try { - // We already selected a camera facing that direction - if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) { - return true; - } - - final int cameraCount = sCameraWrapper.getNumberOfCameras(); - Assert.isTrue(cameraCount > 0); - - mCameraIndex = NO_CAMERA_SELECTED; - setCamera(null); - final CameraInfo cameraInfo = new CameraInfo(); - for (int i = 0; i < cameraCount; i++) { - sCameraWrapper.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == desiredFacing) { - mCameraIndex = i; - sCameraWrapper.getCameraInfo(i, mCameraInfo); - break; - } - } - - // There's no camera in the desired facing direction, just select the first camera - // regardless of direction - if (mCameraIndex < 0) { - mCameraIndex = 0; - sCameraWrapper.getCameraInfo(0, mCameraInfo); - } - - if (mOpenRequested) { - // The camera is open, so reopen with the newly selected camera - openCamera(); - } - return true; - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e); - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, e); - } - return false; - } - } - - int getCameraIndex() { - return mCameraIndex; - } - - void selectCameraByIndex(final int cameraIndex) { - if (mCameraIndex == cameraIndex) { - return; - } - - try { - mCameraIndex = cameraIndex; - sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo); - if (mOpenRequested) { - openCamera(); - } - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e); - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, e); - } - } - } - - @VisibleForTesting - CameraInfo getCameraInfo() { - if (mCameraIndex == NO_CAMERA_SELECTED) { - return null; - } - return mCameraInfo; - } - - /** @return True if this device has camera capabilities */ - boolean hasAnyCamera() { - return sCameraWrapper.getNumberOfCameras() > 0; - } - - /** @return True if the device has both a front and back camera */ - boolean hasFrontAndBackCamera() { - return mHasFrontAndBackCamera; - } - - /** - * Opens the camera on a separate thread and initiates the preview if one is available - */ - void openCamera() { - if (mCameraIndex == NO_CAMERA_SELECTED) { - // Ensure a selected camera if none is currently selected. This may happen if the - // camera chooser is not the default media chooser. - selectCamera(CameraInfo.CAMERA_FACING_BACK); - } - mOpenRequested = true; - // We're already opening the camera or already have the camera handle, nothing more to do - if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) { - return; - } - - // True if the task to open the camera has to be delayed until the current one completes - boolean delayTask = false; - - // Cancel any previous open camera tasks - if (mOpenCameraTask != null) { - mPendingOpenCameraIndex = NO_CAMERA_SELECTED; - delayTask = true; - } - - mPendingOpenCameraIndex = mCameraIndex; - mOpenCameraTask = new AsyncTask() { - private Exception mException; - - @Override - protected Camera doInBackground(final Integer... params) { - try { - final int cameraIndex = params[0]; - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Opening camera " + mCameraIndex); - } - return sCameraWrapper.open(cameraIndex); - } catch (final Exception e) { - LogUtil.e(TAG, "Exception while opening camera", e); - mException = e; - return null; - } - } - - @Override - protected void onPostExecute(final Camera camera) { - // If we completed, but no longer want this camera, then release the camera - if (mOpenCameraTask != this || !mOpenRequested) { - releaseCamera(camera); - cleanup(); - return; - } - - cleanup(); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null)); - } - - setCamera(camera); - if (camera == null) { - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, mException); - } - LogUtil.e(TAG, "Error opening camera"); - } - } - - @Override - protected void onCancelled() { - super.onCancelled(); - cleanup(); - } - - private void cleanup() { - mPendingOpenCameraIndex = NO_CAMERA_SELECTED; - if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) { - // If there's another task waiting on this one to complete, start it now - mOpenCameraTask.execute(mCameraIndex); - } else { - mOpenCameraTask = null; - } - - } - }; - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Start opening camera " + mCameraIndex); - } - - if (!delayTask) { - mOpenCameraTask.execute(mCameraIndex); - } - } - - boolean isVideoMode() { - return mVideoModeRequested; - } - - boolean isRecording() { - return mVideoModeRequested && mVideoCallback != null; - } - - void setVideoMode(final boolean videoMode) { - if (mVideoModeRequested == videoMode) { - return; - } - mVideoModeRequested = videoMode; - tryInitOrCleanupVideoMode(); - } - - /** Closes the camera releasing the resources it uses */ - void closeCamera() { - mOpenRequested = false; - setCamera(null); - } - - /** Temporarily closes the camera if it is open */ - void onPause() { - setCamera(null); - } - - /** Reopens the camera if it was opened when onPause was called */ - void onResume() { - if (mOpenRequested) { - openCamera(); - } - } - - /** - * Sets the listener which will be notified of errors or other events in the camera - * @param listener The listener to notify - */ - void setListener(final CameraManagerListener listener) { - Assert.isMainThread(); - mListener = listener; - if (!mIsHardwareAccelerationSupported && mListener != null) { - mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null); - } - } - - void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) { - mSubscriptionDataProvider = provider; - } - - void takePicture(final float heightPercent, @NonNull final MediaCallback callback) { - Assert.isTrue(!mVideoModeRequested); - Assert.isTrue(!mTakingPicture); - Assert.notNull(callback); - if (mCamera == null) { - // The caller should have checked isCameraAvailable first, but just in case, protect - // against a null camera by notifying the callback that taking the picture didn't work - callback.onMediaFailed(null); - return; - } - final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() { - @Override - public void onPictureTaken(final byte[] bytes, final Camera camera) { - mTakingPicture = false; - if (mCamera != camera) { - // This may happen if the camera was changed between front/back while the - // picture is being taken. - callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED); - return; - } - - if (bytes == null) { - callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA); - return; - } - - final Camera.Size size = camera.getParameters().getPictureSize(); - int width; - int height; - if (mRotation == 90 || mRotation == 270) { - width = size.height; - height = size.width; - } else { - width = size.width; - height = size.height; - } - new ImagePersistTask( - width, height, heightPercent, bytes, mCameraPreview.getContext(), callback) - .executeOnThreadPool(); - } - }; - - mTakingPicture = true; - try { - mCamera.takePicture( - null /* shutter */, - null /* raw */, - null /* postView */, - jpegCallback); - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e); - mTakingPicture = false; - if (mListener != null) { - mListener.onCameraError(ERROR_TAKING_PICTURE, e); - } - } - } - - void startVideo(final MediaCallback callback) { - Assert.notNull(callback); - Assert.isTrue(!isRecording()); - mVideoCallback = callback; - tryStartVideoCapture(); - } - - /** - * Asynchronously releases a camera - * @param camera The camera to release - */ - private void releaseCamera(final Camera camera) { - if (camera == null) { - return; - } - - mFocusOverlayManager.onCameraReleased(); - - new AsyncTask() { - @Override - protected Void doInBackground(final Void... params) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "Releasing camera " + mCameraIndex); - } - sCameraWrapper.release(camera); - return null; - } - }.execute(); - } - - private void releaseMediaRecorder(final boolean cleanupFile) { - if (mMediaRecorder == null) { - return; - } - mVideoModeRequested = false; - - if (cleanupFile) { - mMediaRecorder.cleanupTempFile(); - if (mVideoCallback != null) { - final MediaCallback callback = mVideoCallback; - mVideoCallback = null; - // Notify the callback that we've stopped recording - callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/, - 0 /*height*/); - } - } - - mMediaRecorder.release(); - mMediaRecorder = null; - - if (mCamera != null) { - try { - mCamera.reconnect(); - } catch (final IOException e) { - LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e); - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, e); - } - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e); - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, e); - } - } - } - restoreRequestedOrientation(); - } - - /** Updates the orientation of the camera to match the orientation of the device */ - private void updateCameraOrientation() { - if (mCamera == null || mCameraPreview == null || mTakingPicture) { - return; - } - - final WindowManager windowManager = - (WindowManager) mCameraPreview.getContext().getSystemService( - Context.WINDOW_SERVICE); - - int degrees = 0; - switch (windowManager.getDefaultDisplay().getRotation()) { - case Surface.ROTATION_0: degrees = 0; break; - case Surface.ROTATION_90: degrees = 90; break; - case Surface.ROTATION_180: degrees = 180; break; - case Surface.ROTATION_270: degrees = 270; break; - } - - // The display orientation of the camera (this controls the preview image). - int orientation; - - // The clockwise rotation angle relative to the orientation of the camera. This affects - // pictures returned by the camera in Camera.PictureCallback. - int rotation; - if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - orientation = (mCameraInfo.orientation + degrees) % 360; - rotation = orientation; - // compensate the mirror but only for orientation - orientation = (360 - orientation) % 360; - } else { // back-facing - orientation = (mCameraInfo.orientation - degrees + 360) % 360; - rotation = orientation; - } - mRotation = rotation; - if (mMediaRecorder == null) { - try { - mCamera.setDisplayOrientation(orientation); - final Camera.Parameters params = mCamera.getParameters(); - params.setRotation(rotation); - mCamera.setParameters(params); - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e); - if (mListener != null) { - mListener.onCameraError(ERROR_OPENING_CAMERA, e); - } - } - } - } - - /** Sets the current camera, releasing any previously opened camera */ - private void setCamera(final Camera camera) { - if (mCamera == camera) { - return; - } - - releaseMediaRecorder(true /* cleanupFile */); - releaseCamera(mCamera); - mCamera = camera; - tryShowPreview(); - if (mListener != null) { - mListener.onCameraChanged(); - } - } - - /** Shows the preview if the camera is open and the preview is loaded */ - private void tryShowPreview() { - if (mCameraPreview == null || mCamera == null) { - if (mOrientationHandler != null) { - mOrientationHandler.disable(); - mOrientationHandler = null; - } - releaseMediaRecorder(true /* cleanupFile */); - mFocusOverlayManager.onPreviewStopped(); - return; - } - try { - mCamera.stopPreview(); - updateCameraOrientation(); - - final Camera.Parameters params = mCamera.getParameters(); - final Camera.Size pictureSize = chooseBestPictureSize(); - final Camera.Size previewSize = chooseBestPreviewSize(pictureSize); - params.setPreviewSize(previewSize.width, previewSize.height); - params.setPictureSize(pictureSize.width, pictureSize.height); - logCameraSize("Setting preview size: ", previewSize); - logCameraSize("Setting picture size: ", pictureSize); - mCameraPreview.setSize(previewSize, mCameraInfo.orientation); - for (final String focusMode : params.getSupportedFocusModes()) { - if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - // Use continuous focus if available - params.setFocusMode(focusMode); - break; - } - } - - mCamera.setParameters(params); - mCameraPreview.startPreview(mCamera); - mCamera.startPreview(); - mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() { - @Override - public void onAutoFocusMoving(final boolean start, final Camera camera) { - mFocusOverlayManager.onAutoFocusMoving(start); - } - }); - mFocusOverlayManager.setParameters(mCamera.getParameters()); - mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK); - mFocusOverlayManager.onPreviewStarted(); - tryInitOrCleanupVideoMode(); - if (mOrientationHandler == null) { - mOrientationHandler = new OrientationHandler(mCameraPreview.getContext()); - mOrientationHandler.enable(); - } - } catch (final IOException e) { - LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e); - if (mListener != null) { - mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); - } - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e); - if (mListener != null) { - mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); - } - } - } - - private void tryInitOrCleanupVideoMode() { - if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) { - releaseMediaRecorder(true /* cleanupFile */); - return; - } - - if (mMediaRecorder != null) { - return; - } - - try { - mCamera.unlock(); - final int maxMessageSize = getMmsConfig().getMaxMessageSize(); - mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize); - mMediaRecorder.prepare(); - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e); - if (mListener != null) { - mListener.onCameraError(ERROR_STORAGE_FAILURE, e); - } - setVideoMode(false); - return; - } catch (final IOException e) { - LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e); - if (mListener != null) { - mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e); - } - setVideoMode(false); - return; - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e); - if (mListener != null) { - mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e); - } - setVideoMode(false); - return; - } - - tryStartVideoCapture(); - } - - private void tryStartVideoCapture() { - if (mMediaRecorder == null || mVideoCallback == null) { - return; - } - - mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() { - @Override - public void onError(final MediaRecorder mediaRecorder, final int what, - final int extra) { - if (mListener != null) { - mListener.onCameraError(ERROR_RECORDING_VIDEO, null); - } - restoreRequestedOrientation(); - } - }); - - mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { - @Override - public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) { - if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED || - what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { - stopVideo(); - } - } - }); - - try { - mMediaRecorder.start(); - final Activity activity = UiUtils.getActivity(mCameraPreview.getContext()); - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - lockOrientation(); - } catch (final IllegalStateException e) { - LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e); - if (mListener != null) { - mListener.onCameraError(ERROR_RECORDING_VIDEO, e); - } - setVideoMode(false); - restoreRequestedOrientation(); - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e); - if (mListener != null) { - mListener.onCameraError(ERROR_RECORDING_VIDEO, e); - } - setVideoMode(false); - restoreRequestedOrientation(); - } - } - - void stopVideo() { - int width = ImageRequest.UNSPECIFIED_SIZE; - int height = ImageRequest.UNSPECIFIED_SIZE; - Uri uri = null; - String contentType = null; - try { - final Activity activity = UiUtils.getActivity(mCameraPreview.getContext()); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - mMediaRecorder.stop(); - width = mMediaRecorder.getVideoWidth(); - height = mMediaRecorder.getVideoHeight(); - uri = mMediaRecorder.getVideoUri(); - contentType = mMediaRecorder.getContentType(); - } catch (final RuntimeException e) { - // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the - // finally clause call the callback with null uri and handle cleanup - LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e); - } finally { - final MediaCallback videoCallback = mVideoCallback; - mVideoCallback = null; - releaseMediaRecorder(false /* cleanupFile */); - if (uri == null) { - tryInitOrCleanupVideoMode(); - } - videoCallback.onMediaReady(uri, contentType, width, height); - } - } - - boolean isCameraAvailable() { - return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported; - } - - /** - * External components call into this to report if hardware acceleration is supported. When - * hardware acceleration isn't supported, we need to report an error through the listener - * interface - * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware - * accelerated view. - */ - void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) { - Assert.isMainThread(); - if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) { - // If the value hasn't changed nothing more to do - return; - } - - mIsHardwareAccelerationSupported = isHardwareAccelerationSupported; - if (!isHardwareAccelerationSupported) { - LogUtil.e(TAG, "Software rendering - cannot open camera"); - if (mListener != null) { - mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null); - } - } - } - - /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */ - private float getScaleFactorForMaxAllowedSize(final int width, final int height, - final int maxWidth, final int maxHeight) { - if (maxWidth <= 0 || maxHeight <= 0) { - // MmsConfig initialization runs asynchronously on application startup, so there's a - // chance (albeit a very slight one) that we don't have it yet. - LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig"); - return 1.0f; - } - - if (width <= maxWidth && height <= maxHeight) { - // Already meeting requirements. - return 1.0f; - } - - return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height); - } - - private MmsConfig getMmsConfig() { - final int subId = mSubscriptionDataProvider != null ? - mSubscriptionDataProvider.getConversationSelfSubId() : - ParticipantData.DEFAULT_SELF_SUB_ID; - return MmsConfig.get(subId); - } - - /** - * Choose the best picture size by trying to find a size close to the MmsConfig's max size, - * which is closest to the screen aspect ratio - */ - private Camera.Size chooseBestPictureSize() { - final Context context = mCameraPreview.getContext(); - final Resources resources = context.getResources(); - final DisplayMetrics displayMetrics = resources.getDisplayMetrics(); - final int displayOrientation = resources.getConfiguration().orientation; - int cameraOrientation = mCameraInfo.orientation; - - int screenWidth; - int screenHeight; - if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) { - // Rotate the camera orientation 90 degrees to compensate for the rotated display - // metrics. Direction doesn't matter because we're just using it for width/height - cameraOrientation += 90; - } - - // Check the camera orientation relative to the display. - // For 0, 180, 360, the screen width/height are the display width/height - // For 90, 270, the screen width/height are inverted from the display - if (cameraOrientation % 180 == 0) { - screenWidth = displayMetrics.widthPixels; - screenHeight = displayMetrics.heightPixels; - } else { - screenWidth = displayMetrics.heightPixels; - screenHeight = displayMetrics.widthPixels; - } - - final MmsConfig mmsConfig = getMmsConfig(); - final int maxWidth = mmsConfig.getMaxImageWidth(); - final int maxHeight = mmsConfig.getMaxImageHeight(); - - // Constrain the size within the max width/height defined by MmsConfig. - final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight, - maxWidth, maxHeight); - screenWidth *= scaleFactor; - screenHeight *= scaleFactor; - - final float aspectRatio = BugleGservices.get().getFloat( - BugleGservicesKeys.CAMERA_ASPECT_RATIO, - screenWidth / (float) screenHeight); - final List sizes = new ArrayList( - mCamera.getParameters().getSupportedPictureSizes()); - final int maxPixels = maxWidth * maxHeight; - - // Sort the sizes so the best size is first - Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels)); - - return sizes.get(0); - } - - /** - * Chose the best preview size based on the picture size. Try to find a size with the same - * aspect ratio and size as the picture if possible - */ - private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) { - final List sizes = new ArrayList( - mCamera.getParameters().getSupportedPreviewSizes()); - final float aspectRatio = pictureSize.width / (float) pictureSize.height; - final int capturePixels = pictureSize.width * pictureSize.height; - - // Sort the sizes so the best size is first - Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE, - aspectRatio, capturePixels)); - - return sizes.get(0); - } - - private class OrientationHandler extends OrientationEventListener { - OrientationHandler(final Context context) { - super(context); - } - - @Override - public void onOrientationChanged(final int orientation) { - updateCameraOrientation(); - } - } - - private static class SizeComparator implements Comparator { - private static final int PREFER_LEFT = -1; - private static final int PREFER_RIGHT = 1; - - // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit - private final int mMaxWidth; - private final int mMaxHeight; - - // The desired aspect ratio - private final float mTargetAspectRatio; - - // The desired size (width x height) to try to match - private final int mTargetPixels; - - public SizeComparator(final int maxWidth, final int maxHeight, - final float targetAspectRatio, final int targetPixels) { - mMaxWidth = maxWidth; - mMaxHeight = maxHeight; - mTargetAspectRatio = targetAspectRatio; - mTargetPixels = targetPixels; - } - - /** - * Returns a negative value if left is a better choice than right, or a positive value if - * right is a better choice is better than left. 0 if they are equal - */ - @Override - public int compare(final Camera.Size left, final Camera.Size right) { - // If one size is less than the max size prefer it over the other - if ((left.width <= mMaxWidth && left.height <= mMaxHeight) != - (right.width <= mMaxWidth && right.height <= mMaxHeight)) { - return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT; - } - - // If one is closer to the target aspect ratio, prefer it. - final float leftAspectRatio = left.width / (float) left.height; - final float rightAspectRatio = right.width / (float) right.height; - final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio); - final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio); - if (leftAspectRatioDiff != rightAspectRatioDiff) { - return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ? - PREFER_LEFT : PREFER_RIGHT; - } - - // At this point they have the same aspect ratio diff and are either both bigger - // than the max size or both smaller than the max size, so prefer the one closest - // to target size - final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels); - final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels); - return leftDiff - rightDiff; - } - } - - @Override // From FocusOverlayManager.Listener - public void autoFocus() { - if (mCamera == null) { - return; - } - - try { - mCamera.autoFocus(new Camera.AutoFocusCallback() { - @Override - public void onAutoFocus(final boolean success, final Camera camera) { - mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */); - } - }); - } catch (final RuntimeException e) { - LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e); - // If autofocus fails, the camera should have called the callback with success=false, - // but some throw an exception here - mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/); - } - } - - @Override // From FocusOverlayManager.Listener - public void cancelAutoFocus() { - if (mCamera == null) { - return; - } - try { - mCamera.cancelAutoFocus(); - } catch (final RuntimeException e) { - // Ignore - LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e); - } - } - - @Override // From FocusOverlayManager.Listener - public boolean capture() { - return false; - } - - @Override // From FocusOverlayManager.Listener - public void setFocusParameters() { - if (mCamera == null) { - return; - } - try { - final Camera.Parameters parameters = mCamera.getParameters(); - parameters.setFocusMode(mFocusOverlayManager.getFocusMode()); - if (parameters.getMaxNumFocusAreas() > 0) { - // Don't set focus areas (even to null) if focus areas aren't supported, camera may - // crash - parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas()); - } - parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas()); - mCamera.setParameters(parameters); - } catch (final RuntimeException e) { - // This occurs when the device is out of space or when the camera is locked - LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters"); - } - } - - private void logCameraSize(final String prefix, final Camera.Size size) { - // Log the camera size and aspect ratio for help when examining bug reports for camera - // failures - LogUtil.i(TAG, prefix + size.width + "x" + size.height + - " (" + (size.width / (float) size.height) + ")"); - } - - - private Integer mSavedOrientation = null; - - private void lockOrientation() { - // when we start recording, lock our orientation - final Activity a = UiUtils.getActivity(mCameraPreview.getContext()); - final WindowManager windowManager = - (WindowManager) a.getSystemService(Context.WINDOW_SERVICE); - final int rotation = windowManager.getDefaultDisplay().getRotation(); - - mSavedOrientation = a.getRequestedOrientation(); - switch (rotation) { - case Surface.ROTATION_0: - a.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - break; - case Surface.ROTATION_90: - a.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - break; - case Surface.ROTATION_180: - a.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); - break; - case Surface.ROTATION_270: - a.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); - break; - } - - } - - private void restoreRequestedOrientation() { - if (mSavedOrientation != null) { - final Activity a = UiUtils.getActivity(mCameraPreview.getContext()); - if (a != null) { - a.setRequestedOrientation(mSavedOrientation); - } - mSavedOrientation = null; - } - } - - static boolean hasCameraPermission() { - return OsUtil.hasPermission(Manifest.permission.CAMERA); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java deleted file mode 100644 index 2c7a7f2..0000000 --- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Rect; -import android.hardware.Camera; -import android.net.Uri; -import android.os.SystemClock; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.widget.Chronometer; -import android.widget.ImageButton; - -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MediaPickerMessagePartData; -import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback; -import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -/** - * Chooser which allows the user to take pictures or video without leaving the current app/activity - */ -class CameraMediaChooser extends MediaChooser implements - CameraManager.CameraManagerListener { - private CameraPreview.CameraPreviewHost mCameraPreviewHost; - private ImageButton mFullScreenButton; - private ImageButton mSwapCameraButton; - private ImageButton mSwapModeButton; - private ImageButton mCaptureButton; - private ImageButton mCancelVideoButton; - private Chronometer mVideoCounter; - private boolean mVideoCancelled; - private int mErrorToast; - private View mEnabledView; - private View mMissingPermissionView; - - CameraMediaChooser(final MediaPicker mediaPicker) { - super(mediaPicker); - } - - @Override - public int getSupportedMediaTypes() { - if (CameraManager.get().hasAnyCamera()) { - return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO; - } else { - return MediaPicker.MEDIA_TYPE_NONE; - } - } - - @Override - public View destroyView() { - CameraManager.get().closeCamera(); - CameraManager.get().setListener(null); - CameraManager.get().setSubscriptionDataProvider(null); - return super.destroyView(); - } - - @Override - protected View createView(final ViewGroup container) { - CameraManager.get().setListener(this); - CameraManager.get().setSubscriptionDataProvider(this); - CameraManager.get().setVideoMode(false); - final LayoutInflater inflater = getLayoutInflater(); - final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate( - R.layout.mediapicker_camera_chooser, - container /* root */, - false /* attachToRoot */); - mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById( - R.id.camera_preview); - mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent motionEvent) { - if (CameraManager.get().isVideoMode()) { - // Prevent the swipe down in video mode because video is always captured in - // full screen - return true; - } - - return false; - } - }); - - final View shutterVisual = view.findViewById(R.id.camera_shutter_visual); - - mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button); - mFullScreenButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - mMediaPicker.setFullScreen(true); - } - }); - - mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button); - mSwapCameraButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - CameraManager.get().swapCamera(); - } - }); - - mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button); - mCaptureButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() / - (float) mCameraPreviewHost.getView().getHeight(), 1); - - if (CameraManager.get().isRecording()) { - CameraManager.get().stopVideo(); - } else { - final CameraManager.MediaCallback callback = new CameraManager.MediaCallback() { - @Override - public void onMediaReady( - final Uri uriToVideo, final String contentType, - final int width, final int height) { - mVideoCounter.stop(); - if (mVideoCancelled || uriToVideo == null) { - mVideoCancelled = false; - } else { - final Rect startRect = new Rect(); - // It's possible to throw out the chooser while taking the - // picture/video. In that case, still use the attachment, just - // skip the startRect - if (mView != null) { - mView.getGlobalVisibleRect(startRect); - } - mMediaPicker.dispatchItemsSelected( - new MediaPickerMessagePartData(startRect, contentType, - uriToVideo, width, height), - true /* dismissMediaPicker */); - } - updateViewState(); - } - - @Override - public void onMediaFailed(final Exception exception) { - UiUtils.showToastAtBottom(R.string.camera_media_failure); - updateViewState(); - } - - @Override - public void onMediaInfo(final int what) { - if (what == MediaCallback.MEDIA_NO_DATA) { - UiUtils.showToastAtBottom(R.string.camera_media_failure); - } - updateViewState(); - } - }; - if (CameraManager.get().isVideoMode()) { - CameraManager.get().startVideo(callback); - mVideoCounter.setBase(SystemClock.elapsedRealtime()); - mVideoCounter.start(); - updateViewState(); - } else { - showShutterEffect(shutterVisual); - CameraManager.get().takePicture(heightPercent, callback); - updateViewState(); - } - } - } - }); - - mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button); - mSwapModeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode(); - if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) { - requestRecordAudioPermission(); - } else { - onSwapMode(); - } - } - }); - - mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button); - mCancelVideoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - mVideoCancelled = true; - CameraManager.get().stopVideo(); - mMediaPicker.dismiss(true); - } - }); - - mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter); - - CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual)); - - mEnabledView = view.findViewById(R.id.mediapicker_enabled); - mMissingPermissionView = view.findViewById(R.id.missing_permission_view); - - // Must set mView before calling updateViewState because it operates on mView - mView = view; - updateViewState(); - updateForPermissionState(CameraManager.hasCameraPermission()); - return view; - } - - @Override - public int getIconResource() { - return R.drawable.ic_camera_light; - } - - @Override - public int getIconDescriptionResource() { - return R.string.mediapicker_cameraChooserDescription; - } - - /** - * Updates the view when entering or leaving full-screen camera mode - * @param fullScreen - */ - @Override - void onFullScreenChanged(final boolean fullScreen) { - super.onFullScreenChanged(fullScreen); - if (!fullScreen && CameraManager.get().isVideoMode()) { - CameraManager.get().setVideoMode(false); - } - updateViewState(); - } - - /** - * Initializes the control to a default state when it is opened / closed - * @param open True if the control is opened - */ - @Override - void onOpenedChanged(final boolean open) { - super.onOpenedChanged(open); - updateViewState(); - } - - @Override - protected void setSelected(final boolean selected) { - super.setSelected(selected); - if (selected) { - if (CameraManager.hasCameraPermission()) { - // If an error occurred before the chooser was selected, show it now - showErrorToastIfNeeded(); - } else { - requestCameraPermission(); - } - } - } - - private void requestCameraPermission() { - mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA }, - MediaPicker.CAMERA_PERMISSION_REQUEST_CODE); - } - - private void requestRecordAudioPermission() { - mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, - MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE); - } - - @Override - protected void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { - if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) { - final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; - updateForPermissionState(permissionGranted); - if (permissionGranted) { - mCameraPreviewHost.onCameraPermissionGranted(); - } - } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) { - Assert.isFalse(CameraManager.get().isVideoMode()); - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Switch to video mode - onSwapMode(); - } else { - // Stay in still-photo mode - } - } - } - - private void updateForPermissionState(final boolean granted) { - // onRequestPermissionsResult can sometimes get called before createView(). - if (mEnabledView == null) { - return; - } - - mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE); - mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE); - } - - @Override - public boolean canSwipeDown() { - if (CameraManager.get().isVideoMode()) { - return true; - } - return super.canSwipeDown(); - } - - /** - * Handles an error from the camera manager by showing the appropriate error message to the user - * @param errorCode One of the CameraManager.ERROR_* constants - * @param e The exception which caused the error, if any - */ - @Override - public void onCameraError(final int errorCode, final Exception e) { - switch (errorCode) { - case CameraManager.ERROR_OPENING_CAMERA: - case CameraManager.ERROR_SHOWING_PREVIEW: - mErrorToast = R.string.camera_error_opening; - break; - case CameraManager.ERROR_INITIALIZING_VIDEO: - mErrorToast = R.string.camera_error_video_init_fail; - updateViewState(); - break; - case CameraManager.ERROR_STORAGE_FAILURE: - mErrorToast = R.string.camera_error_storage_fail; - updateViewState(); - break; - case CameraManager.ERROR_TAKING_PICTURE: - mErrorToast = R.string.camera_error_failure_taking_picture; - break; - default: - mErrorToast = R.string.camera_error_unknown; - LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode); - break; - } - showErrorToastIfNeeded(); - } - - private void showErrorToastIfNeeded() { - if (mErrorToast != 0 && mSelected) { - UiUtils.showToastAtBottom(mErrorToast); - mErrorToast = 0; - } - } - - @Override - public void onCameraChanged() { - updateViewState(); - } - - private void onSwapMode() { - CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode()); - if (CameraManager.get().isVideoMode()) { - mMediaPicker.setFullScreen(true); - - // For now we start recording immediately - mCaptureButton.performClick(); - } - updateViewState(); - } - - private void showShutterEffect(final View shutterVisual) { - final float maxAlpha = getContext().getResources().getFraction( - R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */); - - // Divide by 2 so each half of the animation adds up to the full duration - final int animationDuration = getContext().getResources().getInteger( - R.integer.camera_shutter_duration) / 2; - - final AnimationSet animation = new AnimationSet(false /* shareInterpolator */); - final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha); - alphaInAnimation.setDuration(animationDuration); - animation.addAnimation(alphaInAnimation); - - final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f); - alphaOutAnimation.setStartOffset(animationDuration); - alphaOutAnimation.setDuration(animationDuration); - animation.addAnimation(alphaOutAnimation); - - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(final Animation animation) { - shutterVisual.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(final Animation animation) { - shutterVisual.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(final Animation animation) { - } - }); - shutterVisual.startAnimation(animation); - } - - /** Updates the state of the buttons and overlays based on the current state of the view */ - private void updateViewState() { - if (mView == null) { - return; - } - - final Context context = getContext(); - if (context == null) { - // Context is null if the fragment was already removed from the activity - return; - } - final boolean fullScreen = mMediaPicker.isFullScreen(); - final boolean videoMode = CameraManager.get().isVideoMode(); - final boolean isRecording = CameraManager.get().isRecording(); - final boolean isCameraAvailable = isCameraAvailable(); - final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo(); - final boolean frontCamera = cameraInfo != null && cameraInfo.facing == - Camera.CameraInfo.CAMERA_FACING_FRONT; - - mView.setSystemUiVisibility( - fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE : - View.SYSTEM_UI_FLAG_VISIBLE); - - mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE); - mFullScreenButton.setEnabled(isCameraAvailable); - mSwapCameraButton.setVisibility( - fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ? - View.VISIBLE : View.GONE); - mSwapCameraButton.setImageResource(frontCamera ? - R.drawable.ic_camera_front_light : - R.drawable.ic_camera_rear_light); - mSwapCameraButton.setEnabled(isCameraAvailable); - - mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE); - mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE); - - mSwapModeButton.setImageResource(videoMode ? - R.drawable.ic_mp_camera_small_light : - R.drawable.ic_mp_video_small_light); - mSwapModeButton.setContentDescription(context.getString(videoMode ? - R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode)); - mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE); - mSwapModeButton.setEnabled(isCameraAvailable); - - if (isRecording) { - mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_large_light); - mCaptureButton.setContentDescription(context.getString( - R.string.camera_stop_recording)); - } else if (videoMode) { - mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light); - mCaptureButton.setContentDescription(context.getString( - R.string.camera_start_recording)); - } else { - mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_light); - mCaptureButton.setContentDescription(context.getString( - R.string.camera_take_picture)); - } - mCaptureButton.setEnabled(isCameraAvailable); - } - - @Override - int getActionBarTitleResId() { - return 0; - } - - /** - * Returns if the camera is currently ready camera is loaded and not taking a picture. - * otherwise we should avoid taking another picture, swapping camera or recording video. - */ - private boolean isCameraAvailable() { - return CameraManager.get().isCameraAvailable(); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java deleted file mode 100644 index 64c07b2..0000000 --- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.graphics.Canvas; -import android.hardware.Camera; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import com.android.messaging.R; -import com.android.messaging.ui.PersistentInstanceState; -import com.android.messaging.util.ThreadUtil; - -public class CameraMediaChooserView extends FrameLayout implements PersistentInstanceState { - private static final String KEY_CAMERA_INDEX = "camera_index"; - - // True if we have at least queued an update to the view tree to support software rendering - // fallback - private boolean mIsSoftwareFallbackActive; - - public CameraMediaChooserView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected Parcelable onSaveInstanceState() { - final Bundle bundle = new Bundle(); - bundle.putInt(KEY_CAMERA_INDEX, CameraManager.get().getCameraIndex()); - return bundle; - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof Bundle)) { - return; - } - - final Bundle bundle = (Bundle) state; - CameraManager.get().selectCameraByIndex(bundle.getInt(KEY_CAMERA_INDEX)); - } - - @Override - public Parcelable saveState() { - return onSaveInstanceState(); - } - - @Override - public void restoreState(final Parcelable restoredState) { - onRestoreInstanceState(restoredState); - } - - @Override - public void resetState() { - CameraManager.get().selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK); - } - - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - // If the canvas isn't hardware accelerated, we have to replace the HardwareCameraPreview - // with a SoftwareCameraPreview which supports software rendering - if (!canvas.isHardwareAccelerated() && !mIsSoftwareFallbackActive) { - mIsSoftwareFallbackActive = true; - // Post modifying the tree since we can't modify the view tree during a draw pass - ThreadUtil.getMainThreadHandler().post(new Runnable() { - @Override - public void run() { - final HardwareCameraPreview cameraPreview = - (HardwareCameraPreview) findViewById(R.id.camera_preview); - if (cameraPreview == null) { - return; - } - final ViewGroup parent = ((ViewGroup) cameraPreview.getParent()); - final int index = parent.indexOfChild(cameraPreview); - final SoftwareCameraPreview softwareCameraPreview = - new SoftwareCameraPreview(getContext()); - // Be sure to remove the hardware view before adding the software view to - // prevent having 2 camera previews active at the same time - parent.removeView(cameraPreview); - parent.addView(softwareCameraPreview, index); - } - }); - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/CameraPreview.java b/src/com/android/messaging/ui/mediapicker/CameraPreview.java deleted file mode 100644 index ecac978..0000000 --- a/src/com/android/messaging/ui/mediapicker/CameraPreview.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.content.res.Configuration; -import android.hardware.Camera; -import android.view.View; -import android.view.View.MeasureSpec; -import com.android.messaging.util.Assert; - -import java.io.IOException; - -/** - * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance - * because those classes must inherit from separate Views, so those classes delegate calls to this - * helper class. Specifics for each implementation are in CameraPreviewHost - */ -public class CameraPreview { - public interface CameraPreviewHost { - View getView(); - boolean isValid(); - void startPreview(final Camera camera) throws IOException; - void onCameraPermissionGranted(); - - } - - private int mCameraWidth = -1; - private int mCameraHeight = -1; - - private final CameraPreviewHost mHost; - - public CameraPreview(final CameraPreviewHost host) { - Assert.notNull(host); - Assert.notNull(host.getView()); - mHost = host; - } - - public void setSize(final Camera.Size size, final int orientation) { - switch (orientation) { - case 0: - case 180: - mCameraWidth = size.width; - mCameraHeight = size.height; - break; - case 90: - case 270: - default: - mCameraWidth = size.height; - mCameraHeight = size.width; - } - mHost.getView().requestLayout(); - } - - public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) { - if (mCameraHeight >= 0) { - final int width = View.MeasureSpec.getSize(widthMeasureSpec); - return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - } else { - return widthMeasureSpec; - } - } - - public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) { - if (mCameraHeight >= 0) { - final int orientation = getContext().getResources().getConfiguration().orientation; - final int width = View.MeasureSpec.getSize(widthMeasureSpec); - final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight; - int height; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - height = (int) (width * aspectRatio); - } else { - height = (int) (width / aspectRatio); - } - return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - } else { - return heightMeasureSpec; - } - } - - public void onVisibilityChanged(final int visibility) { - if (CameraManager.hasCameraPermission()) { - if (visibility == View.VISIBLE) { - CameraManager.get().openCamera(); - } else { - CameraManager.get().closeCamera(); - } - } - } - - public Context getContext() { - return mHost.getView().getContext(); - } - - public void setOnTouchListener(final View.OnTouchListener listener) { - mHost.getView().setOnTouchListener(listener); - } - - public int getHeight() { - return mHost.getView().getHeight(); - } - - public void onAttachedToWindow() { - if (CameraManager.hasCameraPermission()) { - CameraManager.get().openCamera(); - } - } - - public void onDetachedFromWindow() { - CameraManager.get().closeCamera(); - } - - public void onRestoreInstanceState() { - if (CameraManager.hasCameraPermission()) { - CameraManager.get().openCamera(); - } - } - - public void onCameraPermissionGranted() { - CameraManager.get().openCamera(); - } - - /** - * @return True if the view is valid and prepared for the camera to start showing the preview - */ - public boolean isValid() { - return mHost.isValid(); - } - - /** - * Starts the camera preview on the current surface. Abstracts out the differences in API - * from the CameraManager - * @throws IOException Which is caught by the CameraManager to display an error - */ - public void startPreview(final Camera camera) throws IOException { - mHost.startPreview(camera); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java deleted file mode 100644 index 2c36752..0000000 --- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.ImageUtils; -import com.android.messaging.util.SafeAsyncTask; - -/** - * Wraps around the functionalities to allow the user to pick images from the document - * picker. Instances of this class must be tied to a Fragment which is able to delegate activity - * result callbacks. - */ -public class DocumentImagePicker { - - /** - * An interface for a listener that listens for when a document has been picked. - */ - public interface SelectionListener { - /** - * Called when an document is selected from picker. At this point, the file hasn't been - * actually loaded and staged in the temp directory, so we are passing in a pending - * MessagePartData, which the consumer should use to display a placeholder image. - * @param pendingItem a temporary attachment data for showing the placeholder state. - */ - void onDocumentSelected(PendingAttachmentData pendingItem); - } - - // The owning fragment. - private final Fragment mFragment; - - // The listener on the picker events. - private final SelectionListener mListener; - - private static final String EXTRA_PHOTO_URL = "photo_url"; - - /** - * Creates a new instance of DocumentImagePicker. - * @param activity The activity that owns the picker, or the activity that hosts the owning - * fragment. - */ - public DocumentImagePicker(final Fragment fragment, - final SelectionListener listener) { - mFragment = fragment; - mListener = listener; - } - - /** - * Intent out to open an image/video from document picker. - */ - public void launchPicker() { - UIIntents.get().launchDocumentImagePicker(mFragment); - } - - /** - * Must be called from the fragment/activity's onActivityResult(). - */ - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - if (requestCode == UIIntents.REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER && - resultCode == Activity.RESULT_OK) { - // Sometimes called after media item has been picked from the document picker. - String url = data.getStringExtra(EXTRA_PHOTO_URL); - if (url == null) { - // we're using the builtin photo picker which supplies the return - // url as it's "data" - url = data.getDataString(); - if (url == null) { - final Bundle extras = data.getExtras(); - if (extras != null) { - final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM); - if (uri != null) { - url = uri.toString(); - } - } - } - } - - // Guard against null uri cases for when the activity returns a null/invalid intent. - if (url != null) { - final Uri uri = Uri.parse(url); - prepareDocumentForAttachment(uri); - } - } - } - - private void prepareDocumentForAttachment(final Uri documentUri) { - // Notify our listener with a PendingAttachmentData containing the metadata. - // Asynchronously get the content type for the picked image since - // ImageUtils.getContentType() potentially involves I/O and can be expensive. - new SafeAsyncTask() { - @Override - protected String doInBackgroundTimed(final Void... params) { - return ImageUtils.getContentType( - Factory.get().getApplicationContext().getContentResolver(), documentUri); - } - - @Override - protected void onPostExecute(final String contentType) { - // Ask the listener to create a temporary placeholder item to show the progress. - final PendingAttachmentData pendingItem = - PendingAttachmentData.createPendingAttachmentData(contentType, - documentUri); - mListener.onDocumentSelected(pendingItem); - } - }.executeOnThreadPool(); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java deleted file mode 100644 index fda3b19..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import com.android.messaging.R; -import com.android.messaging.ui.mediapicker.GalleryGridItemView.HostInterface; -import com.android.messaging.util.Assert; - -/** - * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView. - */ -public class GalleryGridAdapter extends CursorAdapter { - private GalleryGridItemView.HostInterface mGgivHostInterface; - - public GalleryGridAdapter(final Context context, final Cursor cursor) { - super(context, cursor, 0); - } - - public void setHostInterface(final HostInterface ggivHostInterface) { - mGgivHostInterface = ggivHostInterface; - } - - /** - * {@inheritDoc} - */ - @Override - public void bindView(final View view, final Context context, final Cursor cursor) { - Assert.isTrue(view instanceof GalleryGridItemView); - final GalleryGridItemView galleryImageView = (GalleryGridItemView) view; - galleryImageView.bind(cursor, mGgivHostInterface); - } - - /** - * {@inheritDoc} - */ - @Override - public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - return layoutInflater.inflate(R.layout.gallery_grid_item_view, parent, false); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java deleted file mode 100644 index 3d71fe6..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.TouchDelegate; -import android.view.View; -import android.widget.CheckBox; -import android.widget.FrameLayout; -import android.widget.ImageView.ScaleType; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.ConversationDrawables; -import com.google.common.annotations.VisibleForTesting; - -import java.util.concurrent.TimeUnit; - -/** - * Shows an item in the gallery picker grid view. Hosts an FileImageView with a checkbox. - */ -public class GalleryGridItemView extends FrameLayout { - /** - * Implemented by the owner of this GalleryGridItemView instance to communicate on media - * picking and selection events. - */ - public interface HostInterface { - void onItemClicked(View view, GalleryGridItemData data, boolean longClick); - boolean isItemSelected(GalleryGridItemData data); - boolean isMultiSelectEnabled(); - } - - @VisibleForTesting - GalleryGridItemData mData; - private AsyncImageView mImageView; - private CheckBox mCheckBox; - private HostInterface mHostInterface; - private final OnClickListener mOnClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/); - } - }; - - public GalleryGridItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = DataModel.get().createGalleryGridItemData(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mImageView = (AsyncImageView) findViewById(R.id.image); - mCheckBox = (CheckBox) findViewById(R.id.checkbox); - mCheckBox.setOnClickListener(mOnClickListener); - setOnClickListener(mOnClickListener); - final OnLongClickListener longClickListener = new OnLongClickListener() { - @Override - public boolean onLongClick(final View v) { - mHostInterface.onItemClicked(v, mData, true /* longClick */); - return true; - } - }; - setOnLongClickListener(longClickListener); - mCheckBox.setOnLongClickListener(longClickListener); - addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - // Enlarge the clickable region for the checkbox to fill the entire view. - final Rect region = new Rect(0, 0, getWidth(), getHeight()); - setTouchDelegate(new TouchDelegate(region, mCheckBox) { - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - setPressed(true); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - setPressed(false); - break; - } - return super.onTouchEvent(event); - } - }); - } - }); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - // The grid view auto-fit the columns, so we want to let the height match the width - // to make the image square. - super.onMeasure(widthMeasureSpec, widthMeasureSpec); - } - - public void bind(final Cursor cursor, final HostInterface hostInterface) { - final int desiredSize = getResources() - .getDimensionPixelSize(R.dimen.gallery_image_cell_size); - mData.bind(cursor, desiredSize, desiredSize); - mHostInterface = hostInterface; - updateViewState(); - } - - private void updateViewState() { - updateImageView(); - if (mHostInterface.isMultiSelectEnabled() && !mData.isDocumentPickerItem()) { - mCheckBox.setVisibility(VISIBLE); - mCheckBox.setClickable(true); - mCheckBox.setChecked(mHostInterface.isItemSelected(mData)); - } else { - mCheckBox.setVisibility(GONE); - mCheckBox.setClickable(false); - } - } - - private void updateImageView() { - if (mData.isDocumentPickerItem()) { - mImageView.setScaleType(ScaleType.CENTER); - setBackgroundColor(ConversationDrawables.get().getConversationThemeColor()); - mImageView.setImageResourceId(null); - mImageView.setImageResource(R.drawable.ic_photo_library_light); - mImageView.setContentDescription(getResources().getString( - R.string.pick_image_from_document_library_content_description)); - } else { - mImageView.setScaleType(ScaleType.CENTER_CROP); - setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background)); - mImageView.setImageResourceId(mData.getImageRequestDescriptor()); - final long dateSeconds = mData.getDateSeconds(); - final boolean isValidDate = (dateSeconds > 0); - final int templateId = isValidDate ? - R.string.mediapicker_gallery_image_item_description : - R.string.mediapicker_gallery_image_item_description_no_date; - String contentDescription = String.format(getResources().getString(templateId), - dateSeconds * TimeUnit.SECONDS.toMillis(1)); - mImageView.setContentDescription(contentDescription); - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java deleted file mode 100644 index a5a7dad..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.util.ArrayMap; -import android.util.AttributeSet; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener; -import com.android.messaging.ui.PersistentInstanceState; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; - -import java.util.Iterator; -import java.util.Map; - -/** - * Shows a list of galley images from external storage in a GridView with multi-select - * capabilities, and with the option to intent out to a standalone image picker. - */ -public class GalleryGridView extends MediaPickerGridView implements - GalleryGridItemView.HostInterface, - PersistentInstanceState, - DraftMessageDataListener { - /** - * Implemented by the owner of this GalleryGridView instance to communicate on image - * picking and multi-image selection events. - */ - public interface GalleryGridViewListener { - void onDocumentPickerItemClicked(); - void onItemSelected(MessagePartData item); - void onItemUnselected(MessagePartData item); - void onConfirmSelection(); - void onUpdate(); - } - - private GalleryGridViewListener mListener; - - // TODO: Consider putting this into the data model object if we add more states. - private final ArrayMap mSelectedImages; - private boolean mIsMultiSelectMode = false; - private ImmutableBindingRef mDraftMessageDataModel; - - public GalleryGridView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mSelectedImages = new ArrayMap(); - } - - public void setHostInterface(final GalleryGridViewListener hostInterface) { - mListener = hostInterface; - } - - public void setDraftMessageDataModel(final BindingBase dataModel) { - mDraftMessageDataModel = BindingBase.createBindingReference(dataModel); - mDraftMessageDataModel.getData().addListener(this); - } - - @Override - public void onItemClicked(final View view, final GalleryGridItemData data, - final boolean longClick) { - if (data.isDocumentPickerItem()) { - mListener.onDocumentPickerItemClicked(); - } else if (ContentType.isMediaType(data.getContentType())) { - if (longClick) { - // Turn on multi-select mode when an item is long-pressed. - setMultiSelectEnabled(true); - } - - final Rect startRect = new Rect(); - view.getGlobalVisibleRect(startRect); - if (isMultiSelectEnabled()) { - toggleItemSelection(startRect, data); - } else { - mListener.onItemSelected(data.constructMessagePartData(startRect)); - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, - "Selected item has invalid contentType " + data.getContentType()); - } - } - - @Override - public boolean isItemSelected(final GalleryGridItemData data) { - return mSelectedImages.containsKey(data.getImageUri()); - } - - int getSelectionCount() { - return mSelectedImages.size(); - } - - @Override - public boolean isMultiSelectEnabled() { - return mIsMultiSelectMode; - } - - private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) { - Assert.isTrue(isMultiSelectEnabled()); - if (isItemSelected(data)) { - final MessagePartData item = mSelectedImages.remove(data.getImageUri()); - mListener.onItemUnselected(item); - if (mSelectedImages.size() == 0) { - // No image is selected any more, turn off multi-select mode. - setMultiSelectEnabled(false); - } - } else { - final MessagePartData item = data.constructMessagePartData(startRect); - mSelectedImages.put(data.getImageUri(), item); - mListener.onItemSelected(item); - } - invalidateViews(); - } - - private void toggleMultiSelect() { - mIsMultiSelectMode = !mIsMultiSelectMode; - invalidateViews(); - } - - private void setMultiSelectEnabled(final boolean enabled) { - if (mIsMultiSelectMode != enabled) { - toggleMultiSelect(); - } - } - - private boolean canToggleMultiSelect() { - // We allow the user to toggle multi-select mode only when nothing has selected. If - // something has been selected, we show a confirm button instead. - return mSelectedImages.size() == 0; - } - - public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { - inflater.inflate(R.menu.gallery_picker_menu, menu); - final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect); - final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect); - final boolean canToggleMultiSelect = canToggleMultiSelect(); - toggleMultiSelect.setVisible(canToggleMultiSelect); - confirmMultiSelect.setVisible(!canToggleMultiSelect); - } - - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_multiselect: - Assert.isTrue(canToggleMultiSelect()); - toggleMultiSelect(); - return true; - - case R.id.action_confirm_multiselect: - Assert.isTrue(!canToggleMultiSelect()); - mListener.onConfirmSelection(); - return true; - } - return false; - } - - - @Override - public void onDraftChanged(final DraftMessageData data, final int changeFlags) { - mDraftMessageDataModel.ensureBound(data); - // Whenever attachment changed, refresh selection state to remove those that are not - // selected. - if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) == - DraftMessageData.ATTACHMENTS_CHANGED) { - refreshImageSelectionStateOnAttachmentChange(); - } - } - - @Override - public void onDraftAttachmentLimitReached(final DraftMessageData data) { - mDraftMessageDataModel.ensureBound(data); - // Whenever draft attachment limit is reach, refresh selection state to remove those - // not actually added to draft. - refreshImageSelectionStateOnAttachmentChange(); - } - - @Override - public void onDraftAttachmentLoadFailed() { - // Nothing to do since the failed attachment gets removed automatically. - } - - private void refreshImageSelectionStateOnAttachmentChange() { - boolean changed = false; - final Iterator> iterator = - mSelectedImages.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) { - iterator.remove(); - changed = true; - } - } - - if (changed) { - mListener.onUpdate(); - invalidateViews(); - } - } - - @Override // PersistentInstanceState - public Parcelable saveState() { - return onSaveInstanceState(); - } - - @Override // PersistentInstanceState - public void restoreState(final Parcelable restoredState) { - onRestoreInstanceState(restoredState); - invalidateViews(); - } - - @Override - public Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final SavedState savedState = new SavedState(superState); - savedState.isMultiSelectMode = mIsMultiSelectMode; - savedState.selectedImages = mSelectedImages.values() - .toArray(new MessagePartData[mSelectedImages.size()]); - return savedState; - } - - @Override - public void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - mIsMultiSelectMode = savedState.isMultiSelectMode; - mSelectedImages.clear(); - for (int i = 0; i < savedState.selectedImages.length; i++) { - final MessagePartData selectedImage = savedState.selectedImages[i]; - mSelectedImages.put(selectedImage.getContentUri(), selectedImage); - } - } - - @Override // PersistentInstanceState - public void resetState() { - mSelectedImages.clear(); - mIsMultiSelectMode = false; - invalidateViews(); - } - - public static class SavedState extends BaseSavedState { - boolean isMultiSelectMode; - MessagePartData[] selectedImages; - - SavedState(final Parcelable superState) { - super(superState); - } - - private SavedState(final Parcel in) { - super(in); - isMultiSelectMode = in.readInt() == 1 ? true : false; - - // Read parts - final int partCount = in.readInt(); - selectedImages = new MessagePartData[partCount]; - for (int i = 0; i < partCount; i++) { - selectedImages[i] = ((MessagePartData) in.readParcelable( - MessagePartData.class.getClassLoader())); - } - } - - @Override - public void writeToParcel(final Parcel out, final int flags) { - super.writeToParcel(out, flags); - out.writeInt(isMultiSelectMode ? 1 : 0); - - // Write parts - out.writeInt(selectedImages.length); - for (final MessagePartData image : selectedImages) { - out.writeParcelable(image, flags); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(final Parcel in) { - return new SavedState(in); - } - @Override - public SavedState[] newArray(final int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java deleted file mode 100644 index 9422386..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.Manifest; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.support.v7.app.ActionBar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener; -import com.android.messaging.util.Assert; -import com.android.messaging.util.OsUtil; - -/** - * Chooser which allows the user to select one or more existing images or videos - */ -class GalleryMediaChooser extends MediaChooser implements - GalleryGridView.GalleryGridViewListener, MediaPickerDataListener { - private final GalleryGridAdapter mAdapter; - private GalleryGridView mGalleryGridView; - private View mMissingPermissionView; - - GalleryMediaChooser(final MediaPicker mediaPicker) { - super(mediaPicker); - mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null); - } - - @Override - public int getSupportedMediaTypes() { - return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO; - } - - @Override - public View destroyView() { - mGalleryGridView.setAdapter(null); - mAdapter.setHostInterface(null); - // The loader is started only if startMediaPickerDataLoader() is called - if (OsUtil.hasStoragePermission()) { - mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_IMAGE_LOADER); - } - return super.destroyView(); - } - - @Override - public int getIconResource() { - return R.drawable.ic_image_light; - } - - @Override - public int getIconDescriptionResource() { - return R.string.mediapicker_galleryChooserDescription; - } - - @Override - public boolean canSwipeDown() { - return mGalleryGridView.canSwipeDown(); - } - - @Override - public void onItemSelected(final MessagePartData item) { - mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.isMultiSelectEnabled()); - } - - @Override - public void onItemUnselected(final MessagePartData item) { - mMediaPicker.dispatchItemUnselected(item); - } - - @Override - public void onConfirmSelection() { - // The user may only confirm if multiselect is enabled. - Assert.isTrue(mGalleryGridView.isMultiSelectEnabled()); - mMediaPicker.dispatchConfirmItemSelection(); - } - - @Override - public void onUpdate() { - mMediaPicker.invalidateOptionsMenu(); - } - - @Override - public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { - if (mView != null) { - mGalleryGridView.onCreateOptionsMenu(inflater, menu); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false; - } - - @Override - protected View createView(final ViewGroup container) { - final LayoutInflater inflater = getLayoutInflater(); - final View view = inflater.inflate( - R.layout.mediapicker_image_chooser, - container /* root */, - false /* attachToRoot */); - - mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view); - mAdapter.setHostInterface(mGalleryGridView); - mGalleryGridView.setAdapter(mAdapter); - mGalleryGridView.setHostInterface(this); - mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel()); - if (OsUtil.hasStoragePermission()) { - startMediaPickerDataLoader(); - } - - mMissingPermissionView = view.findViewById(R.id.missing_permission_view); - updateForPermissionState(OsUtil.hasStoragePermission()); - return view; - } - - @Override - int getActionBarTitleResId() { - return R.string.mediapicker_gallery_title; - } - - @Override - public void onDocumentPickerItemClicked() { - mMediaPicker.launchDocumentPicker(); - } - - @Override - void updateActionBar(final ActionBar actionBar) { - super.updateActionBar(actionBar); - if (mGalleryGridView == null) { - return; - } - final int selectionCount = mGalleryGridView.getSelectionCount(); - if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) { - actionBar.setTitle(getContext().getResources().getString( - R.string.mediapicker_gallery_title_selection, - selectionCount)); - } - } - - @Override - public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data, - final int loaderId) { - mBindingRef.ensureBound(mediaPickerData); - Assert.equals(MediaPickerData.GALLERY_IMAGE_LOADER, loaderId); - Cursor rawCursor = null; - if (data instanceof Cursor) { - rawCursor = (Cursor) data; - } - // Before delivering the cursor, wrap around the local gallery cursor - // with an extra item for document picker integration in the front. - final MatrixCursor specialItemsCursor = - new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS); - specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM }); - final MergeCursor cursor = - new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor }); - mAdapter.swapCursor(cursor); - } - - @Override - public void onResume() { - if (OsUtil.hasStoragePermission()) { - // Work around a bug in MediaStore where cursors querying the Files provider don't get - // updated for changes to Images.Media or Video.Media. - startMediaPickerDataLoader(); - } - } - - @Override - protected void setSelected(final boolean selected) { - super.setSelected(selected); - if (selected && !OsUtil.hasStoragePermission()) { - mMediaPicker.requestPermissions( - new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, - MediaPicker.GALLERY_PERMISSION_REQUEST_CODE); - } - } - - private void startMediaPickerDataLoader() { - mBindingRef.getData().startLoader(MediaPickerData.GALLERY_IMAGE_LOADER, mBindingRef, null, - this); - } - - @Override - protected void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { - if (requestCode == MediaPicker.GALLERY_PERMISSION_REQUEST_CODE) { - final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; - if (permissionGranted) { - startMediaPickerDataLoader(); - } - updateForPermissionState(permissionGranted); - } - } - - private void updateForPermissionState(final boolean granted) { - // onRequestPermissionsResult can sometimes get called before createView(). - if (mGalleryGridView == null) { - return; - } - - mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE); - mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java deleted file mode 100644 index 45d9579..0000000 --- a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.TextureView; -import android.view.View; - -import java.io.IOException; - -/** - * A hardware accelerated preview texture for the camera. This is the preferred CameraPreview - * because it animates smoother. When hardware acceleration isn't available, SoftwareCameraPreview - * is used. - * - * There is a significant amount of duplication between HardwareCameraPreview and - * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The - * implementations of the shared methods are delegated to CameraPreview - */ -public class HardwareCameraPreview extends TextureView implements CameraPreview.CameraPreviewHost { - private CameraPreview mPreview; - - public HardwareCameraPreview(final Context context, final AttributeSet attrs) { - super(context, attrs); - mPreview = new CameraPreview(this); - setSurfaceTextureListener(new SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int i, final int i2) { - CameraManager.get().setSurface(mPreview); - } - - @Override - public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int i, final int i2) { - CameraManager.get().setSurface(mPreview); - } - - @Override - public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) { - CameraManager.get().setSurface(null); - return true; - } - - @Override - public void onSurfaceTextureUpdated(final SurfaceTexture surfaceTexture) { - CameraManager.get().setSurface(mPreview); - } - }); - } - - @Override - protected void onVisibilityChanged(final View changedView, final int visibility) { - super.onVisibilityChanged(changedView, visibility); - mPreview.onVisibilityChanged(visibility); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mPreview.onDetachedFromWindow(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mPreview.onAttachedToWindow(); - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - super.onRestoreInstanceState(state); - mPreview.onRestoreInstanceState(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec); - heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public View getView() { - return this; - } - - @Override - public boolean isValid() { - return getSurfaceTexture() != null; - } - - @Override - public void startPreview(final Camera camera) throws IOException { - camera.setPreviewTexture(getSurfaceTexture()); - } - - @Override - public void onCameraPermissionGranted() { - mPreview.onCameraPermissionGranted(); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java deleted file mode 100644 index 637eb84..0000000 --- a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.net.Uri; - -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.exif.ExifInterface; -import com.android.messaging.util.exif.ExifTag; - -import java.io.IOException; -import java.io.OutputStream; - -public class ImagePersistTask extends SafeAsyncTask { - private static final String JPEG_EXTENSION = "jpg"; - private static final String TAG = LogUtil.BUGLE_TAG; - - private int mWidth; - private int mHeight; - private final float mHeightPercent; - private final byte[] mBytes; - private final Context mContext; - private final CameraManager.MediaCallback mCallback; - private Uri mOutputUri; - private Exception mException; - - public ImagePersistTask( - final int width, - final int height, - final float heightPercent, - final byte[] bytes, - final Context context, - final CameraManager.MediaCallback callback) { - Assert.isTrue(heightPercent >= 0 && heightPercent <= 1); - Assert.notNull(bytes); - Assert.notNull(context); - Assert.notNull(callback); - mWidth = width; - mHeight = height; - mHeightPercent = heightPercent; - mBytes = bytes; - mContext = context; - mCallback = callback; - // TODO: We probably want to store directly in MMS storage to prevent this - // intermediate step - mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION); - } - - @Override - protected Void doInBackgroundTimed(final Void... params) { - OutputStream outputStream = null; - Bitmap bitmap = null; - Bitmap clippedBitmap = null; - try { - outputStream = - mContext.getContentResolver().openOutputStream(mOutputUri); - if (mHeightPercent != 1.0f) { - int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED; - final ExifInterface exifInterface = new ExifInterface(); - try { - exifInterface.readExif(mBytes); - final Integer orientationValue = - exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION); - if (orientationValue != null) { - orientation = orientationValue.intValue(); - } - // The thumbnail is of the full image, but we're cropping it, so just clear - // the thumbnail - exifInterface.setCompressedThumbnail((byte[]) null); - } catch (IOException e) { - // Couldn't get exif tags, not the end of the world - } - bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length); - final int clippedWidth; - final int clippedHeight; - if (ExifInterface.getOrientationParams(orientation).invertDimensions) { - Assert.equals(mWidth, bitmap.getHeight()); - Assert.equals(mHeight, bitmap.getWidth()); - clippedWidth = (int) (mHeight * mHeightPercent); - clippedHeight = mWidth; - } else { - Assert.equals(mWidth, bitmap.getWidth()); - Assert.equals(mHeight, bitmap.getHeight()); - clippedWidth = mWidth; - clippedHeight = (int) (mHeight * mHeightPercent); - } - final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2; - final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2; - mWidth = clippedWidth; - mHeight = clippedHeight; - clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight, - Bitmap.Config.ARGB_8888); - clippedBitmap.setDensity(bitmap.getDensity()); - final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap); - final Matrix matrix = new Matrix(); - matrix.postTranslate(-offsetLeft, -offsetTop); - clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */); - clippedBitmapCanvas.save(); - // EXIF data can take a big chunk of the file size and is often cleared by the - // carrier, only store orientation since that's critical - ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION); - exifInterface.clearExif(); - exifInterface.setTag(orientationTag); - exifInterface.writeExif(clippedBitmap, outputStream); - } else { - outputStream.write(mBytes); - } - } catch (final IOException e) { - mOutputUri = null; - mException = e; - LogUtil.e(TAG, "Unable to persist image to temp storage " + e); - } finally { - if (bitmap != null) { - bitmap.recycle(); - } - - if (clippedBitmap != null) { - clippedBitmap.recycle(); - } - - if (outputStream != null) { - try { - outputStream.flush(); - } catch (final IOException e) { - mOutputUri = null; - mException = e; - LogUtil.e(TAG, "error trying to flush and close the outputStream" + e); - } finally { - try { - outputStream.close(); - } catch (final IOException e) { - // Do nothing. - } - } - } - } - return null; - } - - @Override - protected void onPostExecute(final Void aVoid) { - if (mOutputUri != null) { - mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight); - } else { - Assert.notNull(mException); - mCallback.onMediaFailed(mException); - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java b/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java deleted file mode 100644 index 06730a3..0000000 --- a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.ParcelFileDescriptor; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UiUtils; - -import java.io.IOException; - -/** - * Wraps around the functionalities of MediaRecorder, performs routine setup for audio recording - * and updates the audio level to be displayed in UI. - * - * During the start and end of a recording session, we kick off a thread that polls for audio - * levels, and updates the thread-safe AudioLevelSource instance. Consumers may bind to the - * sound level by either polling from the level source, or register for a level change callback - * on the level source object. In Bugle, the UI element (SoundLevels) polls for the sound level - * on the UI thread by using animation ticks and invalidating itself. - * - * Aside from tracking sound levels, this also encapsulates the functionality to save the file - * to the scratch space. The saved file is returned by calling stopRecording(). - */ -public class LevelTrackingMediaRecorder { - // We refresh sound level every 100ms during a recording session. - private static final int REFRESH_INTERVAL_MILLIS = 100; - - // The native amplitude returned from MediaRecorder ranges from 0~32768 (unfortunately, this - // is not a constant that's defined anywhere, but the framework's Recorder app is using the - // same hard-coded number). Therefore, a constant is needed in order to make it 0~100. - private static final int MAX_AMPLITUDE_FACTOR = 32768 / 100; - - // We want to limit the max audio file size by the max message size allowed by MmsConfig, - // plus multiplied by this fudge ratio to guarantee that we don't go over limit. - private static final float MAX_SIZE_RATIO = 0.8f; - - // Default recorder settings for Bugle. - // TODO: Do we want these to be tweakable? - private static final int MEDIA_RECORDER_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; - private static final int MEDIA_RECORDER_OUTPUT_FORMAT = MediaRecorder.OutputFormat.THREE_GPP; - private static final int MEDIA_RECORDER_AUDIO_ENCODER = MediaRecorder.AudioEncoder.AMR_NB; - - private final AudioLevelSource mLevelSource; - private Thread mRefreshLevelThread; - private MediaRecorder mRecorder; - private Uri mOutputUri; - private ParcelFileDescriptor mOutputFD; - - public LevelTrackingMediaRecorder() { - mLevelSource = new AudioLevelSource(); - } - - public AudioLevelSource getLevelSource() { - return mLevelSource; - } - - /** - * @return if we are currently in a recording session. - */ - public boolean isRecording() { - return mRecorder != null; - } - - /** - * Start a new recording session. - * @return true if a session is successfully started; false if something went wrong or if - * we are already recording. - */ - public boolean startRecording(final MediaRecorder.OnErrorListener errorListener, - final MediaRecorder.OnInfoListener infoListener, int maxSize) { - synchronized (LevelTrackingMediaRecorder.class) { - if (mRecorder == null) { - mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri( - ContentType.THREE_GPP_EXTENSION); - mRecorder = new MediaRecorder(); - try { - // The scratch space file is a Uri, however MediaRecorder - // API only accepts absolute FD's. Therefore, get the - // FileDescriptor from the content resolver to ensure the - // directory is created and get the file path to output the - // audio to. - maxSize *= MAX_SIZE_RATIO; - mOutputFD = Factory.get().getApplicationContext() - .getContentResolver().openFileDescriptor(mOutputUri, "w"); - mRecorder.setAudioSource(MEDIA_RECORDER_AUDIO_SOURCE); - mRecorder.setOutputFormat(MEDIA_RECORDER_OUTPUT_FORMAT); - mRecorder.setAudioEncoder(MEDIA_RECORDER_AUDIO_ENCODER); - mRecorder.setOutputFile(mOutputFD.getFileDescriptor()); - mRecorder.setMaxFileSize(maxSize); - mRecorder.setOnErrorListener(errorListener); - mRecorder.setOnInfoListener(infoListener); - mRecorder.prepare(); - mRecorder.start(); - startTrackingSoundLevel(); - return true; - } catch (final Exception e) { - // There may be a device failure or I/O failure, record the error but - // don't fail. - LogUtil.e(LogUtil.BUGLE_TAG, "Something went wrong when starting " + - "media recorder. " + e); - UiUtils.showToastAtBottom(R.string.audio_recording_start_failed); - stopRecording(); - } - } else { - Assert.fail("Trying to start a new recording session while already recording!"); - } - return false; - } - } - - /** - * Stop the current recording session. - * @return the Uri of the output file, or null if not currently recording. - */ - public Uri stopRecording() { - synchronized (LevelTrackingMediaRecorder.class) { - if (mRecorder != null) { - try { - mRecorder.stop(); - } catch (final RuntimeException ex) { - // This may happen when the recording is too short, so just drop the recording - // in this case. - LogUtil.w(LogUtil.BUGLE_TAG, "Something went wrong when stopping " + - "media recorder. " + ex); - if (mOutputUri != null) { - final Uri outputUri = mOutputUri; - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - Factory.get().getApplicationContext().getContentResolver().delete( - outputUri, null, null); - } - }); - mOutputUri = null; - } - } finally { - mRecorder.release(); - mRecorder = null; - } - } else { - Assert.fail("Not currently recording!"); - return null; - } - } - - if (mOutputFD != null) { - try { - mOutputFD.close(); - } catch (final IOException e) { - // Nothing to do - } - mOutputFD = null; - } - - stopTrackingSoundLevel(); - return mOutputUri; - } - - private int getAmplitude() { - synchronized (LevelTrackingMediaRecorder.class) { - if (mRecorder != null) { - final int maxAmplitude = mRecorder.getMaxAmplitude() / MAX_AMPLITUDE_FACTOR; - return Math.min(maxAmplitude, 100); - } else { - return 0; - } - } - } - - private void startTrackingSoundLevel() { - stopTrackingSoundLevel(); - mRefreshLevelThread = new Thread() { - @Override - public void run() { - try { - while (true) { - synchronized (LevelTrackingMediaRecorder.class) { - if (mRecorder != null) { - mLevelSource.setSpeechLevel(getAmplitude()); - } else { - // The recording session is over, finish the thread. - return; - } - } - Thread.sleep(REFRESH_INTERVAL_MILLIS); - } - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - }; - mRefreshLevelThread.start(); - } - - private void stopTrackingSoundLevel() { - if (mRefreshLevelThread != null && mRefreshLevelThread.isAlive()) { - mRefreshLevelThread.interrupt(); - mRefreshLevelThread = null; - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/MediaChooser.java b/src/com/android/messaging/ui/mediapicker/MediaChooser.java deleted file mode 100644 index 9ac0d1b..0000000 --- a/src/com/android/messaging/ui/mediapicker/MediaChooser.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.app.FragmentManager; -import android.content.Context; -import android.support.v7.app.ActionBar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; -import com.android.messaging.ui.BasePagerViewHolder; -import com.android.messaging.util.Assert; -import com.android.messaging.util.OsUtil; - -abstract class MediaChooser extends BasePagerViewHolder - implements DraftMessageSubscriptionDataProvider { - /** The media picker that the chooser is hosted in */ - protected final MediaPicker mMediaPicker; - - /** Referencing the main media picker binding to perform data loading */ - protected final ImmutableBindingRef mBindingRef; - - /** True if this is the selected chooser */ - protected boolean mSelected; - - /** True if this chooser is open */ - protected boolean mOpen; - - /** The button to show in the tab strip */ - private ImageButton mTabButton; - - /** Used by subclasses to indicate that no loader is required from the data model in order for - * this chooser to function. - */ - public static final int NO_LOADER_REQUIRED = -1; - - /** - * Initializes a new instance of the Chooser class - * @param mediaPicker The media picker that the chooser is hosted in - */ - MediaChooser(final MediaPicker mediaPicker) { - Assert.notNull(mediaPicker); - mMediaPicker = mediaPicker; - mBindingRef = mediaPicker.getMediaPickerDataBinding(); - mSelected = false; - } - - protected void setSelected(final boolean selected) { - mSelected = selected; - if (selected) { - // If we're selected, it must be open - mOpen = true; - } - if (mTabButton != null) { - mTabButton.setSelected(selected); - mTabButton.setAlpha(selected ? 1 : 0.5f); - } - } - - ImageButton getTabButton() { - return mTabButton; - } - - void onCreateTabButton(final LayoutInflater inflater, final ViewGroup parent) { - mTabButton = (ImageButton) inflater.inflate( - R.layout.mediapicker_tab_button, - parent, - false /* addToParent */); - mTabButton.setImageResource(getIconResource()); - mTabButton.setContentDescription( - inflater.getContext().getResources().getString(getIconDescriptionResource())); - setSelected(mSelected); - mTabButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - mMediaPicker.selectChooser(MediaChooser.this); - } - }); - } - - protected Context getContext() { - return mMediaPicker.getActivity(); - } - - protected FragmentManager getFragmentManager() { - return OsUtil.isAtLeastJB_MR1() ? mMediaPicker.getChildFragmentManager() : - mMediaPicker.getFragmentManager(); - } - protected LayoutInflater getLayoutInflater() { - return LayoutInflater.from(getContext()); - } - - /** Allows the chooser to handle full screen change */ - void onFullScreenChanged(final boolean fullScreen) {} - - /** Allows the chooser to handle the chooser being opened or closed */ - void onOpenedChanged(final boolean open) { - mOpen = open; - } - - /** @return The bit field of media types that this chooser can pick */ - public abstract int getSupportedMediaTypes(); - - /** @return The resource id of the icon for the chooser */ - abstract int getIconResource(); - - /** @return The resource id of the string to use for the accessibility text of the icon */ - abstract int getIconDescriptionResource(); - - /** - * Sets up the action bar to show the current state of the full-screen chooser - * @param actionBar The action bar to populate - */ - void updateActionBar(final ActionBar actionBar) { - final int actionBarTitleResId = getActionBarTitleResId(); - if (actionBarTitleResId == 0) { - actionBar.hide(); - } else { - actionBar.setCustomView(null); - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.show(); - // Use X instead of <- in the action bar - actionBar.setHomeAsUpIndicator(R.drawable.ic_remove_small_light); - actionBar.setTitle(actionBarTitleResId); - } - } - - /** - * Returns the resource Id used for the action bar title. - */ - abstract int getActionBarTitleResId(); - - /** - * Throws an exception if the media chooser object doesn't require data support. - */ - public void onDataUpdated(final Object data, final int loaderId) { - throw new IllegalStateException(); - } - - /** - * Called by the MediaPicker to determine whether this panel can be swiped down further. If - * not, then a swipe down gestured will be captured by the MediaPickerPanel to shrink the - * entire panel. - */ - public boolean canSwipeDown() { - return false; - } - - /** - * Typically the media picker is closed when the IME is opened, but this allows the chooser to - * specify that showing the IME is okay while the chooser is up - */ - public boolean canShowIme() { - return false; - } - - public boolean onBackPressed() { - return false; - } - - public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { - } - - public boolean onOptionsItemSelected(final MenuItem item) { - return false; - } - - public void setThemeColor(final int color) { - } - - /** - * Returns true if the chooser is owning any incoming touch events, so that the media picker - * panel won't process it and slide the panel. - */ - public boolean isHandlingTouch() { - return false; - } - - public void stopTouchHandling() { - } - - @Override - public int getConversationSelfSubId() { - return mMediaPicker.getConversationSelfSubId(); - } - - /** Optional activity life-cycle methods to be overridden by subclasses */ - public void onPause() { } - public void onResume() { } - protected void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { } -} diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java deleted file mode 100644 index f441d09..0000000 --- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java +++ /dev/null @@ -1,736 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; -import com.android.messaging.ui.BugleActionBarActivity; -import com.android.messaging.ui.FixedViewPagerAdapter; -import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener; -import com.android.messaging.util.AccessibilityUtil; -import com.android.messaging.util.Assert; -import com.android.messaging.util.UiUtils; -import com.google.common.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Fragment used to select or capture media to be added to the message - */ -public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider { - /** The listener interface for events from the media picker */ - public interface MediaPickerListener { - /** Called when the media picker is opened so the host can accommodate the UI */ - void onOpened(); - - /** - * Called when the media picker goes into or leaves full screen mode so the host can - * accommodate the fullscreen UI - */ - void onFullScreenChanged(boolean fullScreen); - - /** - * Called when the user selects one or more items - * @param items The list of items which were selected - */ - void onItemsSelected(Collection items, boolean dismissMediaPicker); - - /** - * Called when the user unselects one item. - */ - void onItemUnselected(MessagePartData item); - - /** - * Called when the media picker is closed. Always called immediately after onItemsSelected - */ - void onDismissed(); - - /** - * Called when media item selection is confirmed in a multi-select action. - */ - void onConfirmItemSelection(); - - /** - * Called when a pending attachment is added. - * @param pendingItem the pending attachment data being loaded. - */ - void onPendingItemAdded(PendingAttachmentData pendingItem); - - /** - * Called when a new media chooser is selected. - */ - void onChooserSelected(final int chooserIndex); - } - - /** The tag used when registering and finding this fragment */ - public static final String FRAGMENT_TAG = "mediapicker"; - - // Media type constants that the media picker supports - public static final int MEDIA_TYPE_DEFAULT = 0x0000; - public static final int MEDIA_TYPE_NONE = 0x0000; - public static final int MEDIA_TYPE_IMAGE = 0x0001; - public static final int MEDIA_TYPE_VIDEO = 0x0002; - public static final int MEDIA_TYPE_AUDIO = 0x0004; - public static final int MEDIA_TYPE_VCARD = 0x0008; - public static final int MEDIA_TYPE_LOCATION = 0x0010; - private static final int MEDA_TYPE_INVALID = 0x0020; - public static final int MEDIA_TYPE_ALL = 0xFFFF; - - /** The listener to call when events occur */ - private MediaPickerListener mListener; - - /** The handler used to dispatch calls to the listener */ - private Handler mListenerHandler; - - /** The bit flags of media types supported */ - private int mSupportedMediaTypes; - - /** The list of choosers which could be within the media picker */ - private final MediaChooser[] mChoosers; - - /** The list of currently enabled choosers */ - private final ArrayList mEnabledChoosers; - - /** The currently selected chooser */ - private MediaChooser mSelectedChooser; - - /** The main panel that controls the custom layout */ - private MediaPickerPanel mMediaPickerPanel; - - /** The linear layout that holds the icons to select individual chooser tabs */ - private LinearLayout mTabStrip; - - /** The view pager to swap between choosers */ - private ViewPager mViewPager; - - /** The current pager adapter for the view pager */ - private FixedViewPagerAdapter mPagerAdapter; - - /** True if the media picker is visible */ - private boolean mOpen; - - /** The theme color to use to make the media picker match the rest of the UI */ - private int mThemeColor; - - @VisibleForTesting - final Binding mBinding = BindingBase.createBinding(this); - - /** Handles picking image from the document picker */ - private DocumentImagePicker mDocumentImagePicker; - - /** Provides subscription-related data to access per-subscription configurations. */ - private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider; - - /** Provides access to DraftMessageData associated with the current conversation */ - private ImmutableBindingRef mDraftMessageDataModel; - - public MediaPicker() { - this(Factory.get().getApplicationContext()); - } - - public MediaPicker(final Context context) { - mBinding.bind(DataModel.get().createMediaPickerData(context)); - mEnabledChoosers = new ArrayList(); - mChoosers = new MediaChooser[] { - new CameraMediaChooser(this), - new GalleryMediaChooser(this), - new AudioMediaChooser(this), - }; - - mOpen = false; - setSupportedMediaTypes(MEDIA_TYPE_ALL); - } - - private boolean mIsAttached; - private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID; - private boolean mAnimateOnAttach; - - @Override - public void onAttach (final Activity activity) { - super.onAttach(activity); - mIsAttached = true; - if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) { - // open() was previously called. Do the pending open now. - doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach); - } - } - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mBinding.getData().init(getLoaderManager()); - mDocumentImagePicker = new DocumentImagePicker(this, - new SelectionListener() { - @Override - public void onDocumentSelected(final PendingAttachmentData data) { - if (mBinding.isBound()) { - dispatchPendingItemAdded(data); - } - } - }); - } - - @Override - public View onCreateView( - final LayoutInflater inflater, - final ViewGroup container, - final Bundle savedInstanceState) { - mMediaPickerPanel = (MediaPickerPanel) inflater.inflate( - R.layout.mediapicker_fragment, - container, - false); - mMediaPickerPanel.setMediaPicker(this); - mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip); - mTabStrip.setBackgroundColor(mThemeColor); - for (final MediaChooser chooser : mChoosers) { - chooser.onCreateTabButton(inflater, mTabStrip); - final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) != - MEDIA_TYPE_NONE; - final ImageButton tabButton = chooser.getTabButton(); - if (tabButton != null) { - tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE); - mTabStrip.addView(tabButton); - } - } - - mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager); - mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled( - final int position, - final float positionOffset, - final int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - // The position returned is relative to if we are in RtL mode. This class never - // switches the indices of the elements if we are in RtL mode so we need to - // translate the index back. For example, if the user clicked the item most to the - // right in RtL mode we would want the index to appear as 0 here, however the - // position returned would the last possible index. - if (UiUtils.isRtlMode()) { - position = mEnabledChoosers.size() - 1 - position; - } - selectChooser(mEnabledChoosers.get(position)); - } - - @Override - public void onPageScrollStateChanged(final int state) { - } - }); - // Camera initialization is expensive, so don't realize offscreen pages if not needed. - mViewPager.setOffscreenPageLimit(0); - mViewPager.setAdapter(mPagerAdapter); - final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled( - getActivity()); - mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled); - mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser)); - return mMediaPickerPanel; - } - - @Override - public void onPause() { - super.onPause(); - CameraManager.get().onPause(); - for (final MediaChooser chooser : mEnabledChoosers) { - chooser.onPause(); - } - } - - @Override - public void onResume() { - super.onResume(); - CameraManager.get().onResume(); - - for (final MediaChooser chooser : mEnabledChoosers) { - chooser.onResume(); - } - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mDocumentImagePicker.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mBinding.unbind(); - } - - /** - * Sets the theme color to make the media picker match the surrounding UI - * @param themeColor The new theme color - */ - public void setConversationThemeColor(final int themeColor) { - mThemeColor = themeColor; - if (mTabStrip != null) { - mTabStrip.setBackgroundColor(mThemeColor); - } - - for (final MediaChooser chooser : mEnabledChoosers) { - chooser.setThemeColor(mThemeColor); - } - } - - /** - * Gets the current conversation theme color. - */ - public int getConversationThemeColor() { - return mThemeColor; - } - - public void setDraftMessageDataModel(final BindingBase draftBinding) { - mDraftMessageDataModel = Binding.createBindingReference(draftBinding); - } - - public ImmutableBindingRef getDraftMessageDataModel() { - return mDraftMessageDataModel; - } - - public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) { - mSubscriptionDataProvider = provider; - } - - @Override - public int getConversationSelfSubId() { - return mSubscriptionDataProvider.getConversationSelfSubId(); - } - - /** - * Opens the media picker and optionally shows the chooser for the supplied media type - * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT} - * is used, then the default chooser from saved shared prefs is opened - */ - public void open(final int startingMediaType, final boolean animate) { - mOpen = true; - if (mIsAttached) { - doOpen(startingMediaType, animate); - } else { - // open() can get called immediately after the MediaPicker is created. In that case, - // we defer doing work as it may require an attached fragment (eg. calling - // Fragment#requestPermission) - mStartingMediaTypeOnAttach = startingMediaType; - mAnimateOnAttach = animate; - } - } - - private void doOpen(int startingMediaType, final boolean animate) { - final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled( - // getActivity() will be null at this point - Factory.get().getApplicationContext()); - - // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the - // last opened chooser index from shared prefs. - if (startingMediaType == MEDIA_TYPE_DEFAULT) { - final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex(); - if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) { - selectChooser(mEnabledChoosers.get(selectedChooserIndex)); - } else { - // This is the first time the picker is being used - if (isTouchExplorationEnabled) { - // Accessibility defaults to audio attachment mode. - startingMediaType = MEDIA_TYPE_AUDIO; - } - } - } - - if (mSelectedChooser == null) { - for (final MediaChooser chooser : mEnabledChoosers) { - if (startingMediaType == MEDIA_TYPE_DEFAULT || - (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) { - selectChooser(chooser); - break; - } - } - } - - if (mSelectedChooser == null) { - // Fall back to the first chooser. - selectChooser(mEnabledChoosers.get(0)); - } - - if (mMediaPickerPanel != null) { - mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled); - mMediaPickerPanel.setExpanded(true, animate, - mEnabledChoosers.indexOf(mSelectedChooser)); - } - } - - /** @return True if the media picker is open */ - public boolean isOpen() { - return mOpen; - } - - /** - * Sets the list of media types to allow the user to select - * @param mediaTypes The bit flags of media types to allow. Can be any combination of the - * MEDIA_TYPE_* values - */ - void setSupportedMediaTypes(final int mediaTypes) { - mSupportedMediaTypes = mediaTypes; - mEnabledChoosers.clear(); - boolean selectNextChooser = false; - for (final MediaChooser chooser : mChoosers) { - final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) != - MEDIA_TYPE_NONE; - if (enabled) { - // TODO Add a way to inform the chooser which media types are supported - mEnabledChoosers.add(chooser); - if (selectNextChooser) { - selectChooser(chooser); - selectNextChooser = false; - } - } else if (mSelectedChooser == chooser) { - selectNextChooser = true; - } - final ImageButton tabButton = chooser.getTabButton(); - if (tabButton != null) { - tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE); - } - } - - if (selectNextChooser && mEnabledChoosers.size() > 0) { - selectChooser(mEnabledChoosers.get(0)); - } - final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()]; - mEnabledChoosers.toArray(enabledChoosers); - mPagerAdapter = new FixedViewPagerAdapter(enabledChoosers); - if (mViewPager != null) { - mViewPager.setAdapter(mPagerAdapter); - } - - // Only rebind data if we are currently bound. Otherwise, we must have not - // bound to any data yet and should wait until onCreate() to bind data. - if (mBinding.isBound() && getActivity() != null) { - mBinding.unbind(); - mBinding.bind(DataModel.get().createMediaPickerData(getActivity())); - mBinding.getData().init(getLoaderManager()); - } - } - - ViewPager getViewPager() { - return mViewPager; - } - - /** Hides the media picker, and frees up any resources it’s using */ - public void dismiss(final boolean animate) { - mOpen = false; - if (mMediaPickerPanel != null) { - mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET); - } - mSelectedChooser = null; - } - - /** - * Sets the listener for the media picker events - * @param listener The listener which will receive events - */ - public void setListener(final MediaPickerListener listener) { - Assert.isMainThread(); - mListener = listener; - mListenerHandler = listener != null ? new Handler() : null; - } - - /** @return True if the media picker is in full-screen mode */ - public boolean isFullScreen() { - return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen(); - } - - public void setFullScreen(final boolean fullScreen) { - mMediaPickerPanel.setFullScreenView(fullScreen, true); - } - - public void updateActionBar(final ActionBar actionBar) { - if (getActivity() == null) { - return; - } - if (isFullScreen() && mSelectedChooser != null) { - mSelectedChooser.updateActionBar(actionBar); - } else { - actionBar.hide(); - } - } - - /** - * Selects a new chooser - * @param newSelectedChooser The newly selected chooser - */ - void selectChooser(final MediaChooser newSelectedChooser) { - if (mSelectedChooser == newSelectedChooser) { - return; - } - - if (mSelectedChooser != null) { - mSelectedChooser.setSelected(false); - } - mSelectedChooser = newSelectedChooser; - if (mSelectedChooser != null) { - mSelectedChooser.setSelected(true); - } - - final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser); - if (mViewPager != null) { - mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */); - } - - if (isFullScreen()) { - invalidateOptionsMenu(); - } - - // Save the newly selected chooser's index so we may directly switch to it the - // next time user opens the media picker. - mBinding.getData().saveSelectedChooserIndex(chooserIndex); - if (mMediaPickerPanel != null) { - mMediaPickerPanel.onChooserChanged(); - } - dispatchChooserSelected(chooserIndex); - } - - public boolean canShowIme() { - if (mSelectedChooser != null) { - return mSelectedChooser.canShowIme(); - } - return false; - } - - public boolean onBackPressed() { - return mSelectedChooser != null && mSelectedChooser.onBackPressed(); - } - - void invalidateOptionsMenu() { - ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu(); - } - - void dispatchOpened() { - setHasOptionsMenu(false); - mOpen = true; - mPagerAdapter.notifyDataSetChanged(); - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onOpened(); - } - }); - } - if (mSelectedChooser != null) { - mSelectedChooser.onFullScreenChanged(false); - mSelectedChooser.onOpenedChanged(true); - } - } - - void dispatchDismissed() { - setHasOptionsMenu(false); - mOpen = false; - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDismissed(); - } - }); - } - if (mSelectedChooser != null) { - mSelectedChooser.onOpenedChanged(false); - } - } - - void dispatchFullScreen(final boolean fullScreen) { - setHasOptionsMenu(fullScreen); - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onFullScreenChanged(fullScreen); - } - }); - } - if (mSelectedChooser != null) { - mSelectedChooser.onFullScreenChanged(fullScreen); - } - } - - void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) { - final List items = new ArrayList(1); - items.add(item); - dispatchItemsSelected(items, dismissMediaPicker); - } - - void dispatchItemsSelected(final Collection items, - final boolean dismissMediaPicker) { - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onItemsSelected(items, dismissMediaPicker); - } - }); - } - - if (isFullScreen() && !dismissMediaPicker) { - invalidateOptionsMenu(); - } - } - - void dispatchItemUnselected(final MessagePartData item) { - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onItemUnselected(item); - } - }); - } - - if (isFullScreen()) { - invalidateOptionsMenu(); - } - } - - void dispatchConfirmItemSelection() { - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onConfirmItemSelection(); - } - }); - } - } - - void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) { - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onPendingItemAdded(pendingItem); - } - }); - } - - if (isFullScreen()) { - invalidateOptionsMenu(); - } - } - - void dispatchChooserSelected(final int chooserIndex) { - if (mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onChooserSelected(chooserIndex); - } - }); - } - } - - public boolean canSwipeDownChooser() { - return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown(); - } - - public boolean isChooserHandlingTouch() { - return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch(); - } - - public void stopChooserTouchHandling() { - if (mSelectedChooser != null) { - mSelectedChooser.stopTouchHandling(); - } - } - - boolean getChooserShowsActionBarInFullScreen() { - return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0; - } - - @Override - public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - if (mSelectedChooser != null) { - mSelectedChooser.onCreateOptionsMenu(inflater, menu); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) || - super.onOptionsItemSelected(item); - } - - PagerAdapter getPagerAdapter() { - return mPagerAdapter; - } - - public void resetViewHolderState() { - mPagerAdapter.resetState(); - } - - /** - * Launch an external picker to pick item from document picker as attachment. - */ - public void launchDocumentPicker() { - mDocumentImagePicker.launchPicker(); - } - - public ImmutableBindingRef getMediaPickerDataBinding() { - return BindingBase.createBindingReference(mBinding); - } - - protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1; - protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2; - protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3; - protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4; - - @Override - public void onRequestPermissionsResult( - final int requestCode, final String permissions[], final int[] grantResults) { - if (mSelectedChooser != null) { - mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java deleted file mode 100644 index cc3a4a1..0000000 --- a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.GridView; - -public class MediaPickerGridView extends GridView { - - public MediaPickerGridView(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - /** - * Returns if the grid view can be swiped down further. It cannot be swiped down - * if there's no item or if we are already at the top. - */ - public boolean canSwipeDown() { - if (getAdapter() == null || getAdapter().getCount() == 0 || getChildCount() == 0) { - return false; - } - - final int firstVisiblePosition = getFirstVisiblePosition(); - if (firstVisiblePosition == 0 && getChildAt(0).getTop() >= 0) { - return false; - } - return true; - } -} diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java b/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java deleted file mode 100644 index 56b0a03..0000000 --- a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.Transformation; -import android.widget.LinearLayout; - -import com.android.messaging.R; -import com.android.messaging.ui.PagingAwareViewPager; -import com.android.messaging.util.Assert; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.UiUtils; - -/** - * Custom layout panel which makes the MediaPicker animations seamless and synchronized - * Designed to be very specific to the MediaPicker's usage - */ -public class MediaPickerPanel extends ViewGroup { - /** - * The window of time in which we might to decide to reinterpret the intent of a gesture. - */ - private static final long TOUCH_RECAPTURE_WINDOW_MS = 500L; - - // The two view components to layout - private LinearLayout mTabStrip; - private boolean mFullScreenOnly; - private PagingAwareViewPager mViewPager; - - /** - * True if the MediaPicker is full screen or animating into it - */ - private boolean mFullScreen; - - /** - * True if the MediaPicker is open at all - */ - private boolean mExpanded; - - /** - * The current desired height of the MediaPicker. This property may be animated and the - * measure pass uses it to determine what size the components are. - */ - private int mCurrentDesiredHeight; - - private final Handler mHandler = new Handler(); - - /** - * The media picker for dispatching events to the MediaPicker's listener - */ - private MediaPicker mMediaPicker; - - /** - * The computed default "half-screen" height of the view pager in px - */ - private final int mDefaultViewPagerHeight; - - /** - * The action bar height used to compute the padding on the view pager when it's full screen. - */ - private final int mActionBarHeight; - - private TouchHandler mTouchHandler; - - static final int PAGE_NOT_SET = -1; - - public MediaPickerPanel(final Context context, final AttributeSet attrs) { - super(context, attrs); - // Cache the computed dimension - mDefaultViewPagerHeight = getResources().getDimensionPixelSize( - R.dimen.mediapicker_default_chooser_height); - mActionBarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mTabStrip = (LinearLayout) findViewById(R.id.mediapicker_tabstrip); - mViewPager = (PagingAwareViewPager) findViewById(R.id.mediapicker_view_pager); - mTouchHandler = new TouchHandler(); - setOnTouchListener(mTouchHandler); - mViewPager.setOnTouchListener(mTouchHandler); - - // Make sure full screen mode is updated in landscape mode change when the panel is open. - addOnLayoutChangeListener(new OnLayoutChangeListener() { - private boolean mLandscapeMode = UiUtils.isLandscapeMode(); - - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - final boolean newLandscapeMode = UiUtils.isLandscapeMode(); - if (mLandscapeMode != newLandscapeMode) { - mLandscapeMode = newLandscapeMode; - if (mExpanded) { - setExpanded(mExpanded, false /* animate */, mViewPager.getCurrentItem(), - true /* force */); - } - } - } - }); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - int requestedHeight = MeasureSpec.getSize(heightMeasureSpec); - if (mMediaPicker.getChooserShowsActionBarInFullScreen()) { - requestedHeight -= mActionBarHeight; - } - int desiredHeight = Math.min(mCurrentDesiredHeight, requestedHeight); - if (mExpanded && desiredHeight == 0) { - // If we want to be shown, we have to have a non-0 height. Returning a height of 0 will - // cause the framework to abort the animation from 0, so we must always have some - // height once we start expanding - desiredHeight = 1; - } else if (!mExpanded && desiredHeight == 0) { - mViewPager.setVisibility(View.GONE); - mViewPager.setAdapter(null); - } - - measureChild(mTabStrip, widthMeasureSpec, heightMeasureSpec); - - int tabStripHeight; - if (requiresFullScreen()) { - // Ensure that the tab strip is always visible, even in full screen. - tabStripHeight = mTabStrip.getMeasuredHeight(); - } else { - // Slide out the tab strip at the end of the animation to full screen. - tabStripHeight = Math.min(mTabStrip.getMeasuredHeight(), - requestedHeight - desiredHeight); - } - - // If we are animating and have an interim desired height, use the default height. We can't - // take the max here as on some devices the mDefaultViewPagerHeight may be too big in - // landscape mode after animation. - final int tabAdjustedDesiredHeight = desiredHeight - tabStripHeight; - final int viewPagerHeight = - tabAdjustedDesiredHeight <= 1 ? mDefaultViewPagerHeight : tabAdjustedDesiredHeight; - - int viewPagerHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - viewPagerHeight, MeasureSpec.EXACTLY); - measureChild(mViewPager, widthMeasureSpec, viewPagerHeightMeasureSpec); - setMeasuredDimension(mViewPager.getMeasuredWidth(), desiredHeight); - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, - final int bottom) { - int y = top; - final int width = right - left; - - final int viewPagerHeight = mViewPager.getMeasuredHeight(); - mViewPager.layout(0, y, width, y + viewPagerHeight); - y += viewPagerHeight; - - mTabStrip.layout(0, y, width, y + mTabStrip.getMeasuredHeight()); - } - - void onChooserChanged() { - if (mFullScreen) { - setDesiredHeight(getDesiredHeight(), true); - } - } - - void setFullScreenOnly(boolean fullScreenOnly) { - mFullScreenOnly = fullScreenOnly; - } - - boolean isFullScreen() { - return mFullScreen; - } - - void setMediaPicker(final MediaPicker mediaPicker) { - mMediaPicker = mediaPicker; - } - - /** - * Get the desired height of the media picker panel for when the panel is not in motion (i.e. - * not being dragged by the user). - */ - private int getDesiredHeight() { - if (mFullScreen) { - int fullHeight = getContext().getResources().getDisplayMetrics().heightPixels; - if (OsUtil.isAtLeastKLP() && isAttachedToWindow()) { - // When we're attached to the window, we can get an accurate height, not necessary - // on older API level devices because they don't include the action bar height - View composeContainer = - getRootView().findViewById(R.id.conversation_and_compose_container); - if (composeContainer != null) { - // protect against composeContainer having been unloaded already - fullHeight -= UiUtils.getMeasuredBoundsOnScreen(composeContainer).top; - } - } - if (mMediaPicker.getChooserShowsActionBarInFullScreen()) { - return fullHeight - mActionBarHeight; - } else { - return fullHeight; - } - } else if (mExpanded) { - return LayoutParams.WRAP_CONTENT; - } else { - return 0; - } - } - - private void setupViewPager(final int startingPage) { - mViewPager.setVisibility(View.VISIBLE); - if (startingPage >= 0 && startingPage < mMediaPicker.getPagerAdapter().getCount()) { - mViewPager.setAdapter(mMediaPicker.getPagerAdapter()); - mViewPager.setCurrentItem(startingPage); - } - updateViewPager(); - } - - /** - * Expand the media picker panel. Since we always set the pager adapter to null when the panel - * is collapsed, we need to restore the adapter and the starting page. - * @param expanded expanded or collapsed - * @param animate need animation - * @param startingPage the desired selected page to start - */ - void setExpanded(final boolean expanded, final boolean animate, final int startingPage) { - setExpanded(expanded, animate, startingPage, false /* force */); - } - - private void setExpanded(final boolean expanded, final boolean animate, final int startingPage, - final boolean force) { - if (expanded == mExpanded && !force) { - return; - } - mFullScreen = false; - mExpanded = expanded; - mHandler.post(new Runnable() { - @Override - public void run() { - setDesiredHeight(getDesiredHeight(), animate); - } - }); - if (expanded) { - setupViewPager(startingPage); - mMediaPicker.dispatchOpened(); - } else { - mMediaPicker.dispatchDismissed(); - } - - // Call setFullScreenView() when we are in landscape mode so it can go full screen as - // soon as it is expanded. - if (expanded && requiresFullScreen()) { - setFullScreenView(true, animate); - } - } - - private boolean requiresFullScreen() { - return mFullScreenOnly || UiUtils.isLandscapeMode(); - } - - private void setDesiredHeight(int height, final boolean animate) { - final int startHeight = mCurrentDesiredHeight; - if (height == LayoutParams.WRAP_CONTENT) { - height = measureHeight(); - } - clearAnimation(); - if (animate) { - final int deltaHeight = height - startHeight; - final Animation animation = new Animation() { - @Override - protected void applyTransformation(final float interpolatedTime, - final Transformation t) { - mCurrentDesiredHeight = (int) (startHeight + deltaHeight * interpolatedTime); - requestLayout(); - } - - @Override - public boolean willChangeBounds() { - return true; - } - }; - animation.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION); - animation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR); - startAnimation(animation); - } else { - mCurrentDesiredHeight = height; - } - requestLayout(); - } - - /** - * @return The minimum total height of the view - */ - private int measureHeight() { - final int measureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST); - measureChild(mTabStrip, measureSpec, measureSpec); - return mDefaultViewPagerHeight + mTabStrip.getMeasuredHeight(); - } - - /** - * Enters or leaves full screen view - * - * @param fullScreen True to enter full screen view, false to leave - * @param animate True to animate the transition - */ - void setFullScreenView(final boolean fullScreen, final boolean animate) { - if (fullScreen == mFullScreen) { - return; - } - - if (requiresFullScreen() && !fullScreen) { - setExpanded(false /* expanded */, true /* animate */, PAGE_NOT_SET); - return; - } - mFullScreen = fullScreen; - setDesiredHeight(getDesiredHeight(), animate); - mMediaPicker.dispatchFullScreen(mFullScreen); - updateViewPager(); - } - - /** - * ViewPager should have its paging disabled when in full screen mode. - */ - private void updateViewPager() { - mViewPager.setPagingEnabled(!mFullScreen); - } - - @Override - public boolean onInterceptTouchEvent(final MotionEvent ev) { - return mTouchHandler.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev); - } - - /** - * Helper class to handle touch events and swipe gestures - */ - private class TouchHandler implements OnTouchListener { - /** - * The height of the view when the touch press started - */ - private int mDownHeight = -1; - - /** - * True if the panel moved at all (changed height) during the drag - */ - private boolean mMoved = false; - - // The threshold constants converted from DP to px - private final float mFlingThresholdPx; - private final float mBigFlingThresholdPx; - - // The system defined pixel size to determine when a movement is considered a drag. - private final int mTouchSlop; - - /** - * A copy of the MotionEvent that started the drag/swipe gesture - */ - private MotionEvent mDownEvent; - - /** - * Whether we are currently moving down. We may not be able to move down in full screen - * mode when the child view can swipe down (such as a list view). - */ - private boolean mMovedDown = false; - - /** - * Indicates whether the child view contained in the panel can swipe down at the beginning - * of the drag event (i.e. the initial down). The MediaPanel can contain - * scrollable children such as a list view / grid view. If the child view can swipe down, - * We want to let the child view handle the scroll first instead of handling it ourselves. - */ - private boolean mCanChildViewSwipeDown = false; - - /** - * Necessary direction ratio for a fling to be considered in one direction this prevents - * horizontal swipes with small vertical components from triggering vertical swipe actions - */ - private static final float DIRECTION_RATIO = 1.1f; - - TouchHandler() { - final Resources resources = getContext().getResources(); - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mFlingThresholdPx = resources.getDimensionPixelSize( - R.dimen.mediapicker_fling_threshold); - mBigFlingThresholdPx = resources.getDimensionPixelSize( - R.dimen.mediapicker_big_fling_threshold); - mTouchSlop = configuration.getScaledTouchSlop(); - } - - /** - * The media picker panel may contain scrollable children such as a GridView, which eats - * all touch events before we get to it. Therefore, we'd like to intercept these events - * before the children to determine if we should handle swiping down in full screen mode. - * In non-full screen mode, we should handle all vertical scrolling events and leave - * horizontal scrolling to the view pager. - */ - public boolean onInterceptTouchEvent(final MotionEvent ev) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - // Never capture the initial down, so that the children may handle it - // as well. Let the touch handler know about the down event as well. - mTouchHandler.onTouch(MediaPickerPanel.this, ev); - - // Ask the MediaPicker whether the contained view can be swiped down. - // We record the value at the start of the drag to decide the swiping mode - // for the entire motion. - mCanChildViewSwipeDown = mMediaPicker.canSwipeDownChooser(); - return false; - - case MotionEvent.ACTION_MOVE: { - if (mMediaPicker.isChooserHandlingTouch()) { - if (shouldAllowRecaptureTouch(ev)) { - mMediaPicker.stopChooserTouchHandling(); - mViewPager.setPagingEnabled(true); - return false; - } - // If the chooser is claiming ownership on all touch events, then we - // shouldn't try to handle them (neither should the view pager). - mViewPager.setPagingEnabled(false); - return false; - } else if (mCanChildViewSwipeDown) { - // Never capture event if the child view can swipe down. - return false; - } else if (!mFullScreen && mMoved) { - // When we are not fullscreen, we own any vertical drag motion. - return true; - } else if (mMovedDown) { - // We are currently handling the down swipe ourselves, so always - // capture this event. - return true; - } else { - // The current interaction mode is undetermined, so always let the - // touch handler know about this event. However, don't capture this - // event so the child may handle it as well. - mTouchHandler.onTouch(MediaPickerPanel.this, ev); - - // Capture the touch event from now on if we are handling the drag. - return mFullScreen ? mMovedDown : mMoved; - } - } - } - return false; - } - - /** - * Determine whether we think the user is actually trying to expand or slide despite the - * fact that they touched first on a chooser that captured the input. - */ - private boolean shouldAllowRecaptureTouch(MotionEvent ev) { - final long elapsedMs = ev.getEventTime() - ev.getDownTime(); - if (mDownEvent == null || elapsedMs == 0 || elapsedMs > TOUCH_RECAPTURE_WINDOW_MS) { - // Either we don't have info to decide or it's been long enough that we no longer - // want to reinterpret user intent. - return false; - } - final float dx = ev.getRawX() - mDownEvent.getRawX(); - final float dy = ev.getRawY() - mDownEvent.getRawY(); - final float dt = elapsedMs / 1000.0f; - final float maxAbsDelta = Math.max(Math.abs(dx), Math.abs(dy)); - final float velocity = maxAbsDelta / dt; - return velocity > mFlingThresholdPx; - } - - @Override - public boolean onTouch(final View view, final MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_UP: { - if (!mMoved || mDownEvent == null) { - return false; - } - final float dx = motionEvent.getRawX() - mDownEvent.getRawX(); - final float dy = motionEvent.getRawY() - mDownEvent.getRawY(); - - final float dt = - (motionEvent.getEventTime() - mDownEvent.getEventTime()) / 1000.0f; - final float yVelocity = dy / dt; - - boolean handled = false; - - // Vertical swipe occurred if the direction is as least mostly in the y - // component and has the required velocity (px/sec) - if ((dx == 0 || (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) && - Math.abs(yVelocity) > mFlingThresholdPx) { - if (yVelocity < 0 && mExpanded) { - setFullScreenView(true, true); - handled = true; - } else if (yVelocity > 0) { - if (mFullScreen && yVelocity < mBigFlingThresholdPx) { - setFullScreenView(false, true); - } else { - setExpanded(false, true, PAGE_NOT_SET); - } - handled = true; - } - } - - if (!handled) { - // If they didn't swipe enough, animate back to resting state - setDesiredHeight(getDesiredHeight(), true); - } - resetState(); - break; - } - case MotionEvent.ACTION_DOWN: { - mDownHeight = getHeight(); - mDownEvent = MotionEvent.obtain(motionEvent); - // If we are here and care about the return value (i.e. this is not called - // from onInterceptTouchEvent), then presumably no children view in the panel - // handles the down event. We'd like to handle future ACTION_MOVE events, so - // always claim ownership on this event so it doesn't fall through and gets - // cancelled by the framework. - return true; - } - case MotionEvent.ACTION_MOVE: { - if (mDownEvent == null) { - return mMoved; - } - - final float dx = mDownEvent.getRawX() - motionEvent.getRawX(); - final float dy = mDownEvent.getRawY() - motionEvent.getRawY(); - // Don't act if the move is mostly horizontal - if (Math.abs(dy) > mTouchSlop && - (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) { - setDesiredHeight((int) (mDownHeight + dy), false); - mMoved = true; - if (dy < -mTouchSlop) { - mMovedDown = true; - } - } - return mMoved; - } - - } - return mMoved; - } - - private void resetState() { - mDownEvent = null; - mDownHeight = -1; - mMoved = false; - mMovedDown = false; - mCanChildViewSwipeDown = false; - updateViewPager(); - } - } -} - diff --git a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java b/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java deleted file mode 100644 index 7ac7871..0000000 --- a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.hardware.Camera; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.net.Uri; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.SafeAsyncTask; - -import java.io.FileNotFoundException; - -class MmsVideoRecorder extends MediaRecorder { - private static final float VIDEO_OVERSHOOT_SLOP = .85F; - - private static final int BITS_PER_BYTE = 8; - - // We think user will expect to be able to record videos at least this long - private static final long MIN_DURATION_LIMIT_SECONDS = 25; - - /** The uri where video is being recorded to */ - private Uri mTempVideoUri; - - /** The settings used for video recording */ - private final CamcorderProfile mCamcorderProfile; - - public MmsVideoRecorder(final Camera camera, final int cameraIndex, final int orientation, - final int maxMessageSize) - throws FileNotFoundException { - mCamcorderProfile = - CamcorderProfile.get(cameraIndex, CamcorderProfile.QUALITY_LOW); - mTempVideoUri = MediaScratchFileProvider.buildMediaScratchSpaceUri( - ContentType.getExtension(getContentType())); - - // The video recorder can sometimes return a file that's larger than the max we - // say we can handle. Try to handle that overshoot by specifying an 85% limit. - final long sizeLimit = (long) (maxMessageSize * VIDEO_OVERSHOOT_SLOP); - - // The QUALITY_LOW profile might not be low enough to allow for video of a reasonable - // minimum duration. Adjust a/v bitrates to allow at least MIN_DURATION_LIMIT video - // to be recorded. - int audioBitRate = mCamcorderProfile.audioBitRate; - int videoBitRate = mCamcorderProfile.videoBitRate; - final double initialDurationLimit = sizeLimit * BITS_PER_BYTE - / (double) (audioBitRate + videoBitRate); - if (initialDurationLimit < MIN_DURATION_LIMIT_SECONDS) { - // Reduce the suggested bitrates. These bitrates are only requests, if implementation - // can't actually hit these goals it will still record video at higher rate and stop when - // it hits the size limit. - final double bitRateAdjustmentFactor = initialDurationLimit / MIN_DURATION_LIMIT_SECONDS; - audioBitRate *= bitRateAdjustmentFactor; - videoBitRate *= bitRateAdjustmentFactor; - } - - setCamera(camera); - setOrientationHint(orientation); - setAudioSource(MediaRecorder.AudioSource.CAMCORDER); - setVideoSource(MediaRecorder.VideoSource.CAMERA); - setOutputFormat(mCamcorderProfile.fileFormat); - setOutputFile( - Factory.get().getApplicationContext().getContentResolver().openFileDescriptor( - mTempVideoUri, "w").getFileDescriptor()); - - // Copy settings from CamcorderProfile to MediaRecorder - setAudioEncodingBitRate(audioBitRate); - setAudioChannels(mCamcorderProfile.audioChannels); - setAudioEncoder(mCamcorderProfile.audioCodec); - setAudioSamplingRate(mCamcorderProfile.audioSampleRate); - setVideoEncodingBitRate(videoBitRate); - setVideoEncoder(mCamcorderProfile.videoCodec); - setVideoFrameRate(mCamcorderProfile.videoFrameRate); - setVideoSize( - mCamcorderProfile.videoFrameWidth, mCamcorderProfile.videoFrameHeight); - setMaxFileSize(sizeLimit); - } - - Uri getVideoUri() { - return mTempVideoUri; - } - - int getVideoWidth() { - return mCamcorderProfile.videoFrameWidth; - } - - int getVideoHeight() { - return mCamcorderProfile.videoFrameHeight; - } - - void cleanupTempFile() { - final Uri tempUri = mTempVideoUri; - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - Factory.get().getApplicationContext().getContentResolver().delete( - tempUri, null, null); - } - }); - mTempVideoUri = null; - } - - String getContentType() { - if (mCamcorderProfile.fileFormat == OutputFormat.MPEG_4) { - return ContentType.VIDEO_MP4; - } else { - // 3GPP is the only other video format with a constant in OutputFormat - return ContentType.VIDEO_3GPP; - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java b/src/com/android/messaging/ui/mediapicker/PausableChronometer.java deleted file mode 100644 index dc8f90b..0000000 --- a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.widget.Chronometer; - -import com.android.messaging.ui.PlaybackStateView; - -/** - * A pausable Chronometer implementation. The default Chronometer in Android only stops the UI - * from updating when you call stop(), but doesn't actually pause it. This implementation adds an - * additional timestamp that tracks the timespan for the pause and compensate for that. - */ -public class PausableChronometer extends Chronometer implements PlaybackStateView { - // Keeps track of how far long the Chronometer has been tracking when it's paused. We'd like - // to start from this time the next time it's resumed. - private long mTimeWhenPaused = 0; - - public PausableChronometer(final Context context, final AttributeSet attrs) { - super(context, attrs); - } - - /** - * Reset the timer and start counting from zero. - */ - @Override - public void restart() { - reset(); - start(); - } - - /** - * Reset the timer to zero, but don't start it. - */ - @Override - public void reset() { - stop(); - setBase(SystemClock.elapsedRealtime()); - mTimeWhenPaused = 0; - } - - /** - * Resume the timer after a previous pause. - */ - @Override - public void resume() { - setBase(SystemClock.elapsedRealtime() - mTimeWhenPaused); - start(); - } - - /** - * Pause the timer. - */ - @Override - public void pause() { - stop(); - mTimeWhenPaused = SystemClock.elapsedRealtime() - getBase(); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java deleted file mode 100644 index 5dc3185..0000000 --- a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.hardware.Camera; -import android.os.Parcelable; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; - -import java.io.IOException; - -/** - * A software rendered preview surface for the camera. This renders slower and causes more jank, so - * HardwareCameraPreview is preferred if possible. - * - * There is a significant amount of duplication between HardwareCameraPreview and - * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The - * implementations of the shared methods are delegated to CameraPreview - */ -public class SoftwareCameraPreview extends SurfaceView implements CameraPreview.CameraPreviewHost { - private final CameraPreview mPreview; - - public SoftwareCameraPreview(final Context context) { - super(context); - mPreview = new CameraPreview(this); - getHolder().addCallback(new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(final SurfaceHolder surfaceHolder) { - CameraManager.get().setSurface(mPreview); - } - - @Override - public void surfaceChanged(final SurfaceHolder surfaceHolder, final int format, final int width, - final int height) { - CameraManager.get().setSurface(mPreview); - } - - @Override - public void surfaceDestroyed(final SurfaceHolder surfaceHolder) { - CameraManager.get().setSurface(null); - } - }); - } - - - @Override - protected void onVisibilityChanged(final View changedView, final int visibility) { - super.onVisibilityChanged(changedView, visibility); - mPreview.onVisibilityChanged(visibility); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mPreview.onDetachedFromWindow(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mPreview.onAttachedToWindow(); - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - super.onRestoreInstanceState(state); - mPreview.onRestoreInstanceState(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec); - heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public View getView() { - return this; - } - - @Override - public boolean isValid() { - return getHolder() != null; - } - - @Override - public void startPreview(final Camera camera) throws IOException { - camera.setPreviewDisplay(getHolder()); - } - - @Override - public void onCameraPermissionGranted() { - mPreview.onCameraPermissionGranted(); - } -} - - diff --git a/src/com/android/messaging/ui/mediapicker/SoundLevels.java b/src/com/android/messaging/ui/mediapicker/SoundLevels.java deleted file mode 100644 index 6f4dca6..0000000 --- a/src/com/android/messaging/ui/mediapicker/SoundLevels.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.animation.ObjectAnimator; -import android.animation.TimeAnimator; -import android.animation.TimeAnimator.TimeListener; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo; - -import com.android.messaging.R; -import com.android.messaging.util.LogUtil; - -/** - * This view draws circular sound levels. By default the sound levels are black, unless - * otherwise defined via {@link #mPrimaryLevelPaint}. - */ -public class SoundLevels extends View { - private static final String TAG = LogUtil.BUGLE_TAG; - private static final boolean DEBUG = false; - - private boolean mCenterDefined; - private int mCenterX; - private int mCenterY; - - // Paint for the main level meter, most closely follows the mic. - private final Paint mPrimaryLevelPaint; - - // The minimum size of the levels as a percentage of the max, that is the size when volume is 0. - private final float mMinimumLevel; - - // The minimum size of the levels, that is the size when volume is 0. - private final float mMinimumLevelSize; - - // The maximum size of the levels, that is the size when volume is 100. - private final float mMaximumLevelSize; - - // Generates clock ticks for the animation using the global animation loop. - private final TimeAnimator mSpeechLevelsAnimator; - - private float mCurrentVolume; - - // Indicates whether we should be animating the sound level. - private boolean mIsEnabled; - - // Input level is pulled from here. - private AudioLevelSource mLevelSource; - - public SoundLevels(final Context context) { - this(context, null); - } - - public SoundLevels(final Context context, final AttributeSet attrs) { - this(context, attrs, 0); - } - - public SoundLevels(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - - // Safe source, replaced with system one when attached. - mLevelSource = new AudioLevelSource(); - mLevelSource.setSpeechLevel(0); - - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SoundLevels, - defStyle, 0); - - mMaximumLevelSize = a.getDimensionPixelOffset( - R.styleable.SoundLevels_maxLevelRadius, 0); - mMinimumLevelSize = a.getDimensionPixelOffset( - R.styleable.SoundLevels_minLevelRadius, 0); - mMinimumLevel = mMinimumLevelSize / mMaximumLevelSize; - - mPrimaryLevelPaint = new Paint(); - mPrimaryLevelPaint.setColor( - a.getColor(R.styleable.SoundLevels_primaryColor, Color.BLACK)); - mPrimaryLevelPaint.setFlags(Paint.ANTI_ALIAS_FLAG); - - a.recycle(); - - // This animator generates ticks that invalidate the - // view so that the animation is synced with the global animation loop. - // TODO: We could probably remove this in favor of using postInvalidateOnAnimation - // which might improve things further. - mSpeechLevelsAnimator = new TimeAnimator(); - mSpeechLevelsAnimator.setRepeatCount(ObjectAnimator.INFINITE); - mSpeechLevelsAnimator.setTimeListener(new TimeListener() { - @Override - public void onTimeUpdate(final TimeAnimator animation, final long totalTime, - final long deltaTime) { - invalidate(); - } - }); - } - - @Override - protected void onDraw(final Canvas canvas) { - if (!mIsEnabled) { - return; - } - - if (!mCenterDefined) { - // One time computation here, because we can't rely on getWidth() to be computed at - // constructor time or in onFinishInflate :(. - mCenterX = getWidth() / 2; - mCenterY = getWidth() / 2; - mCenterDefined = true; - } - - final int level = mLevelSource.getSpeechLevel(); - // Either ease towards the target level, or decay away from it depending on whether - // its higher or lower than the current. - if (level > mCurrentVolume) { - mCurrentVolume = mCurrentVolume + ((level - mCurrentVolume) / 4); - } else { - mCurrentVolume = mCurrentVolume * 0.95f; - } - - final float radius = mMinimumLevel + (1f - mMinimumLevel) * mCurrentVolume / 100; - mPrimaryLevelPaint.setStyle(Style.FILL); - canvas.drawCircle(mCenterX, mCenterY, radius * mMaximumLevelSize, mPrimaryLevelPaint); - } - - public void setLevelSource(final AudioLevelSource source) { - if (DEBUG) { - Log.d(TAG, "Speech source set."); - } - mLevelSource = source; - } - - private void startSpeechLevelsAnimator() { - if (DEBUG) { - Log.d(TAG, "startAnimator()"); - } - if (!mSpeechLevelsAnimator.isStarted()) { - mSpeechLevelsAnimator.start(); - } - } - - private void stopSpeechLevelsAnimator() { - if (DEBUG) { - Log.d(TAG, "stopAnimator()"); - } - if (mSpeechLevelsAnimator.isStarted()) { - mSpeechLevelsAnimator.end(); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - stopSpeechLevelsAnimator(); - } - - @Override - public void setEnabled(final boolean enabled) { - if (enabled == mIsEnabled) { - return; - } - if (DEBUG) { - Log.d("TAG", "setEnabled: " + enabled); - } - super.setEnabled(enabled); - mIsEnabled = enabled; - setKeepScreenOn(enabled); - updateSpeechLevelsAnimatorState(); - } - - private void updateSpeechLevelsAnimatorState() { - if (mIsEnabled) { - startSpeechLevelsAnimator(); - } else { - stopSpeechLevelsAnimator(); - } - } - - /** - * This is required to make the View findable by uiautomator - */ - @Override - public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(SoundLevels.class.getCanonicalName()); - } - - /** - * Set the alpha level of the sound circles. - */ - public void setPrimaryColorAlpha(final int alpha) { - mPrimaryLevelPaint.setAlpha(alpha); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java deleted file mode 100644 index 92ed3c1..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -public interface FocusIndicator { - public void showStart(); - public void showSuccess(boolean timeout); - public void showFail(boolean timeout); - public void clear(); -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java deleted file mode 100644 index e620fc2..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.hardware.Camera.Area; -import android.hardware.Camera.Parameters; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; -import java.util.List; - -/* A class that handles everything about focus in still picture mode. - * This also handles the metering area because it is the same as focus area. - * - * The test cases: - * (1) The camera has continuous autofocus. Move the camera. Take a picture when - * CAF is not in progress. - * (2) The camera has continuous autofocus. Move the camera. Take a picture when - * CAF is in progress. - * (3) The camera has face detection. Point the camera at some faces. Hold the - * shutter. Release to take a picture. - * (4) The camera has face detection. Point the camera at some faces. Single tap - * the shutter to take a picture. - * (5) The camera has autofocus. Single tap the shutter to take a picture. - * (6) The camera has autofocus. Hold the shutter. Release to take a picture. - * (7) The camera has no autofocus. Single tap the shutter and take a picture. - * (8) The camera has autofocus and supports focus area. Touch the screen to - * trigger autofocus. Take a picture. - * (9) The camera has autofocus and supports focus area. Touch the screen to - * trigger autofocus. Wait until it times out. - * (10) The camera has no autofocus and supports metering area. Touch the screen - * to change metering area. - */ -public class FocusOverlayManager { - private static final String TAG = LogUtil.BUGLE_TAG; - private static final String TRUE = "true"; - private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported"; - private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = - "auto-whitebalance-lock-supported"; - - private static final int RESET_TOUCH_FOCUS = 0; - private static final int RESET_TOUCH_FOCUS_DELAY = 3000; - - private int mState = STATE_IDLE; - private static final int STATE_IDLE = 0; // Focus is not active. - private static final int STATE_FOCUSING = 1; // Focus is in progress. - // Focus is in progress and the camera should take a picture after focus finishes. - private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2; - private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds. - private static final int STATE_FAIL = 4; // Focus finishes and fails. - - private boolean mInitialized; - private boolean mFocusAreaSupported; - private boolean mMeteringAreaSupported; - private boolean mLockAeAwbNeeded; - private boolean mAeAwbLock; - private Matrix mMatrix; - - private PieRenderer mPieRenderer; - - private int mPreviewWidth; // The width of the preview frame layout. - private int mPreviewHeight; // The height of the preview frame layout. - private boolean mMirror; // true if the camera is front-facing. - private int mDisplayOrientation; - private List mFocusArea; // focus area in driver format - private List mMeteringArea; // metering area in driver format - private String mFocusMode; - private String mOverrideFocusMode; - private Parameters mParameters; - private Handler mHandler; - Listener mListener; - - public interface Listener { - public void autoFocus(); - public void cancelAutoFocus(); - public boolean capture(); - public void setFocusParameters(); - } - - private class MainHandler extends Handler { - public MainHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case RESET_TOUCH_FOCUS: { - cancelAutoFocus(); - break; - } - } - } - } - - public FocusOverlayManager(Listener listener, Looper looper) { - mHandler = new MainHandler(looper); - mMatrix = new Matrix(); - mListener = listener; - } - - public void setFocusRenderer(PieRenderer renderer) { - mPieRenderer = renderer; - mInitialized = (mMatrix != null); - } - - public void setParameters(Parameters parameters) { - // parameters can only be null when onConfigurationChanged is called - // before camera is open. We will just return in this case, because - // parameters will be set again later with the right parameters after - // camera is open. - if (parameters == null) { - return; - } - mParameters = parameters; - mFocusAreaSupported = isFocusAreaSupported(parameters); - mMeteringAreaSupported = isMeteringAreaSupported(parameters); - mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) || - isAutoWhiteBalanceLockSupported(mParameters)); - } - - public void setPreviewSize(int previewWidth, int previewHeight) { - if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) { - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; - setMatrix(); - } - } - - public void setMirror(boolean mirror) { - mMirror = mirror; - setMatrix(); - } - - public void setDisplayOrientation(int displayOrientation) { - mDisplayOrientation = displayOrientation; - setMatrix(); - } - - private void setMatrix() { - if (mPreviewWidth != 0 && mPreviewHeight != 0) { - Matrix matrix = new Matrix(); - prepareMatrix(matrix, mMirror, mDisplayOrientation, - mPreviewWidth, mPreviewHeight); - // In face detection, the matrix converts the driver coordinates to UI - // coordinates. In tap focus, the inverted matrix converts the UI - // coordinates to driver coordinates. - matrix.invert(mMatrix); - mInitialized = (mPieRenderer != null); - } - } - - private void lockAeAwbIfNeeded() { - if (mLockAeAwbNeeded && !mAeAwbLock) { - mAeAwbLock = true; - mListener.setFocusParameters(); - } - } - - private void unlockAeAwbIfNeeded() { - if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) { - mAeAwbLock = false; - mListener.setFocusParameters(); - } - } - - public void onShutterDown() { - if (!mInitialized) { - return; - } - - boolean autoFocusCalled = false; - if (needAutoFocusCall()) { - // Do not focus if touch focus has been triggered. - if (mState != STATE_SUCCESS && mState != STATE_FAIL) { - autoFocus(); - autoFocusCalled = true; - } - } - - if (!autoFocusCalled) { - lockAeAwbIfNeeded(); - } - } - - public void onShutterUp() { - if (!mInitialized) { - return; - } - - if (needAutoFocusCall()) { - // User releases half-pressed focus key. - if (mState == STATE_FOCUSING || mState == STATE_SUCCESS - || mState == STATE_FAIL) { - cancelAutoFocus(); - } - } - - // Unlock AE and AWB after cancelAutoFocus. Camera API does not - // guarantee setParameters can be called during autofocus. - unlockAeAwbIfNeeded(); - } - - public void doSnap() { - if (!mInitialized) { - return; - } - - // If the user has half-pressed the shutter and focus is completed, we - // can take the photo right away. If the focus mode is infinity, we can - // also take the photo. - if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) { - capture(); - } else if (mState == STATE_FOCUSING) { - // Half pressing the shutter (i.e. the focus button event) will - // already have requested AF for us, so just request capture on - // focus here. - mState = STATE_FOCUSING_SNAP_ON_FINISH; - } else if (mState == STATE_IDLE) { - // We didn't do focus. This can happen if the user press focus key - // while the snapshot is still in progress. The user probably wants - // the next snapshot as soon as possible, so we just do a snapshot - // without focusing again. - capture(); - } - } - - public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { - if (mState == STATE_FOCUSING_SNAP_ON_FINISH) { - // Take the picture no matter focus succeeds or fails. No need - // to play the AF sound if we're about to play the shutter - // sound. - if (focused) { - mState = STATE_SUCCESS; - } else { - mState = STATE_FAIL; - } - updateFocusUI(); - capture(); - } else if (mState == STATE_FOCUSING) { - // This happens when (1) user is half-pressing the focus key or - // (2) touch focus is triggered. Play the focus tone. Do not - // take the picture now. - if (focused) { - mState = STATE_SUCCESS; - } else { - mState = STATE_FAIL; - } - updateFocusUI(); - // If this is triggered by touch focus, cancel focus after a - // while. - if (mFocusArea != null) { - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); - } - if (shutterButtonPressed) { - // Lock AE & AWB so users can half-press shutter and recompose. - lockAeAwbIfNeeded(); - } - } else if (mState == STATE_IDLE) { - // User has released the focus key before focus completes. - // Do nothing. - } - } - - public void onAutoFocusMoving(boolean moving) { - if (!mInitialized) { - return; - } - - // Ignore if we have requested autofocus. This method only handles - // continuous autofocus. - if (mState != STATE_IDLE) { - return; - } - - if (moving) { - mPieRenderer.showStart(); - } else { - mPieRenderer.showSuccess(true); - } - } - - private void initializeFocusAreas(int focusWidth, int focusHeight, - int x, int y, int previewWidth, int previewHeight) { - if (mFocusArea == null) { - mFocusArea = new ArrayList(); - mFocusArea.add(new Area(new Rect(), 1)); - } - - // Convert the coordinates to driver format. - calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, - ((Area) mFocusArea.get(0)).rect); - } - - private void initializeMeteringAreas(int focusWidth, int focusHeight, - int x, int y, int previewWidth, int previewHeight) { - if (mMeteringArea == null) { - mMeteringArea = new ArrayList(); - mMeteringArea.add(new Area(new Rect(), 1)); - } - - // Convert the coordinates to driver format. - // AE area is bigger because exposure is sensitive and - // easy to over- or underexposure if area is too small. - calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight, - ((Area) mMeteringArea.get(0)).rect); - } - - public void onSingleTapUp(int x, int y) { - if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) { - return; - } - - // Let users be able to cancel previous touch focus. - if ((mFocusArea != null) && (mState == STATE_FOCUSING || - mState == STATE_SUCCESS || mState == STATE_FAIL)) { - cancelAutoFocus(); - } - // Initialize variables. - int focusWidth = mPieRenderer.getSize(); - int focusHeight = mPieRenderer.getSize(); - if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) { - return; - } - int previewWidth = mPreviewWidth; - int previewHeight = mPreviewHeight; - // Initialize mFocusArea. - if (mFocusAreaSupported) { - initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); - } - // Initialize mMeteringArea. - if (mMeteringAreaSupported) { - initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); - } - - // Use margin to set the focus indicator to the touched area. - mPieRenderer.setFocus(x, y); - - // Set the focus area and metering area. - mListener.setFocusParameters(); - if (mFocusAreaSupported) { - autoFocus(); - } else { // Just show the indicator in all other cases. - updateFocusUI(); - // Reset the metering area in 3 seconds. - mHandler.removeMessages(RESET_TOUCH_FOCUS); - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); - } - } - - public void onPreviewStarted() { - mState = STATE_IDLE; - } - - public void onPreviewStopped() { - // If auto focus was in progress, it would have been stopped. - mState = STATE_IDLE; - resetTouchFocus(); - updateFocusUI(); - } - - public void onCameraReleased() { - onPreviewStopped(); - } - - private void autoFocus() { - LogUtil.v(TAG, "Start autofocus."); - mListener.autoFocus(); - mState = STATE_FOCUSING; - updateFocusUI(); - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - private void cancelAutoFocus() { - LogUtil.v(TAG, "Cancel autofocus."); - - // Reset the tap area before calling mListener.cancelAutofocus. - // Otherwise, focus mode stays at auto and the tap area passed to the - // driver is not reset. - resetTouchFocus(); - mListener.cancelAutoFocus(); - mState = STATE_IDLE; - updateFocusUI(); - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - private void capture() { - if (mListener.capture()) { - mState = STATE_IDLE; - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - } - - public String getFocusMode() { - if (mOverrideFocusMode != null) { - return mOverrideFocusMode; - } - List supportedFocusModes = mParameters.getSupportedFocusModes(); - - if (mFocusAreaSupported && mFocusArea != null) { - // Always use autofocus in tap-to-focus. - mFocusMode = Parameters.FOCUS_MODE_AUTO; - } else { - mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; - } - - if (!isSupported(mFocusMode, supportedFocusModes)) { - // For some reasons, the driver does not support the current - // focus mode. Fall back to auto. - if (isSupported(Parameters.FOCUS_MODE_AUTO, - mParameters.getSupportedFocusModes())) { - mFocusMode = Parameters.FOCUS_MODE_AUTO; - } else { - mFocusMode = mParameters.getFocusMode(); - } - } - return mFocusMode; - } - - public List getFocusAreas() { - return mFocusArea; - } - - public List getMeteringAreas() { - return mMeteringArea; - } - - public void updateFocusUI() { - if (!mInitialized) { - return; - } - FocusIndicator focusIndicator = mPieRenderer; - - if (mState == STATE_IDLE) { - if (mFocusArea == null) { - focusIndicator.clear(); - } else { - // Users touch on the preview and the indicator represents the - // metering area. Either focus area is not supported or - // autoFocus call is not required. - focusIndicator.showStart(); - } - } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) { - focusIndicator.showStart(); - } else { - if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) { - // TODO: check HAL behavior and decide if this can be removed. - focusIndicator.showSuccess(false); - } else if (mState == STATE_SUCCESS) { - focusIndicator.showSuccess(false); - } else if (mState == STATE_FAIL) { - focusIndicator.showFail(false); - } - } - } - - public void resetTouchFocus() { - if (!mInitialized) { - return; - } - - // Put focus indicator to the center. clear reset position - mPieRenderer.clear(); - - mFocusArea = null; - mMeteringArea = null; - } - - private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, - int x, int y, int previewWidth, int previewHeight, Rect rect) { - int areaWidth = (int) (focusWidth * areaMultiple); - int areaHeight = (int) (focusHeight * areaMultiple); - int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth); - int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight); - - RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight); - mMatrix.mapRect(rectF); - rectFToRect(rectF, rect); - } - - /* package */ int getFocusState() { - return mState; - } - - public boolean isFocusCompleted() { - return mState == STATE_SUCCESS || mState == STATE_FAIL; - } - - public boolean isFocusingSnapOnFinish() { - return mState == STATE_FOCUSING_SNAP_ON_FINISH; - } - - public void removeMessages() { - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - public void overrideFocusMode(String focusMode) { - mOverrideFocusMode = focusMode; - } - - public void setAeAwbLock(boolean lock) { - mAeAwbLock = lock; - } - - public boolean getAeAwbLock() { - return mAeAwbLock; - } - - private boolean needAutoFocusCall() { - String focusMode = getFocusMode(); - return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY) - || focusMode.equals(Parameters.FOCUS_MODE_FIXED) - || focusMode.equals(Parameters.FOCUS_MODE_EDOF)); - } - - public static boolean isAutoExposureLockSupported(Parameters params) { - return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); - } - - public static boolean isAutoWhiteBalanceLockSupported(Parameters params) { - return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); - } - - public static boolean isSupported(String value, List supported) { - return supported != null && supported.indexOf(value) >= 0; - } - - public static boolean isMeteringAreaSupported(Parameters params) { - return params.getMaxNumMeteringAreas() > 0; - } - - public static boolean isFocusAreaSupported(Parameters params) { - return (params.getMaxNumFocusAreas() > 0 - && isSupported(Parameters.FOCUS_MODE_AUTO, - params.getSupportedFocusModes())); - } - - public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, - int viewWidth, int viewHeight) { - // Need mirror for front camera. - matrix.setScale(mirror ? -1 : 1, 1); - // This is the value for android.hardware.Camera.setDisplayOrientation. - matrix.postRotate(displayOrientation); - // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). - // UI coordinates range from (0, 0) to (width, height). - matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); - matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); - } - - public static int clamp(int x, int min, int max) { - Assert.isTrue(max >= min); - if (x > max) { - return max; - } - if (x < min) { - return min; - } - return x; - } - - public static void rectFToRect(RectF rectF, Rect rect) { - rect.left = Math.round(rectF.left); - rect.top = Math.round(rectF.top); - rect.right = Math.round(rectF.right); - rect.bottom = Math.round(rectF.bottom); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java deleted file mode 100644 index df6734f..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -import android.content.Context; -import android.graphics.Canvas; -import android.view.MotionEvent; - -public abstract class OverlayRenderer implements RenderOverlay.Renderer { - - private static final String TAG = "CAM OverlayRenderer"; - protected RenderOverlay mOverlay; - - protected int mLeft, mTop, mRight, mBottom; - - protected boolean mVisible; - - public void setVisible(boolean vis) { - mVisible = vis; - update(); - } - - public boolean isVisible() { - return mVisible; - } - - // default does not handle touch - @Override - public boolean handlesTouch() { - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - return false; - } - - public abstract void onDraw(Canvas canvas); - - public void draw(Canvas canvas) { - if (mVisible) { - onDraw(canvas); - } - } - - @Override - public void setOverlay(RenderOverlay overlay) { - mOverlay = overlay; - } - - @Override - public void layout(int left, int top, int right, int bottom) { - mLeft = left; - mRight = right; - mTop = top; - mBottom = bottom; - } - - protected Context getContext() { - if (mOverlay != null) { - return mOverlay.getContext(); - } else { - return null; - } - } - - public int getWidth() { - return mRight - mLeft; - } - - public int getHeight() { - return mBottom - mTop; - } - - protected void update() { - if (mOverlay != null) { - mOverlay.update(); - } - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java deleted file mode 100644 index c602852..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.drawable.Drawable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Pie menu item - */ -public class PieItem { - - public static interface OnClickListener { - void onClick(PieItem item); - } - - private Drawable mDrawable; - private int level; - private float mCenter; - private float start; - private float sweep; - private float animate; - private int inner; - private int outer; - private boolean mSelected; - private boolean mEnabled; - private List mItems; - private Path mPath; - private OnClickListener mOnClickListener; - private float mAlpha; - - // Gray out the view when disabled - private static final float ENABLED_ALPHA = 1; - private static final float DISABLED_ALPHA = (float) 0.3; - private boolean mChangeAlphaWhenDisabled = true; - - public PieItem(Drawable drawable, int level) { - mDrawable = drawable; - this.level = level; - setAlpha(1f); - mEnabled = true; - setAnimationAngle(getAnimationAngle()); - start = -1; - mCenter = -1; - } - - public boolean hasItems() { - return mItems != null; - } - - public List getItems() { - return mItems; - } - - public void addItem(PieItem item) { - if (mItems == null) { - mItems = new ArrayList(); - } - mItems.add(item); - } - - public void setPath(Path p) { - mPath = p; - } - - public Path getPath() { - return mPath; - } - - public void setChangeAlphaWhenDisabled (boolean enable) { - mChangeAlphaWhenDisabled = enable; - } - - public void setAlpha(float alpha) { - mAlpha = alpha; - mDrawable.setAlpha((int) (255 * alpha)); - } - - public void setAnimationAngle(float a) { - animate = a; - } - - public float getAnimationAngle() { - return animate; - } - - public void setEnabled(boolean enabled) { - mEnabled = enabled; - if (mChangeAlphaWhenDisabled) { - if (mEnabled) { - setAlpha(ENABLED_ALPHA); - } else { - setAlpha(DISABLED_ALPHA); - } - } - } - - public boolean isEnabled() { - return mEnabled; - } - - public void setSelected(boolean s) { - mSelected = s; - } - - public boolean isSelected() { - return mSelected; - } - - public int getLevel() { - return level; - } - - public void setGeometry(float st, float sw, int inside, int outside) { - start = st; - sweep = sw; - inner = inside; - outer = outside; - } - - public void setFixedSlice(float center, float sweep) { - mCenter = center; - this.sweep = sweep; - } - - public float getCenter() { - return mCenter; - } - - public float getStart() { - return start; - } - - public float getStartAngle() { - return start + animate; - } - - public float getSweep() { - return sweep; - } - - public int getInnerRadius() { - return inner; - } - - public int getOuterRadius() { - return outer; - } - - public void setOnClickListener(OnClickListener listener) { - mOnClickListener = listener; - } - - public void performClick() { - if (mOnClickListener != null) { - mOnClickListener.onClick(this); - } - } - - public int getIntrinsicWidth() { - return mDrawable.getIntrinsicWidth(); - } - - public int getIntrinsicHeight() { - return mDrawable.getIntrinsicHeight(); - } - - public void setBounds(int left, int top, int right, int bottom) { - mDrawable.setBounds(left, top, right, bottom); - } - - public void draw(Canvas canvas) { - mDrawable.draw(canvas); - } - - public void setImageResource(Context context, int resId) { - Drawable d = context.getResources().getDrawable(resId).mutate(); - d.setBounds(mDrawable.getBounds()); - mDrawable = d; - setAlpha(mAlpha); - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java deleted file mode 100644 index ce8ca00..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java +++ /dev/null @@ -1,825 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.RectF; -import android.os.Handler; -import android.os.Message; -import android.view.MotionEvent; -import android.view.ViewConfiguration; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.LinearInterpolator; -import android.view.animation.Transformation; -import com.android.messaging.R; - -import java.util.ArrayList; -import java.util.List; - -public class PieRenderer extends OverlayRenderer - implements FocusIndicator { - // Sometimes continuous autofocus starts and stops several times quickly. - // These states are used to make sure the animation is run for at least some - // time. - private volatile int mState; - private ScaleAnimation mAnimation = new ScaleAnimation(); - private static final int STATE_IDLE = 0; - private static final int STATE_FOCUSING = 1; - private static final int STATE_FINISHING = 2; - private static final int STATE_PIE = 8; - - private Runnable mDisappear = new Disappear(); - private Animation.AnimationListener mEndAction = new EndAction(); - private static final int SCALING_UP_TIME = 600; - private static final int SCALING_DOWN_TIME = 100; - private static final int DISAPPEAR_TIMEOUT = 200; - private static final int DIAL_HORIZONTAL = 157; - - private static final long PIE_FADE_IN_DURATION = 200; - private static final long PIE_XFADE_DURATION = 200; - private static final long PIE_SELECT_FADE_DURATION = 300; - - private static final int MSG_OPEN = 0; - private static final int MSG_CLOSE = 1; - private static final float PIE_SWEEP = (float) (Math.PI * 2 / 3); - // geometry - private Point mCenter; - private int mRadius; - private int mRadiusInc; - - // the detection if touch is inside a slice is offset - // inbounds by this amount to allow the selection to show before the - // finger covers it - private int mTouchOffset; - - private List mItems; - - private PieItem mOpenItem; - - private Paint mSelectedPaint; - private Paint mSubPaint; - - // touch handling - private PieItem mCurrentItem; - - private Paint mFocusPaint; - private int mSuccessColor; - private int mFailColor; - private int mCircleSize; - private int mFocusX; - private int mFocusY; - private int mCenterX; - private int mCenterY; - - private int mDialAngle; - private RectF mCircle; - private RectF mDial; - private Point mPoint1; - private Point mPoint2; - private int mStartAnimationAngle; - private boolean mFocused; - private int mInnerOffset; - private int mOuterStroke; - private int mInnerStroke; - private boolean mTapMode; - private boolean mBlockFocus; - private int mTouchSlopSquared; - private Point mDown; - private boolean mOpening; - private LinearAnimation mXFade; - private LinearAnimation mFadeIn; - private volatile boolean mFocusCancelled; - - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_OPEN: - if (mListener != null) { - mListener.onPieOpened(mCenter.x, mCenter.y); - } - break; - case MSG_CLOSE: - if (mListener != null) { - mListener.onPieClosed(); - } - break; - } - } - }; - - private PieListener mListener; - - public static interface PieListener { - public void onPieOpened(int centerX, int centerY); - public void onPieClosed(); - } - - public void setPieListener(PieListener pl) { - mListener = pl; - } - - public PieRenderer(Context context) { - init(context); - } - - private void init(Context ctx) { - setVisible(false); - mItems = new ArrayList(); - Resources res = ctx.getResources(); - mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start); - mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset); - mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment); - mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset); - mCenter = new Point(0, 0); - mSelectedPaint = new Paint(); - mSelectedPaint.setColor(Color.argb(255, 51, 181, 229)); - mSelectedPaint.setAntiAlias(true); - mSubPaint = new Paint(); - mSubPaint.setAntiAlias(true); - mSubPaint.setColor(Color.argb(200, 250, 230, 128)); - mFocusPaint = new Paint(); - mFocusPaint.setAntiAlias(true); - mFocusPaint.setColor(Color.WHITE); - mFocusPaint.setStyle(Paint.Style.STROKE); - mSuccessColor = Color.GREEN; - mFailColor = Color.RED; - mCircle = new RectF(); - mDial = new RectF(); - mPoint1 = new Point(); - mPoint2 = new Point(); - mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset); - mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke); - mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke); - mState = STATE_IDLE; - mBlockFocus = false; - mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop(); - mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared; - mDown = new Point(); - } - - public boolean showsItems() { - return mTapMode; - } - - public void addItem(PieItem item) { - // add the item to the pie itself - mItems.add(item); - } - - public void removeItem(PieItem item) { - mItems.remove(item); - } - - public void clearItems() { - mItems.clear(); - } - - public void showInCenter() { - if ((mState == STATE_PIE) && isVisible()) { - mTapMode = false; - show(false); - } else { - if (mState != STATE_IDLE) { - cancelFocus(); - } - mState = STATE_PIE; - setCenter(mCenterX, mCenterY); - mTapMode = true; - show(true); - } - } - - public void hide() { - show(false); - } - - /** - * guaranteed has center set - * @param show - */ - private void show(boolean show) { - if (show) { - mState = STATE_PIE; - // ensure clean state - mCurrentItem = null; - mOpenItem = null; - for (PieItem item : mItems) { - item.setSelected(false); - } - layoutPie(); - fadeIn(); - } else { - mState = STATE_IDLE; - mTapMode = false; - if (mXFade != null) { - mXFade.cancel(); - } - } - setVisible(show); - mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE); - } - - private void fadeIn() { - mFadeIn = new LinearAnimation(0, 1); - mFadeIn.setDuration(PIE_FADE_IN_DURATION); - mFadeIn.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mFadeIn = null; - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mFadeIn.startNow(); - mOverlay.startAnimation(mFadeIn); - } - - public void setCenter(int x, int y) { - mCenter.x = x; - mCenter.y = y; - // when using the pie menu, align the focus ring - alignFocus(x, y); - } - - private void layoutPie() { - int rgap = 2; - int inner = mRadius + rgap; - int outer = mRadius + mRadiusInc - rgap; - int gap = 1; - layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap); - } - - private void layoutItems(List items, float centerAngle, int inner, - int outer, int gap) { - float emptyangle = PIE_SWEEP / 16; - float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size(); - float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2; - // check if we have custom geometry - // first item we find triggers custom sweep for all - // this allows us to re-use the path - for (PieItem item : items) { - if (item.getCenter() >= 0) { - sweep = item.getSweep(); - break; - } - } - Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap, - outer, inner, mCenter); - for (PieItem item : items) { - // shared between items - item.setPath(path); - if (item.getCenter() >= 0) { - angle = item.getCenter(); - } - int w = item.getIntrinsicWidth(); - int h = item.getIntrinsicHeight(); - // move views to outer border - int r = inner + (outer - inner) * 2 / 3; - int x = (int) (r * Math.cos(angle)); - int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2; - x = mCenter.x + x - w / 2; - item.setBounds(x, y, x + w, y + h); - float itemstart = angle - sweep / 2; - item.setGeometry(itemstart, sweep, inner, outer); - if (item.hasItems()) { - layoutItems(item.getItems(), angle, inner, - outer + mRadiusInc / 2, gap); - } - angle += sweep; - } - } - - private Path makeSlice(float start, float end, int outer, int inner, Point center) { - RectF bb = - new RectF(center.x - outer, center.y - outer, center.x + outer, - center.y + outer); - RectF bbi = - new RectF(center.x - inner, center.y - inner, center.x + inner, - center.y + inner); - Path path = new Path(); - path.arcTo(bb, start, end - start, true); - path.arcTo(bbi, end, start - end); - path.close(); - return path; - } - - /** - * converts a - * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock) - * @return skia angle - */ - private float getDegrees(double angle) { - return (float) (360 - 180 * angle / Math.PI); - } - - private void startFadeOut() { - mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - deselect(); - show(false); - mOverlay.setAlpha(1); - super.onAnimationEnd(animation); - } - }).setDuration(PIE_SELECT_FADE_DURATION); - } - - @Override - public void onDraw(Canvas canvas) { - float alpha = 1; - if (mXFade != null) { - alpha = mXFade.getValue(); - } else if (mFadeIn != null) { - alpha = mFadeIn.getValue(); - } - int state = canvas.save(); - if (mFadeIn != null) { - float sf = 0.9f + alpha * 0.1f; - canvas.scale(sf, sf, mCenter.x, mCenter.y); - } - drawFocus(canvas); - if (mState == STATE_FINISHING) { - canvas.restoreToCount(state); - return; - } - if ((mOpenItem == null) || (mXFade != null)) { - // draw base menu - for (PieItem item : mItems) { - drawItem(canvas, item, alpha); - } - } - if (mOpenItem != null) { - for (PieItem inner : mOpenItem.getItems()) { - drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1); - } - } - canvas.restoreToCount(state); - } - - private void drawItem(Canvas canvas, PieItem item, float alpha) { - if (mState == STATE_PIE) { - if (item.getPath() != null) { - if (item.isSelected()) { - Paint p = mSelectedPaint; - int state = canvas.save(); - float r = getDegrees(item.getStartAngle()); - canvas.rotate(r, mCenter.x, mCenter.y); - canvas.drawPath(item.getPath(), p); - canvas.restoreToCount(state); - } - alpha = alpha * (item.isEnabled() ? 1 : 0.3f); - // draw the item view - item.setAlpha(alpha); - item.draw(canvas); - } - } - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - float x = evt.getX(); - float y = evt.getY(); - int action = evt.getActionMasked(); - PointF polar = getPolar(x, y, !(mTapMode)); - if (MotionEvent.ACTION_DOWN == action) { - mDown.x = (int) evt.getX(); - mDown.y = (int) evt.getY(); - mOpening = false; - if (mTapMode) { - PieItem item = findItem(polar); - if ((item != null) && (mCurrentItem != item)) { - mState = STATE_PIE; - onEnter(item); - } - } else { - setCenter((int) x, (int) y); - show(true); - } - return true; - } else if (MotionEvent.ACTION_UP == action) { - if (isVisible()) { - PieItem item = mCurrentItem; - if (mTapMode) { - item = findItem(polar); - if (item != null && mOpening) { - mOpening = false; - return true; - } - } - if (item == null) { - mTapMode = false; - show(false); - } else if (!mOpening - && !item.hasItems()) { - item.performClick(); - startFadeOut(); - mTapMode = false; - } - return true; - } - } else if (MotionEvent.ACTION_CANCEL == action) { - if (isVisible() || mTapMode) { - show(false); - } - deselect(); - return false; - } else if (MotionEvent.ACTION_MOVE == action) { - if (polar.y < mRadius) { - if (mOpenItem != null) { - mOpenItem = null; - } else { - deselect(); - } - return false; - } - PieItem item = findItem(polar); - boolean moved = hasMoved(evt); - if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) { - // only select if we didn't just open or have moved past slop - mOpening = false; - if (moved) { - // switch back to swipe mode - mTapMode = false; - } - onEnter(item); - } - } - return false; - } - - private boolean hasMoved(MotionEvent e) { - return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x) - + (e.getY() - mDown.y) * (e.getY() - mDown.y); - } - - /** - * enter a slice for a view - * updates model only - * @param item - */ - private void onEnter(PieItem item) { - if (mCurrentItem != null) { - mCurrentItem.setSelected(false); - } - if (item != null && item.isEnabled()) { - item.setSelected(true); - mCurrentItem = item; - if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) { - openCurrentItem(); - } - } else { - mCurrentItem = null; - } - } - - private void deselect() { - if (mCurrentItem != null) { - mCurrentItem.setSelected(false); - } - if (mOpenItem != null) { - mOpenItem = null; - } - mCurrentItem = null; - } - - private void openCurrentItem() { - if ((mCurrentItem != null) && mCurrentItem.hasItems()) { - mCurrentItem.setSelected(false); - mOpenItem = mCurrentItem; - mOpening = true; - mXFade = new LinearAnimation(1, 0); - mXFade.setDuration(PIE_XFADE_DURATION); - mXFade.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mXFade = null; - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mXFade.startNow(); - mOverlay.startAnimation(mXFade); - } - } - - private PointF getPolar(float x, float y, boolean useOffset) { - PointF res = new PointF(); - // get angle and radius from x/y - res.x = (float) Math.PI / 2; - x = x - mCenter.x; - y = mCenter.y - y; - res.y = (float) Math.sqrt(x * x + y * y); - if (x != 0) { - res.x = (float) Math.atan2(y, x); - if (res.x < 0) { - res.x = (float) (2 * Math.PI + res.x); - } - } - res.y = res.y + (useOffset ? mTouchOffset : 0); - return res; - } - - /** - * @param polar x: angle, y: dist - * @return the item at angle/dist or null - */ - private PieItem findItem(PointF polar) { - // find the matching item: - List items = (mOpenItem != null) ? mOpenItem.getItems() : mItems; - for (PieItem item : items) { - if (inside(polar, item)) { - return item; - } - } - return null; - } - - private boolean inside(PointF polar, PieItem item) { - return (item.getInnerRadius() < polar.y) - && (item.getStartAngle() < polar.x) - && (item.getStartAngle() + item.getSweep() > polar.x) - && (!mTapMode || (item.getOuterRadius() > polar.y)); - } - - @Override - public boolean handlesTouch() { - return true; - } - - // focus specific code - - public void setBlockFocus(boolean blocked) { - mBlockFocus = blocked; - if (blocked) { - clear(); - } - } - - public void setFocus(int x, int y) { - mFocusX = x; - mFocusY = y; - setCircle(mFocusX, mFocusY); - } - - public void alignFocus(int x, int y) { - mOverlay.removeCallbacks(mDisappear); - mAnimation.cancel(); - mAnimation.reset(); - mFocusX = x; - mFocusY = y; - mDialAngle = DIAL_HORIZONTAL; - setCircle(x, y); - mFocused = false; - } - - public int getSize() { - return 2 * mCircleSize; - } - - private int getRandomRange() { - return (int) (-60 + 120 * Math.random()); - } - - @Override - public void layout(int l, int t, int r, int b) { - super.layout(l, t, r, b); - mCenterX = (r - l) / 2; - mCenterY = (b - t) / 2; - mFocusX = mCenterX; - mFocusY = mCenterY; - setCircle(mFocusX, mFocusY); - if (isVisible() && mState == STATE_PIE) { - setCenter(mCenterX, mCenterY); - layoutPie(); - } - } - - private void setCircle(int cx, int cy) { - mCircle.set(cx - mCircleSize, cy - mCircleSize, - cx + mCircleSize, cy + mCircleSize); - mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset, - cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset); - } - - public void drawFocus(Canvas canvas) { - if (mBlockFocus) { - return; - } - mFocusPaint.setStrokeWidth(mOuterStroke); - canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint); - if (mState == STATE_PIE) { - return; - } - int color = mFocusPaint.getColor(); - if (mState == STATE_FINISHING) { - mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor); - } - mFocusPaint.setStrokeWidth(mInnerStroke); - drawLine(canvas, mDialAngle, mFocusPaint); - drawLine(canvas, mDialAngle + 45, mFocusPaint); - drawLine(canvas, mDialAngle + 180, mFocusPaint); - drawLine(canvas, mDialAngle + 225, mFocusPaint); - canvas.save(); - // rotate the arc instead of its offset to better use framework's shape caching - canvas.rotate(mDialAngle, mFocusX, mFocusY); - canvas.drawArc(mDial, 0, 45, false, mFocusPaint); - canvas.drawArc(mDial, 180, 45, false, mFocusPaint); - canvas.restore(); - mFocusPaint.setColor(color); - } - - private void drawLine(Canvas canvas, int angle, Paint p) { - convertCart(angle, mCircleSize - mInnerOffset, mPoint1); - convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2); - canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY, - mPoint2.x + mFocusX, mPoint2.y + mFocusY, p); - } - - private static void convertCart(int angle, int radius, Point out) { - double a = 2 * Math.PI * (angle % 360) / 360; - out.x = (int) (radius * Math.cos(a) + 0.5); - out.y = (int) (radius * Math.sin(a) + 0.5); - } - - @Override - public void showStart() { - if (mState == STATE_PIE) { - return; - } - cancelFocus(); - mStartAnimationAngle = 67; - int range = getRandomRange(); - startAnimation(SCALING_UP_TIME, - false, mStartAnimationAngle, mStartAnimationAngle + range); - mState = STATE_FOCUSING; - } - - @Override - public void showSuccess(boolean timeout) { - if (mState == STATE_FOCUSING) { - startAnimation(SCALING_DOWN_TIME, - timeout, mStartAnimationAngle); - mState = STATE_FINISHING; - mFocused = true; - } - } - - @Override - public void showFail(boolean timeout) { - if (mState == STATE_FOCUSING) { - startAnimation(SCALING_DOWN_TIME, - timeout, mStartAnimationAngle); - mState = STATE_FINISHING; - mFocused = false; - } - } - - private void cancelFocus() { - mFocusCancelled = true; - mOverlay.removeCallbacks(mDisappear); - if (mAnimation != null) { - mAnimation.cancel(); - } - mFocusCancelled = false; - mFocused = false; - mState = STATE_IDLE; - } - - @Override - public void clear() { - if (mState == STATE_PIE) { - return; - } - cancelFocus(); - mOverlay.post(mDisappear); - } - - private void startAnimation(long duration, boolean timeout, - float toScale) { - startAnimation(duration, timeout, mDialAngle, - toScale); - } - - private void startAnimation(long duration, boolean timeout, - float fromScale, float toScale) { - setVisible(true); - mAnimation.reset(); - mAnimation.setDuration(duration); - mAnimation.setScale(fromScale, toScale); - mAnimation.setAnimationListener(timeout ? mEndAction : null); - mOverlay.startAnimation(mAnimation); - update(); - } - - private class EndAction implements Animation.AnimationListener { - @Override - public void onAnimationEnd(Animation animation) { - // Keep the focus indicator for some time. - if (!mFocusCancelled) { - mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationStart(Animation animation) { - } - } - - private class Disappear implements Runnable { - @Override - public void run() { - if (mState == STATE_PIE) { - return; - } - setVisible(false); - mFocusX = mCenterX; - mFocusY = mCenterY; - mState = STATE_IDLE; - setCircle(mFocusX, mFocusY); - mFocused = false; - } - } - - private class ScaleAnimation extends Animation { - private float mFrom = 1f; - private float mTo = 1f; - - public ScaleAnimation() { - setFillAfter(true); - } - - public void setScale(float from, float to) { - mFrom = from; - mTo = to; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - mDialAngle = (int) (mFrom + (mTo - mFrom) * interpolatedTime); - } - } - - - private class LinearAnimation extends Animation { - private float mFrom; - private float mTo; - private float mValue; - - public LinearAnimation(float from, float to) { - setFillAfter(true); - setInterpolator(new LinearInterpolator()); - mFrom = from; - mTo = to; - } - - public float getValue() { - return mValue; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - mValue = (mFrom + (mTo - mFrom) * interpolatedTime); - } - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt deleted file mode 100644 index ed4e783..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The files in this package were copied from the android-4.4.4_r1 branch of ASOP from the folders -com/android/camera/ and com/android/camera/ui from files with the same name. Some modifications -have been made to remove unneeded features and adjust to our needs. \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java deleted file mode 100644 index 95cddc4..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker.camerafocus; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; - -import java.util.ArrayList; -import java.util.List; - -public class RenderOverlay extends FrameLayout { - - interface Renderer { - - public boolean handlesTouch(); - public boolean onTouchEvent(MotionEvent evt); - public void setOverlay(RenderOverlay overlay); - public void layout(int left, int top, int right, int bottom); - public void draw(Canvas canvas); - - } - - private RenderView mRenderView; - private List mClients; - - // reverse list of touch clients - private List mTouchClients; - private int[] mPosition = new int[2]; - - public RenderOverlay(Context context, AttributeSet attrs) { - super(context, attrs); - mRenderView = new RenderView(context); - addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - mClients = new ArrayList(10); - mTouchClients = new ArrayList(10); - setWillNotDraw(false); - - addRenderer(new PieRenderer(context)); - } - - public PieRenderer getPieRenderer() { - for (Renderer renderer : mClients) { - if (renderer instanceof PieRenderer) { - return (PieRenderer) renderer; - } - } - return null; - } - - public void addRenderer(Renderer renderer) { - mClients.add(renderer); - renderer.setOverlay(this); - if (renderer.handlesTouch()) { - mTouchClients.add(0, renderer); - } - renderer.layout(getLeft(), getTop(), getRight(), getBottom()); - } - - public void addRenderer(int pos, Renderer renderer) { - mClients.add(pos, renderer); - renderer.setOverlay(this); - renderer.layout(getLeft(), getTop(), getRight(), getBottom()); - } - - public void remove(Renderer renderer) { - mClients.remove(renderer); - renderer.setOverlay(null); - } - - public int getClientSize() { - return mClients.size(); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent m) { - return false; - } - - public boolean directDispatchTouch(MotionEvent m, Renderer target) { - mRenderView.setTouchTarget(target); - boolean res = super.dispatchTouchEvent(m); - mRenderView.setTouchTarget(null); - return res; - } - - private void adjustPosition() { - getLocationInWindow(mPosition); - } - - public int getWindowPositionX() { - return mPosition[0]; - } - - public int getWindowPositionY() { - return mPosition[1]; - } - - public void update() { - mRenderView.invalidate(); - } - - private class RenderView extends View { - - private Renderer mTouchTarget; - - public RenderView(Context context) { - super(context); - setWillNotDraw(false); - } - - public void setTouchTarget(Renderer target) { - mTouchTarget = target; - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - if (mTouchTarget != null) { - return mTouchTarget.onTouchEvent(evt); - } - if (mTouchClients != null) { - boolean res = false; - for (Renderer client : mTouchClients) { - res |= client.onTouchEvent(evt); - } - return res; - } - return false; - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - adjustPosition(); - super.onLayout(changed, left, top, right, bottom); - if (mClients == null) { - return; - } - for (Renderer renderer : mClients) { - renderer.layout(left, top, right, bottom); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - if (mClients == null) { - return; - } - boolean redraw = false; - for (Renderer renderer : mClients) { - renderer.draw(canvas); - redraw = redraw || ((OverlayRenderer) renderer).isVisible(); - } - if (redraw) { - invalidate(); - } - } - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java deleted file mode 100644 index d139a38..0000000 --- a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.photoviewer; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.support.rastermill.FrameSequenceDrawable; -import android.support.v4.content.AsyncTaskLoader; - -import com.android.ex.photo.PhotoViewController; -import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface; -import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.util.ImageUtils; - -/** - * Loader for the bitmap of a photo. - */ -public class BuglePhotoBitmapLoader extends AsyncTaskLoader - implements PhotoBitmapLoaderInterface { - private String mPhotoUri; - private ImageResource mImageResource; - // The drawable that is currently "in use" and being presented to the user. This drawable - // should never exist without the image resource backing it. - private Drawable mDrawable; - - public BuglePhotoBitmapLoader(Context context, String photoUri) { - super(context); - mPhotoUri = photoUri; - } - - @Override - public void setPhotoUri(String photoUri) { - mPhotoUri = photoUri; - } - - @Override - public BitmapResult loadInBackground() { - final BitmapResult result = new BitmapResult(); - final Context context = getContext(); - if (context != null && mPhotoUri != null) { - final ImageRequestDescriptor descriptor = - new UriImageRequestDescriptor(Uri.parse(mPhotoUri), - PhotoViewController.sMaxPhotoSize, PhotoViewController.sMaxPhotoSize, - true /* allowCompression */, false /* isStatic */, - false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); - final MediaRequest imageRequest = - descriptor.buildSyncMediaRequest(context); - final ImageResource imageResource = - MediaResourceManager.get().requestMediaResourceSync(imageRequest); - if (imageResource != null) { - setImageResource(imageResource); - result.status = BitmapResult.STATUS_SUCCESS; - result.drawable = mImageResource.getDrawable(context.getResources()); - } else { - releaseImageResource(); - result.status = BitmapResult.STATUS_EXCEPTION; - } - } else { - result.status = BitmapResult.STATUS_EXCEPTION; - } - return result; - } - - /** - * Called when there is new data to deliver to the client. The super class will take care of - * delivering it; the implementation here just adds a little more logic. - */ - @Override - public void deliverResult(BitmapResult result) { - final Drawable drawable = result != null ? result.drawable : null; - if (isReset()) { - // An async query came in while the loader is stopped. We don't need the result. - releaseDrawable(drawable); - return; - } - - // We are now going to display this drawable so set to mDrawable - mDrawable = drawable; - - if (isStarted()) { - // If the Loader is currently started, we can immediately deliver its results. - super.deliverResult(result); - } - } - - /** - * Handles a request to start the Loader. - */ - @Override - protected void onStartLoading() { - if (mDrawable != null) { - // If we currently have a result available, deliver it - // immediately. - final BitmapResult result = new BitmapResult(); - result.status = BitmapResult.STATUS_SUCCESS; - result.drawable = mDrawable; - deliverResult(result); - } - - if (takeContentChanged() || (mImageResource == null)) { - // If the data has changed since the last time it was loaded - // or is not currently available, start a load. - forceLoad(); - } - } - - /** - * Handles a request to stop the Loader. - */ - @Override - protected void onStopLoading() { - // Attempt to cancel the current load task if possible. - cancelLoad(); - } - - /** - * Handles a request to cancel a load. - */ - @Override - public void onCanceled(BitmapResult result) { - super.onCanceled(result); - - // At this point we can release the resources associated with 'drawable' if needed. - if (result != null) { - releaseDrawable(result.drawable); - } - } - - /** - * Handles a request to completely reset the Loader. - */ - @Override - protected void onReset() { - super.onReset(); - - // Ensure the loader is stopped - onStopLoading(); - - releaseImageResource(); - } - - private void releaseDrawable(Drawable drawable) { - if (drawable != null && drawable instanceof FrameSequenceDrawable - && !((FrameSequenceDrawable) drawable).isDestroyed()) { - ((FrameSequenceDrawable) drawable).destroy(); - } - - } - - private void setImageResource(final ImageResource resource) { - if (mImageResource != resource) { - // Clear out any information for what is currently used - releaseImageResource(); - mImageResource = resource; - // No need to add ref since a ref is already reserved as a result of - // requestMediaResourceSync. - } - } - - private void releaseImageResource() { - // If we are getting rid of the imageResource backing the drawable, we must also - // destroy the drawable before releasing it. - releaseDrawable(mDrawable); - mDrawable = null; - - if (mImageResource != null) { - mImageResource.release(); - } - mImageResource = null; - } -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java deleted file mode 100644 index 52498c7..0000000 --- a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.photoviewer; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.support.v4.app.FragmentManager; - -import com.android.ex.photo.adapters.PhotoPagerAdapter; -import com.android.ex.photo.fragments.PhotoViewFragment; - -public class BuglePhotoPageAdapter extends PhotoPagerAdapter { - - public BuglePhotoPageAdapter(Context context, FragmentManager fm, Cursor c, float maxScale, - boolean thumbsFullScreen) { - super(context, fm, c, maxScale, thumbsFullScreen); - } - - @Override - protected PhotoViewFragment createPhotoViewFragment(Intent intent, int position, - boolean onlyShowSpinner) { - return BuglePhotoViewFragment.newInstance(intent, position, onlyShowSpinner); - } -} diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java deleted file mode 100644 index 1924a96..0000000 --- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.photoviewer; - -import com.android.ex.photo.PhotoViewActivity; -import com.android.ex.photo.PhotoViewController; - -/** - * Activity to display the conversation images in full-screen. Most of the customization is in - * {@link BuglePhotoViewController}. - */ -public class BuglePhotoViewActivity extends PhotoViewActivity { - @Override - public PhotoViewController createController() { - return new BuglePhotoViewController(this); - } -} diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java deleted file mode 100644 index eb39886..0000000 --- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.photoviewer; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ShareActionProvider; -import android.widget.Toast; - -import com.android.ex.photo.PhotoViewController; -import com.android.ex.photo.adapters.PhotoPagerAdapter; -import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult; -import com.android.messaging.R; -import com.android.messaging.datamodel.ConversationImagePartsView.PhotoViewQuery; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.ui.conversation.ConversationFragment; -import com.android.messaging.util.Dates; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; - -/** - * Customizations for the photoviewer to display conversation images in full screen. - */ -public class BuglePhotoViewController extends PhotoViewController { - private ShareActionProvider mShareActionProvider; - private MenuItem mShareItem; - private MenuItem mSaveItem; - - public BuglePhotoViewController(final ActivityInterface activity) { - super(activity); - } - - @Override - public Loader onCreateBitmapLoader( - final int id, final Bundle args, final String uri) { - switch (id) { - case BITMAP_LOADER_AVATAR: - case BITMAP_LOADER_THUMBNAIL: - case BITMAP_LOADER_PHOTO: - return new BuglePhotoBitmapLoader(getActivity().getContext(), uri); - default: - LogUtil.e(LogUtil.BUGLE_TAG, - "Photoviewer unable to open bitmap loader with unknown id: " + id); - return null; - } - } - - @Override - public void updateActionBar() { - final Cursor cursor = getCursorAtProperPosition(); - - if (mSaveItem == null || cursor == null) { - // Load not finished, called from framework code before ready - return; - } - // Show the name as the title - mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_SENDER_FULL_NAME); - if (TextUtils.isEmpty(mActionBarTitle)) { - // If the name is not known, fall back to the phone number - mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_DISPLAY_DESTINATION); - } - - // Show the timestamp as the subtitle - final long receivedTimestamp = cursor.getLong(PhotoViewQuery.INDEX_RECEIVED_TIMESTAMP); - mActionBarSubtitle = Dates.getMessageTimeString(receivedTimestamp).toString(); - - setActionBarTitles(getActivity().getActionBarInterface()); - mSaveItem.setVisible(!isTempFile()); - - updateShareActionProvider(); - } - - private void updateShareActionProvider() { - final PhotoPagerAdapter adapter = getAdapter(); - final Cursor cursor = getCursorAtProperPosition(); - if (mShareActionProvider == null || mShareItem == null || adapter == null || - cursor == null) { - // Not enough stuff loaded to update the share action - return; - } - final String photoUri = adapter.getPhotoUri(cursor); - if (isTempFile()) { - mShareItem.setVisible(false); - return; - } - final String contentType = adapter.getContentType(cursor); - - final Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.setType(contentType); - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(photoUri)); - mShareActionProvider.setShareIntent(shareIntent); - mShareItem.setVisible(true); - } - - /** - * Checks whether the current photo is a temp file. A temp file can be deleted at any time, so - * we need to disable share and save options because the file may no longer be there. - */ - private boolean isTempFile() { - final Cursor cursor = getCursorAtProperPosition(); - final Uri photoUri = Uri.parse(getAdapter().getPhotoUri(cursor)); - return MediaScratchFileProvider.isMediaScratchSpaceUri(photoUri); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - ((Activity) getActivity()).getMenuInflater().inflate(R.menu.photo_view_menu, menu); - - // Get the ShareActionProvider - mShareItem = menu.findItem(R.id.action_share); - mShareActionProvider = (ShareActionProvider) mShareItem.getActionProvider(); - updateShareActionProvider(); - - mSaveItem = menu.findItem(R.id.action_save); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(final Menu menu) { - return !mIsEmpty; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == R.id.action_save) { - if (OsUtil.hasStoragePermission()) { - final PhotoPagerAdapter adapter = getAdapter(); - final Cursor cursor = getCursorAtProperPosition(); - if (cursor == null) { - final Context context = getActivity().getContext(); - final String error = context.getResources().getQuantityString( - R.plurals.attachment_save_error, 1, 1); - Toast.makeText(context, error, Toast.LENGTH_SHORT).show(); - return true; - } - final String photoUri = adapter.getPhotoUri(cursor); - new ConversationFragment.SaveAttachmentTask(((Activity) getActivity()), - Uri.parse(photoUri), adapter.getContentType(cursor)).executeOnThreadPool(); - } else { - ((Activity)getActivity()).requestPermissions( - new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); - } - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - @Override - public PhotoPagerAdapter createPhotoPagerAdapter(final Context context, - final FragmentManager fm, final Cursor c, final float maxScale) { - return new BuglePhotoPageAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen); - } -} diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java deleted file mode 100644 index 698c510..0000000 --- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.photoviewer; - -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.support.rastermill.FrameSequenceDrawable; -import android.support.v4.content.Loader; - -import com.android.ex.photo.PhotoViewCallbacks; -import com.android.ex.photo.fragments.PhotoViewFragment; -import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult; - -public class BuglePhotoViewFragment extends PhotoViewFragment { - - /** Public no-arg constructor for allowing the framework to handle orientation changes */ - public BuglePhotoViewFragment() { - // Do nothing. - } - - public static PhotoViewFragment newInstance(Intent intent, int position, - boolean onlyShowSpinner) { - final PhotoViewFragment f = new BuglePhotoViewFragment(); - initializeArguments(intent, position, onlyShowSpinner, f); - return f; - } - - @Override - public void onLoadFinished(Loader loader, BitmapResult result) { - super.onLoadFinished(loader, result); - // Need to check for the first time when we load the photos - if (PhotoViewCallbacks.BITMAP_LOADER_PHOTO == loader.getId() - && result.status == BitmapResult.STATUS_SUCCESS - && mCallback.isFragmentActive(this)) { - startGif(); - } - } - - @Override - public void onResume() { - super.onResume(); - startGif(); - } - - @Override - public void onPause() { - stopGif(); - super.onPause(); - } - - @Override - public void onViewActivated() { - super.onViewActivated(); - startGif(); - } - - @Override - public void resetViews() { - super.resetViews(); - stopGif(); - } - - private void stopGif() { - final Drawable drawable = getDrawable(); - if (drawable != null && drawable instanceof FrameSequenceDrawable) { - ((FrameSequenceDrawable) drawable).stop(); - } - } - - private void startGif() { - final Drawable drawable = getDrawable(); - if (drawable != null && drawable instanceof FrameSequenceDrawable) { - ((FrameSequenceDrawable) drawable).start(); - } - } -} diff --git a/src/com/android/messaging/util/AccessibilityUtil.java b/src/com/android/messaging/util/AccessibilityUtil.java deleted file mode 100644 index f6c64a9..0000000 --- a/src/com/android/messaging/util/AccessibilityUtil.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.content.res.Resources; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.text.TextUtils; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; - -import com.android.messaging.Factory; -import com.android.messaging.R; - -import javax.annotation.Nullable; - -public class AccessibilityUtil { - public static String sContentDescriptionDivider; - - public static boolean isTouchExplorationEnabled(final Context context) { - final AccessibilityManager accessibilityManager = (AccessibilityManager) - context.getSystemService(Context.ACCESSIBILITY_SERVICE); - return accessibilityManager.isTouchExplorationEnabled(); - } - - public static StringBuilder appendContentDescription(final Context context, - final StringBuilder contentDescription, final String val) { - if (sContentDescriptionDivider == null) { - sContentDescriptionDivider = - context.getResources().getString(R.string.enumeration_comma); - } - if (contentDescription.length() != 0) { - contentDescription.append(sContentDescriptionDivider); - } - contentDescription.append(val); - return contentDescription; - } - - public static void announceForAccessibilityCompat( - final View view, @Nullable final AccessibilityManager accessibilityManager, - final int textResourceId) { - final String text = Factory.get().getApplicationContext().getResources().getString( - textResourceId); - announceForAccessibilityCompat(view, accessibilityManager, text); - } - - public static void announceForAccessibilityCompat( - final View view, @Nullable AccessibilityManager accessibilityManager, - final CharSequence text) { - final Context context = view.getContext().getApplicationContext(); - if (accessibilityManager == null) { - accessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); - } - - if (!accessibilityManager.isEnabled()) { - return; - } - - // Jelly Bean added support for speaking text verbatim - final int eventType = OsUtil.isAtLeastJB() ? AccessibilityEvent.TYPE_ANNOUNCEMENT - : AccessibilityEvent.TYPE_VIEW_FOCUSED; - - // Construct an accessibility event with the minimum recommended - // attributes. An event without a class name or package may be dropped. - final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.getText().add(text); - event.setEnabled(view.isEnabled()); - event.setClassName(view.getClass().getName()); - event.setPackageName(context.getPackageName()); - - // JellyBean MR1 requires a source view to set the window ID. - final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event); - record.setSource(view); - - // Sends the event directly through the accessibility manager. If we only supported SDK 14+ - // we could have done: - // getParent().requestSendAccessibilityEvent(this, event); - accessibilityManager.sendAccessibilityEvent(event); - } - - /** - * Check to see if the current layout is Right-to-Left. This check is only supported for - * API 17+. - * For earlier versions, this method will just return false. - * @return boolean Boolean indicating whether the currently locale is RTL. - */ - public static boolean isLayoutRtl(final View view) { - if (OsUtil.isAtLeastJB_MR1()) { - return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection(); - } else { - return false; - } - } - - public static String getVocalizedPhoneNumber(final Resources res, final String phoneNumber) { - if (TextUtils.isEmpty(phoneNumber)) { - return ""; - } - final StringBuilder vocalizedPhoneNumber = new StringBuilder(); - for (final char c : phoneNumber.toCharArray()) { - getVocalizedNumber(res, c, vocalizedPhoneNumber); - } - return vocalizedPhoneNumber.toString(); - } - - public static void getVocalizedNumber(final Resources res, final char c, - final StringBuilder builder) { - switch (c) { - case '0': - builder.append(res.getString(R.string.content_description_for_number_zero)); - builder.append(" "); - return; - case '1': - builder.append(res.getString(R.string.content_description_for_number_one)); - builder.append(" "); - return; - case '2': - builder.append(res.getString(R.string.content_description_for_number_two)); - builder.append(" "); - return; - case '3': - builder.append(res.getString(R.string.content_description_for_number_three)); - builder.append(" "); - return; - case '4': - builder.append(res.getString(R.string.content_description_for_number_four)); - builder.append(" "); - return; - case '5': - builder.append(res.getString(R.string.content_description_for_number_five)); - builder.append(" "); - return; - case '6': - builder.append(res.getString(R.string.content_description_for_number_six)); - builder.append(" "); - return; - case '7': - builder.append(res.getString(R.string.content_description_for_number_seven)); - builder.append(" "); - return; - case '8': - builder.append(res.getString(R.string.content_description_for_number_eight)); - builder.append(" "); - return; - case '9': - builder.append(res.getString(R.string.content_description_for_number_nine)); - builder.append(" "); - return; - default: - builder.append(c); - return; - } - } -} diff --git a/src/com/android/messaging/util/Assert.java b/src/com/android/messaging/util/Assert.java deleted file mode 100644 index 437965c..0000000 --- a/src/com/android/messaging/util/Assert.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.os.Looper; - -import java.util.Arrays; - -public final class Assert { - public static @interface RunsOnMainThread {} - public static @interface DoesNotRunOnMainThread {} - public static @interface RunsOnAnyThread {} - - private static final String TEST_THREAD_SUBSTRING = "test"; - - private static boolean sIsEngBuild; - private static boolean sShouldCrash; - - // Private constructor so no one creates this class. - private Assert() { - } - - // The proguard rules will strip this method out on user/userdebug builds. - // If you change the method signature you MUST edit proguard-release.flags. - private static void setIfEngBuild() { - sShouldCrash = sIsEngBuild = true; - } - - private static void refreshGservices(final BugleGservices gservices) { - sShouldCrash = sIsEngBuild; - if (!sShouldCrash) { - sShouldCrash = gservices.getBoolean( - BugleGservicesKeys.ASSERTS_FATAL, - BugleGservicesKeys.ASSERTS_FATAL_DEFAULT); - } - } - - // Static initializer block to find out if we're running an eng or - // release build. - static { - setIfEngBuild(); - } - - // This is called from FactoryImpl once the Gservices class is initialized. - public static void initializeGservices (final BugleGservices gservices) { - gservices.registerForChanges(new Runnable() { - @Override - public void run() { - refreshGservices(gservices); - } - }); - refreshGservices(gservices); - } - - /** - * Halt execution if this is not an eng build. - *

Intended for use in code paths that should be run only for tests and never on - * a real build. - *

Note that this will crash on a user build even though asserts don't normally - * crash on a user build. - */ - public static void isEngBuild() { - isTrueReleaseCheck(sIsEngBuild); - } - - /** - * Halt execution if this isn't the case. - */ - public static void isTrue(final boolean condition) { - if (!condition) { - fail("Expected condition to be true", false); - } - } - - /** - * Halt execution if this isn't the case. - */ - public static void isFalse(final boolean condition) { - if (condition) { - fail("Expected condition to be false", false); - } - } - - /** - * Halt execution even in release builds if this isn't the case. - */ - public static void isTrueReleaseCheck(final boolean condition) { - if (!condition) { - fail("Expected condition to be true", true); - } - } - - public static void equals(final int expected, final int actual) { - if (expected != actual) { - fail("Expected " + expected + " but got " + actual, false); - } - } - - public static void equals(final long expected, final long actual) { - if (expected != actual) { - fail("Expected " + expected + " but got " + actual, false); - } - } - - public static void equals(final Object expected, final Object actual) { - if (expected != actual - && (expected == null || actual == null || !expected.equals(actual))) { - fail("Expected " + expected + " but got " + actual, false); - } - } - - public static void oneOf(final int actual, final int ...expected) { - for (int value : expected) { - if (actual == value) { - return; - } - } - fail("Expected value to be one of " + Arrays.toString(expected) + " but was " + actual); - } - - public static void inRange( - final int val, final int rangeMinInclusive, final int rangeMaxInclusive) { - if (val < rangeMinInclusive || val > rangeMaxInclusive) { - fail("Expected value in range [" + rangeMinInclusive + ", " + - rangeMaxInclusive + "], but was " + val, false); - } - } - - public static void inRange( - final long val, final long rangeMinInclusive, final long rangeMaxInclusive) { - if (val < rangeMinInclusive || val > rangeMaxInclusive) { - fail("Expected value in range [" + rangeMinInclusive + ", " + - rangeMaxInclusive + "], but was " + val, false); - } - } - - public static void isMainThread() { - if (Looper.myLooper() != Looper.getMainLooper() - && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) { - fail("Expected to run on main thread", false); - } - } - - public static void isNotMainThread() { - if (Looper.myLooper() == Looper.getMainLooper() - && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) { - fail("Not expected to run on main thread", false); - } - } - - /** - * Halt execution if the value passed in is not null - * @param obj The object to check - */ - public static void isNull(final Object obj) { - if (obj != null) { - fail("Expected object to be null", false); - } - } - - /** - * Halt execution if the value passed in is not null - * @param obj The object to check - * @param failureMessage message to print when halting execution - */ - public static void isNull(final Object obj, final String failureMessage) { - if (obj != null) { - fail(failureMessage, false); - } - } - - /** - * Halt execution if the value passed in is null - * @param obj The object to check - */ - public static void notNull(final Object obj) { - if (obj == null) { - fail("Expected value to be non-null", false); - } - } - - public static void fail(final String message) { - fail("Assert.fail() called: " + message, false); - } - - private static void fail(final String message, final boolean crashRelease) { - LogUtil.e(LogUtil.BUGLE_TAG, message); - if (crashRelease || sShouldCrash) { - throw new AssertionError(message); - } else { - // Find the method whose assertion failed. We're using a depth of 2, because all public - // Assert methods delegate to this one (see javadoc on getCaller() for details). - StackTraceElement caller = DebugUtils.getCaller(2); - if (caller != null) { - // This log message can be de-obfuscated by the Proguard retrace tool, just like a - // full stack trace from a crash. - LogUtil.e(LogUtil.BUGLE_TAG, "\tat " + caller.toString()); - } - } - } -} diff --git a/src/com/android/messaging/util/AvatarUriUtil.java b/src/com/android/messaging/util/AvatarUriUtil.java deleted file mode 100644 index df7d085..0000000 --- a/src/com/android/messaging/util/AvatarUriUtil.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.graphics.Color; -import android.net.Uri; -import android.net.Uri.Builder; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.messaging.datamodel.data.ParticipantData; - -import java.util.ArrayList; -import java.util.List; - -/** - * A helper utility for creating {@link android.net.Uri}s to describe what avatar to fetch or - * generate and will help verify and extract information from avatar {@link android.net.Uri}s. - * - * There are three types of avatar {@link android.net.Uri}. - * - * 1) Group Avatars - These are avatars which are used to represent a group conversation. Group - * avatars uris are basically multiple avatar uri which can be any of the below types but not - * another group avatar. The group avatars can hold anywhere from two to four avatars uri and can - * be in any of the following format - * messaging://avatar/g?p=&p= - * messaging://avatar/g?p=&p=&p= - * messaging://avatar/g?p=&p=&p=&p= - * - * 2) Local Resource - A local resource avatar is use when there is a profile photo for the - * participant. This can be any local resource. - * - * 3) Letter Tile - A letter tile is used when a participant has a name but no profile photo. A - * letter tile will contain the first code point of the participant's name and a background color - * based on the hash of the participant's full name. Letter tiles will be in the following format. - * messaging://avatar/l?n= - * - * 4) Default Avatars - These are avatars are used when the participant has no profile photo or - * name. In these cases we use the default person icon with a color background. The color - * background is based on a hash of the normalized phone number. - * - * 5) Default Background Avatars - This is a special case for Default Avatars where we use the - * default background color for the default avatar. - * - * 6) SIM Selector Avatars - These are avatars used in the SIM selector. This may either be a - * regular local resource avatar (2) or an avatar with a SIM identifier (i.e. SIM background with - * a letter or a slot number). - */ -public class AvatarUriUtil { - private static final int MAX_GROUP_PARTICIPANTS = 4; - - public static final String TYPE_GROUP_URI = "g"; - public static final String TYPE_LOCAL_RESOURCE_URI = "r"; - public static final String TYPE_LETTER_TILE_URI = "l"; - public static final String TYPE_DEFAULT_URI = "d"; - public static final String TYPE_DEFAULT_BACKGROUND_URI = "b"; - public static final String TYPE_SIM_SELECTOR_URI = "s"; - - private static final String SCHEME = "messaging"; - private static final String AUTHORITY = "avatar"; - private static final String PARAM_NAME = "n"; - private static final String PARAM_PRIMARY_URI = "m"; - private static final String PARAM_FALLBACK_URI = "f"; - private static final String PARAM_PARTICIPANT = "p"; - private static final String PARAM_IDENTIFIER = "i"; - private static final String PARAM_SIM_COLOR = "c"; - private static final String PARAM_SIM_SELECTED = "s"; - private static final String PARAM_SIM_INCOMING = "g"; - - public static final Uri DEFAULT_BACKGROUND_AVATAR = new Uri.Builder().scheme(SCHEME) - .authority(AUTHORITY).appendPath(TYPE_DEFAULT_BACKGROUND_URI).build(); - - private static final Uri BLANK_SIM_INDICATOR_INCOMING_URI = createSimIconUri("", - false /* selected */, Color.TRANSPARENT, true /* incoming */); - private static final Uri BLANK_SIM_INDICATOR_OUTGOING_URI = createSimIconUri("", - false /* selected */, Color.TRANSPARENT, false /* incoming */); - - /** - * Creates an avatar uri based on a list of ParticipantData. The list of participants may not - * be null or empty. Depending on the size of the list either a group avatar uri will be create - * or an individual's avatar will be created. This will never return a null uri. - */ - public static Uri createAvatarUri(@NonNull final List participants) { - Assert.notNull(participants); - Assert.isTrue(!participants.isEmpty()); - - if (participants.size() == 1) { - return createAvatarUri(participants.get(0)); - } - - final int numParticipants = Math.min(participants.size(), MAX_GROUP_PARTICIPANTS); - final ArrayList avatarUris = new ArrayList(numParticipants); - for (int i = 0; i < numParticipants; i++) { - avatarUris.add(createAvatarUri(participants.get(i))); - } - return AvatarUriUtil.joinAvatarUriToGroup(avatarUris); - } - - /** - * Joins together a list of valid avatar uri into a group uri.The list of participants may not - * be null or empty. If a lit of one is given then the first element will be return back - * instead of a group avatar uri. All uris in the list must be a valid avatar uri. This will - * never return a null uri. - */ - public static Uri joinAvatarUriToGroup(@NonNull final List avatarUris) { - Assert.notNull(avatarUris); - Assert.isTrue(!avatarUris.isEmpty()); - - if (avatarUris.size() == 1) { - final Uri firstAvatar = avatarUris.get(0); - Assert.isTrue(AvatarUriUtil.isAvatarUri(firstAvatar)); - return firstAvatar; - } - - final Builder builder = new Builder(); - builder.scheme(SCHEME); - builder.authority(AUTHORITY); - builder.appendPath(TYPE_GROUP_URI); - final int numParticipants = Math.min(avatarUris.size(), MAX_GROUP_PARTICIPANTS); - for (int i = 0; i < numParticipants; i++) { - final Uri uri = avatarUris.get(i); - Assert.notNull(uri); - Assert.isTrue(UriUtil.isLocalResourceUri(uri) || AvatarUriUtil.isAvatarUri(uri)); - builder.appendQueryParameter(PARAM_PARTICIPANT, uri.toString()); - } - return builder.build(); - } - - /** - * Creates an avatar uri based on ParticipantData which may not be null and expected to have - * profilePhotoUri, fullName and normalizedDestination populated. This will never return a null - * uri. - */ - public static Uri createAvatarUri(@NonNull final ParticipantData participant) { - Assert.notNull(participant); - final String photoUriString = participant.getProfilePhotoUri(); - final Uri profilePhotoUri = (photoUriString == null) ? null : Uri.parse(photoUriString); - final String name = participant.getFullName(); - final String destination = participant.getNormalizedDestination(); - final String contactLookupKey = participant.getLookupKey(); - return createAvatarUri(profilePhotoUri, name, destination, contactLookupKey); - } - - /** - * Creates an avatar uri based on a the input data. - */ - public static Uri createAvatarUri(final Uri profilePhotoUri, final CharSequence name, - final String defaultIdentifier, final String contactLookupKey) { - Uri generatedUri; - if (!TextUtils.isEmpty(name) && isValidFirstCharacter(name)) { - generatedUri = AvatarUriUtil.fromName(name, contactLookupKey); - } else { - final String identifier = TextUtils.isEmpty(contactLookupKey) - ? defaultIdentifier : contactLookupKey; - generatedUri = AvatarUriUtil.fromIdentifier(identifier); - } - - if (profilePhotoUri != null) { - if (UriUtil.isLocalResourceUri(profilePhotoUri)) { - return fromLocalResourceWithFallback(profilePhotoUri, generatedUri); - } else { - return profilePhotoUri; - } - } else { - return generatedUri; - } - } - - public static boolean isValidFirstCharacter(final CharSequence name) { - final char c = name.charAt(0); - return c != '+'; - } - - /** - * Creates an avatar URI used for the SIM selector. - * @param participantData the self participant data for an active SIM - * @param slotIdentifier when null, this will simply use a regular avatar; otherwise, the - * first letter of slotIdentifier will be used for the icon. - * @param selected is this the currently selected SIM? - * @param incoming is this for an incoming message or outgoing message? - */ - public static Uri createAvatarUri(@NonNull final ParticipantData participantData, - @Nullable final String slotIdentifier, final boolean selected, final boolean incoming) { - Assert.notNull(participantData); - Assert.isTrue(participantData.isActiveSubscription()); - Assert.isTrue(!TextUtils.isEmpty(slotIdentifier) || - !TextUtils.isEmpty(participantData.getProfilePhotoUri())); - if (TextUtils.isEmpty(slotIdentifier)) { - return createAvatarUri(participantData); - } - - return createSimIconUri(slotIdentifier, selected, participantData.getSubscriptionColor(), - incoming); - } - - private static Uri createSimIconUri(final String slotIdentifier, final boolean selected, - final int subColor, final boolean incoming) { - final Builder builder = new Builder(); - builder.scheme(SCHEME); - builder.authority(AUTHORITY); - builder.appendPath(TYPE_SIM_SELECTOR_URI); - builder.appendQueryParameter(PARAM_IDENTIFIER, slotIdentifier); - builder.appendQueryParameter(PARAM_SIM_COLOR, String.valueOf(subColor)); - builder.appendQueryParameter(PARAM_SIM_SELECTED, String.valueOf(selected)); - builder.appendQueryParameter(PARAM_SIM_INCOMING, String.valueOf(incoming)); - return builder.build(); - } - - public static Uri getBlankSimIndicatorUri(final boolean incoming) { - return incoming ? BLANK_SIM_INDICATOR_INCOMING_URI : BLANK_SIM_INDICATOR_OUTGOING_URI; - } - - /** - * Creates an avatar uri from the given local resource Uri, followed by a fallback Uri in case - * the local resource one could not be loaded. - */ - private static Uri fromLocalResourceWithFallback(@NonNull final Uri profilePhotoUri, - @NonNull Uri fallbackUri) { - Assert.notNull(profilePhotoUri); - Assert.notNull(fallbackUri); - final Builder builder = new Builder(); - builder.scheme(SCHEME); - builder.authority(AUTHORITY); - builder.appendPath(TYPE_LOCAL_RESOURCE_URI); - builder.appendQueryParameter(PARAM_PRIMARY_URI, profilePhotoUri.toString()); - builder.appendQueryParameter(PARAM_FALLBACK_URI, fallbackUri.toString()); - return builder.build(); - } - - private static Uri fromName(@NonNull final CharSequence name, final String contactLookupKey) { - Assert.notNull(name); - final Builder builder = new Builder(); - builder.scheme(SCHEME); - builder.authority(AUTHORITY); - builder.appendPath(TYPE_LETTER_TILE_URI); - final String nameString = String.valueOf(name); - builder.appendQueryParameter(PARAM_NAME, nameString); - final String identifier = - TextUtils.isEmpty(contactLookupKey) ? nameString : contactLookupKey; - builder.appendQueryParameter(PARAM_IDENTIFIER, identifier); - return builder.build(); - } - - private static Uri fromIdentifier(@NonNull final String identifier) { - final Builder builder = new Builder(); - builder.scheme(SCHEME); - builder.authority(AUTHORITY); - builder.appendPath(TYPE_DEFAULT_URI); - builder.appendQueryParameter(PARAM_IDENTIFIER, identifier); - return builder.build(); - } - - public static boolean isAvatarUri(@NonNull final Uri uri) { - Assert.notNull(uri); - return uri != null && TextUtils.equals(SCHEME, uri.getScheme()) && - TextUtils.equals(AUTHORITY, uri.getAuthority()); - } - - public static String getAvatarType(@NonNull final Uri uri) { - Assert.notNull(uri); - final List path = uri.getPathSegments(); - return path.isEmpty() ? null : path.get(0); - } - - public static String getIdentifier(@NonNull final Uri uri) { - Assert.notNull(uri); - return uri.getQueryParameter(PARAM_IDENTIFIER); - } - - public static String getName(@NonNull final Uri uri) { - Assert.notNull(uri); - return uri.getQueryParameter(PARAM_NAME); - } - - public static List getGroupParticipantUris(@NonNull final Uri uri) { - Assert.notNull(uri); - return uri.getQueryParameters(PARAM_PARTICIPANT); - } - - public static int getSimColor(@NonNull final Uri uri) { - Assert.notNull(uri); - return Integer.valueOf(uri.getQueryParameter(PARAM_SIM_COLOR)); - } - - public static boolean getSimSelected(@NonNull final Uri uri) { - Assert.notNull(uri); - return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_SELECTED)); - } - - public static boolean getSimIncoming(@NonNull final Uri uri) { - Assert.notNull(uri); - return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_INCOMING)); - } - - public static Uri getPrimaryUri(@NonNull final Uri uri) { - Assert.notNull(uri); - final String primaryUriString = uri.getQueryParameter(PARAM_PRIMARY_URI); - return primaryUriString == null ? null : Uri.parse(primaryUriString); - } - - public static Uri getFallbackUri(@NonNull final Uri uri) { - Assert.notNull(uri); - final String fallbackUriString = uri.getQueryParameter(PARAM_FALLBACK_URI); - return fallbackUriString == null ? null : Uri.parse(fallbackUriString); - } -} diff --git a/src/com/android/messaging/util/BugleActivityUtil.java b/src/com/android/messaging/util/BugleActivityUtil.java deleted file mode 100644 index 7f722fd..0000000 --- a/src/com/android/messaging/util/BugleActivityUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.UserManager; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.ui.conversation.ConversationActivity; -import com.android.messaging.ui.conversationlist.ConversationListActivity; - -/** - * Utility class including logic to verify requirements to run Bugle and other activity startup - * logic. Called from base Bugle activity classes. - */ -public class BugleActivityUtil { - - private static final int REQUEST_GOOGLE_PLAY_SERVICES = 0; - - /** - * Determine if the requirements for the app to run are met. Log any Activity startup - * analytics. - * @param context - * @param activity is used to launch an error Dialog if necessary - * @return true if resume should continue normally. Returns false if some requirements to run - * are not met. - */ - public static boolean onActivityResume(Context context, Activity activity) { - DataModel.get().onActivityResume(); - Factory.get().onActivityResume(); - - // Validate all requirements to run are met - return checkHasSmsPermissionsForUser(context, activity); - } - - /** - * Determine if the user doesn't have SMS permissions. This can happen if you are not the phone - * owner and the owner has disabled your SMS permissions. - * @param context is the Context used to resolve the user permissions - * @param activity is the Activity used to launch an error Dialog if necessary - * @return true if the user has SMS permissions, otherwise false. - */ - private static boolean checkHasSmsPermissionsForUser(Context context, Activity activity) { - if (!OsUtil.isAtLeastL()) { - // UserManager.DISALLOW_SMS added in L. No multiuser phones before this - return true; - } - UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS)) { - new AlertDialog.Builder(activity) - .setMessage(R.string.requires_sms_permissions_message) - .setCancelable(false) - .setNegativeButton(R.string.requires_sms_permissions_close_button, - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - final int button) { - System.exit(0); - } - }) - .show(); - return false; - } - return true; - } -} - diff --git a/src/com/android/messaging/util/BugleApplicationPrefs.java b/src/com/android/messaging/util/BugleApplicationPrefs.java deleted file mode 100644 index e9fceb4..0000000 --- a/src/com/android/messaging/util/BugleApplicationPrefs.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; - -/** - * Provides interface to access application-wide shared preferences. This includes both the user - * visible preferences (e.g. the general settings in the settings page), and internal preferences - * under {@link BuglePrefsKeys}. - */ -public class BugleApplicationPrefs extends BuglePrefsImpl { - public BugleApplicationPrefs(Context context) { - super(context); - } - - @Override - public String getSharedPreferencesName() { - return SHARED_PREFERENCES_NAME; - } - - @Override - protected void validateKey(String key) { - super.validateKey(key); - // Callers shouldn't try to access per-subscription preferences from this class - Assert.isFalse(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX)); - } - - @Override - public void onUpgrade(int oldVersion, int newVersion) { - } -} diff --git a/src/com/android/messaging/util/BugleGservices.java b/src/com/android/messaging/util/BugleGservices.java deleted file mode 100644 index 2e095de..0000000 --- a/src/com/android/messaging/util/BugleGservices.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import com.android.messaging.Factory; - -/** - * A thin wrapper for getting GServices value. During constructor time a one time background thread - * will cache all GServices key with the prefix of "bugle_". All get calls will wait for Gservices - * to finish caching the first time. In practice, the background thread will finish before any get - * request. - */ -public abstract class BugleGservices { - static final String BUGLE_GSERVICES_PREFIX = "bugle_"; - - public static BugleGservices get() { - return Factory.get().getBugleGservices(); - } - - public abstract void registerForChanges(final Runnable r); - - /** - * @param key The key to look up in GServices - * @param defaultValue The default value if value in GServices is null or if - * NumberFormatException is caught. - * @return The corresponding value, or the default value. - */ - public abstract long getLong(final String key, final long defaultValue); - - /** - * @param key The key to look up in GServices - * @param defaultValue The default value if value in GServices is null or if - * NumberFormatException is caught. - * @return The corresponding value, or the default value. - */ - public abstract int getInt(final String key, final int defaultValue); - - /** - * @param key The key to look up in GServices - * @param defaultValue The default value if value in GServices is null. - * @return The corresponding value, or the default value. - */ - public abstract boolean getBoolean(final String key, final boolean defaultValue); - - /** - * @param key The key to look up in GServices - * @param defaultValue The default value if value in GServices is null. - * @return The corresponding value, or the default value. - */ - public abstract String getString(final String key, final String defaultValue); - - /** - * @param key The key to look up in GServices - * @param defaultValue The default value if value in GServices is null. - * @return The corresponding value, or the default value. - */ - public abstract float getFloat(final String key, final float defaultValue); -} diff --git a/src/com/android/messaging/util/BugleGservicesImpl.java b/src/com/android/messaging/util/BugleGservicesImpl.java deleted file mode 100644 index 5ef0898..0000000 --- a/src/com/android/messaging/util/BugleGservicesImpl.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; - -/** - * A thin wrapper for getting GServices value. - */ -public class BugleGservicesImpl extends BugleGservices { - public BugleGservicesImpl(final Context context) { - } - - @Override - public void registerForChanges(final Runnable r) { - } - - /** - * Asserts that the key has the expected prefix. - */ - private void assertKeyAndWaitForGservices(final String key) { - Assert.isTrue(key.startsWith(BUGLE_GSERVICES_PREFIX)); - } - - @Override - public long getLong(final String key, final long defaultValue) { - assertKeyAndWaitForGservices(key); - return defaultValue; - } - - @Override - public int getInt(final String key, final int defaultValue) { - assertKeyAndWaitForGservices(key); - return defaultValue; - } - - @Override - public boolean getBoolean(final String key, final boolean defaultValue) { - assertKeyAndWaitForGservices(key); - return defaultValue; - } - - @Override - public String getString(final String key, final String defaultValue) { - assertKeyAndWaitForGservices(key); - return defaultValue; - } - - @Override - public float getFloat(final String key, final float defaultValue) { - assertKeyAndWaitForGservices(key); - return defaultValue; - } -} diff --git a/src/com/android/messaging/util/BugleGservicesKeys.java b/src/com/android/messaging/util/BugleGservicesKeys.java deleted file mode 100644 index f36dd7f..0000000 --- a/src/com/android/messaging/util/BugleGservicesKeys.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - - -/** - * List of gservices keys and default values which are in use. - */ -public final class BugleGservicesKeys { - private BugleGservicesKeys() {} // do not instantiate - - /** - * Whether to enable extra debugging features on the client. Default is - * {@value #ENABLE_DEBUGGING_FEATURES_DEFAULT}. - */ - public static final String ENABLE_DEBUGGING_FEATURES - = "bugle_debugging"; - public static final boolean ENABLE_DEBUGGING_FEATURES_DEFAULT - = false; - - /** - * Whether to enable saving extra logs. Default is {@value #ENABLE_LOG_SAVER_DEFAULT}. - */ - public static final String ENABLE_LOG_SAVER = "bugle_logsaver"; - public static final boolean ENABLE_LOG_SAVER_DEFAULT = false; - - /** - * Time in milliseconds of initial (attempt 1) resend backoff for failing messages - */ - public static final String INITIAL_MESSAGE_RESEND_DELAY_MS = "bugle_resend_delay_in_millis"; - public static final long INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT = 5 * 1000L; - - /** - * Time in milliseconds of max resend backoff for failing messages - */ - public static final String MAX_MESSAGE_RESEND_DELAY_MS = "bugle_max_resend_delay_in_millis"; - public static final long MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT = 2 * 60 * 60 * 1000L; - - /** - * Time in milliseconds of resend window for unsent messages - */ - public static final String MESSAGE_RESEND_TIMEOUT_MS = "bugle_resend_timeout_in_millis"; - public static final long MESSAGE_RESEND_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L; - - /** - * Time in milliseconds of download window for new mms notifications - */ - public static final String MESSAGE_DOWNLOAD_TIMEOUT_MS = "bugle_download_timeout_in_millis"; - public static final long MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L; - - /** - * Time in milliseconds for SMS send timeout - */ - public static final String SMS_SEND_TIMEOUT_IN_MILLIS = "bugle_sms_send_timeout"; - public static final long SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT = 5 * 60 * 1000L; - - /** - * Keys to control the SMS sync batch size. The batch size is defined by the number - * of messages that incur local database change, e.g. importing messages and - * deleting messages. - * - * 1. The minimum size for a batch and - * 2. The maximum size for a batch. - * The first batch uses the minimum size for probing. Set this to a small number for the - * first sync batch to make sure the user sees SMS showing up in conversations quickly - * Use these two settings to limit the number of messages to sync in each batch. - * The minimum is to make sure we always make progress during sync. The maximum is - * to limit the sync batch size within a reasonable range (needs to fit in an intent). - * 3. The time limit controls the limit of time duration of a sync batch. We can - * not control this directly due to the batching nature of sync. So this provides - * heuristics. We may sometime exceeds the limit if our calculation is off due to - * whatever reasons. Keeping this low ensures responsiveness of the application. - * 4. The limit on number of total messages to scan in one batch. - */ - public static final String SMS_SYNC_BATCH_SIZE_MIN = - "bugle_sms_sync_batch_size_min"; - public static final int SMS_SYNC_BATCH_SIZE_MIN_DEFAULT = 80; - public static final String SMS_SYNC_BATCH_SIZE_MAX = - "bugle_sms_sync_batch_size_max"; - public static final int SMS_SYNC_BATCH_SIZE_MAX_DEFAULT = 1000; - public static final String SMS_SYNC_BATCH_TIME_LIMIT_MILLIS = - "bugle_sms_sync_batch_time_limit"; - public static final long SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT = 400; - public static final String SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN = - "bugle_sms_sync_batch_max_messages_to_scan"; - public static final int SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT = - SMS_SYNC_BATCH_SIZE_MAX_DEFAULT * 4; - - /** - * Time in ms for sync to backoff from "now" to the latest message that will be sync'd. - * - * This controls the best case for how out of date the application will appear to be - * when bringing in changes made outside the application. It also represents a buffer - * to ensure that sync doesn't trigger based on changes made within the application. - */ - public static final String SMS_SYNC_BACKOFF_TIME_MILLIS = - "bugle_sms_sync_backoff_time"; - public static final long SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 5000L; - - /** - * Just in case if we fall into a loop of full sync -> still not synchronized -> full sync ... - * This forces a backoff time so that we at most do full sync once a while (an hour by default) - */ - public static final String SMS_FULL_SYNC_BACKOFF_TIME_MILLIS = - "bugle_sms_full_sync_backoff_time"; - public static final long SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 60 * 60 * 1000; - - /** - * Time duration to retain the most recent SMS messages for SMS storage purging - * - * Format: - * (w|m|y) - * Examples: - * "1y" -- a year - * "2w" -- two weeks - * "6m" -- six months - */ - public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION = - "bugle_sms_storage_purging_message_retaining_duration"; - public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT = "1m"; - - /** - * MMS UA profile url. - * - * This is used on all Android devices running Hangout, so cannot just host the profile of the - * latest and greatest phones. However, if we're on KitKat or below we can't get the phone's - * UA profile and thus we need to send them the default url. - */ - public static final String MMS_UA_PROFILE_URL = - "bugle_mms_uaprofurl"; - public static final String MMS_UA_PROFILE_URL_DEFAULT = - "http://www.gstatic.com/android/sms/mms_ua_profile.xml"; - - /** - * MMS apn mmsc - */ - public static final String MMS_MMSC = - "bugle_mms_mmsc"; - - /** - * MMS apn proxy ip address - */ - public static final String MMS_PROXY_ADDRESS = - "bugle_mms_proxy_address"; - - /** - * MMS apn proxy port - */ - public static final String MMS_PROXY_PORT = - "bugle_mms_proxy_port"; - - /** - * List of known SMS system messages that we will ignore (no deliver, no abort) so that the - * user doesn't see them and the appropriate app is able to handle them. We are delivering - * these as a \n delimited list of patterns, however we should eventually move to storing - * them with the per-carrier mms config xml file. - */ - public static final String SMS_IGNORE_MESSAGE_REGEX = - "bugle_sms_ignore_message_regex"; - public static final String SMS_IGNORE_MESSAGE_REGEX_DEFAULT = ""; - - /** - * When receiving or importing an mms, limit the length of text to this limit. Huge blocks - * of text can cause the app to hang/ANR/or crash in native text code.. - */ - public static final String MMS_TEXT_LIMIT = "bugle_mms_text_limit"; - public static final int MMS_TEXT_LIMIT_DEFAULT = 2000; - - /** - * Max number of attachments the user may add to a single message. - */ - public static final String MMS_ATTACHMENT_LIMIT = "bugle_mms_attachment_limit"; - public static final int MMS_ATTACHMENT_LIMIT_DEFAULT = 10; - - /** - * The max number of messages to show in a single conversation notification. We always show - * the most recent message. If this value is >1, we may also include prior messages as well. - */ - public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION = - "bugle_max_messages_in_conversation_notification"; - public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT = 7; - - /** - * Time (in seconds) between notification ringing for incoming messages of the same - * conversation. We won't ding more often than this value for messages coming in at a high rate. - */ - public static final String NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS - = "bugle_notification_time_between_rings_seconds"; - public static final int NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT = 10; - - /** - * The max number of messages to show in a single conversation notification, when a wearable - * device (i.e. smartwatch) is paired with the phone. Watches have a different UX model and - * less screen real estate, so we may want to optimize for that case. Note that if a wearable - * is paired, this value will apply to notifications as shown both on the watch and the phone. - */ - public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE = - "bugle_max_messages_in_conversation_notification_with_wearable"; - public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT = 1; - - /** - * Regular expression to match against query. If it matches then display - * the query plan for this query. - */ - public static final String EXPLAIN_QUERY_PLAN_REGEXP = "bugle_query_plan_regexp"; - - /** - * Whether asserts are fatal on user/userdebug builds. - * Default is {@value #ASSERTS_FATAL_DEFAULT}. - */ - public static final String ASSERTS_FATAL = "bugle_asserts_fatal"; - public static final boolean ASSERTS_FATAL_DEFAULT = false; - - /** - * Whether to use API for sending/downloading MMS (if present, true for L). - * Default is {@value #USE_MMS_API_IF_PRESENT_DEFAULT}. - */ - public static final String USE_MMS_API_IF_PRESENT = "bugle_use_mms_api"; - public static final boolean USE_MMS_API_IF_PRESENT_DEFAULT = true; - - /** - * Whether to always auto-complete email addresses for sending MMS. By default, Bugle starts - * to auto-complete after the user has typed the "@" character. - * Default is (@value ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT}. - */ - public static final String ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS = - "bugle_always_autocomplete_email_address"; - public static final boolean ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT = false; - - // We typically request an aspect ratio close the the screen size, but some cameras can be - // flaky and not work well in certain aspect ratios. This allows us to guide the CameraManager - // to pick a more reliable aspect ratio. The value is a float like 1.333f or 1.777f. There is - // no hard coded default because the default is the screen aspect ratio. - public static final String CAMERA_ASPECT_RATIO = "bugle_camera_aspect_ratio"; - - /** - * The recent time range within which we should check MMS WAP Push duplication - * If the value is 0, it signals that we should use old dedup algorithm for wap push - */ - public static final String MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS = - "bugle_mms_wap_push_dedup_time_limit_secs"; - public static final long MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT = 7 * 24 * 3600; // 7 days - - /** - * Whether to use persistent, on-disk LogSaver - */ - public static final String PERSISTENT_LOGSAVER = "bugle_persistent_logsaver"; - public static final boolean PERSISTENT_LOGSAVER_DEFAULT = false; - - /** - * For in-memory LogSaver, what's the size of memory buffer in number of records - */ - public static final String IN_MEMORY_LOGSAVER_RECORD_COUNT = - "bugle_in_memory_logsaver_record_count"; - public static final int IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT = 500; - - /** - * For on-disk LogSaver, what's the size of file rotation set - */ - public static final String PERSISTENT_LOGSAVER_ROTATION_SET_SIZE = - "bugle_persistent_logsaver_rotation_set_size"; - public static final int PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT = 8; - - /** - * For on-disk LogSaver, what's the byte limit of a single log file - */ - public static final String PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES = - "bugle_persistent_logsaver_file_limit"; - public static final int PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT = 256 * 1024; // 256KB - - /** - * We concatenate all text parts in an MMS to form the message text. This specifies - * the separator between the combinated text parts. Default is ' ' (space). - */ - public static final String MMS_TEXT_CONCAT_SEPARATOR = "bugle_mms_text_concat_separator"; - public static final String MMS_TEXT_CONCAT_SEPARATOR_DEFAULT = " "; - - /** - * Whether to enable transcoding GIFs. We sometimes need to compress GIFs to make them small - * enough to send via MMS (which often limits messages to 1 MB in size). - */ - public static final String ENABLE_GIF_TRANSCODING = "bugle_gif_transcoding"; - public static final boolean ENABLE_GIF_TRANSCODING_DEFAULT = true; -} diff --git a/src/com/android/messaging/util/BuglePrefs.java b/src/com/android/messaging/util/BuglePrefs.java deleted file mode 100644 index 74a0d46..0000000 --- a/src/com/android/messaging/util/BuglePrefs.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import com.android.messaging.Factory; - -/** - * Thin wrapper to get/set shared prefs values. - */ -public abstract class BuglePrefs { - /** - * Shared preferences name for preferences applicable to the entire app. - */ - public static final String SHARED_PREFERENCES_NAME = "bugle"; - - /** - * Shared preferences name for subscription-specific preferences. - * Note: for all subscription-specific preferences, please prefix the shared preference keys - * with "buglesub_", so that Bugle may perform runtime validations on preferences to make sure - * you don't accidentally write per-subscription settings into the general pref file, and vice - * versa. - */ - public static final String SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX = "buglesub_"; - - /** - * A placeholder base version for Bugle builds where no shared pref version was defined. - */ - public static final int NO_SHARED_PREFERENCES_VERSION = -1; - - /** - * Returns the shared preferences file name to use. - * Subclasses should override and return the shared preferences file. - */ - public abstract String getSharedPreferencesName(); - - - /** - * Handles pref version upgrade. - */ - public abstract void onUpgrade(final int oldVersion, final int newVersion); - - /** - * Gets the SharedPreferences accessor to the application-wide preferences. - */ - public static BuglePrefs getApplicationPrefs() { - return Factory.get().getApplicationPrefs(); - } - - /** - * Gets the SharedPreferences accessor to the subscription-specific preferences. - */ - public static BuglePrefs getSubscriptionPrefs(final int subId) { - return Factory.get().getSubscriptionPrefs(subId); - } - - /** - * @param key The key to look up in shared prefs - * @param defaultValue The default value if value in shared prefs is null or if - * NumberFormatException is caught. - * @return The corresponding value, or the default value. - */ - public abstract int getInt(final String key, final int defaultValue); - - /** - * @param key The key to look up in shared prefs - * @param defaultValue The default value if value in shared prefs is null or if - * NumberFormatException is caught. - * @return The corresponding value, or the default value. - */ - public abstract long getLong(final String key, final long defaultValue); - - /** - * @param key The key to look up in shared prefs - * @param defaultValue The default value if value in shared prefs is null. - * @return The corresponding value, or the default value. - */ - public abstract boolean getBoolean(final String key, final boolean defaultValue); - - /** - * @param key The key to look up in shared prefs - * @param defaultValue The default value if value in shared prefs is null. - * @return The corresponding value, or the default value. - */ - public abstract String getString(final String key, final String defaultValue); - - /** - * @param key The key to look up in shared prefs - * @return The corresponding value, or null if not found. - */ - public abstract byte[] getBytes(final String key); - - /** - * @param key The key to set in shared prefs - * @param value The value to assign to the key - */ - public abstract void putInt(final String key, final int value); - - /** - * @param key The key to set in shared prefs - * @param value The value to assign to the key - */ - public abstract void putLong(final String key, final long value); - - /** - * @param key The key to set in shared prefs - * @param value The value to assign to the key - */ - public abstract void putBoolean(final String key, final boolean value); - - /** - * @param key The key to set in shared prefs - * @param value The value to assign to the key - */ - public abstract void putString(final String key, final String value); - - /** - * @param key The key to set in shared prefs - * @param value The value to assign to the key - */ - public abstract void putBytes(final String key, final byte[] value); - - /** - * @param key The key to remove from shared prefs - */ - public abstract void remove(String key); -} diff --git a/src/com/android/messaging/util/BuglePrefsImpl.java b/src/com/android/messaging/util/BuglePrefsImpl.java deleted file mode 100644 index 7563040..0000000 --- a/src/com/android/messaging/util/BuglePrefsImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Base64; - -/** - * Thin wrapper to get/set shared prefs values. - */ -public abstract class BuglePrefsImpl extends BuglePrefs { - - private final Context mContext; - - public BuglePrefsImpl(final Context context) { - mContext = context; - } - - /** - * Validate the prefs key passed in. Subclasses should override this as needed to perform - * runtime checks (such as making sure per-subscription settings don't sneak into application- - * wide settings). - */ - protected void validateKey(String key) { - } - - @Override - public int getInt(final String key, final int defaultValue) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - return prefs.getInt(key, defaultValue); - } - - @Override - public long getLong(final String key, final long defaultValue) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - return prefs.getLong(key, defaultValue); - } - - @Override - public boolean getBoolean(final String key, final boolean defaultValue) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - return prefs.getBoolean(key, defaultValue); - } - - @Override - public String getString(final String key, final String defaultValue) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - return prefs.getString(key, defaultValue); - } - - @Override - public byte[] getBytes(String key) { - final String byteValue = getString(key, null); - return byteValue == null ? null : Base64.decode(byteValue, Base64.DEFAULT); - } - - @Override - public void putInt(final String key, final int value) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(key, value); - editor.apply(); - } - - @Override - public void putLong(final String key, final long value) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(key, value); - editor.apply(); - } - - @Override - public void putBoolean(final String key, final boolean value) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(key, value); - editor.apply(); - } - - @Override - public void putString(final String key, final String value) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putString(key, value); - editor.apply(); - } - - @Override - public void putBytes(String key, byte[] value) { - final String encodedBytes = Base64.encodeToString(value, Base64.DEFAULT); - putString(key, encodedBytes); - } - - @Override - public void remove(final String key) { - validateKey(key); - final SharedPreferences prefs = mContext.getSharedPreferences( - getSharedPreferencesName(), Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.remove(key); - editor.apply(); - } -} diff --git a/src/com/android/messaging/util/BuglePrefsKeys.java b/src/com/android/messaging/util/BuglePrefsKeys.java deleted file mode 100644 index ae409bc..0000000 --- a/src/com/android/messaging/util/BuglePrefsKeys.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -/** - * List of shared preferences keys and default values. These are all internal - * (not user-visible) preferences. Preferences that are exposed via the Settings - * activity should be defined in the constants.xml resource file instead. - */ -public final class BuglePrefsKeys { - private BuglePrefsKeys() {} // do not instantiate - - /** - * Bugle's shared preferences version - */ - public static final String SHARED_PREFERENCES_VERSION = - "shared_preferences_version"; - public static final int SHARED_PREFERENCES_VERSION_DEFAULT = - BuglePrefs.NO_SHARED_PREFERENCES_VERSION; - - /** - * Last time that we ran a a sync (in millis) - */ - public static final String LAST_SYNC_TIME - = "last_sync_time_millis"; - public static final long LAST_SYNC_TIME_DEFAULT - = -1; - - /** - * Last time that we ran a full sync (in millis) - */ - public static final String LAST_FULL_SYNC_TIME - = "last_full_sync_time_millis"; - public static final long LAST_FULL_SYNC_TIME_DEFAULT - = -1; - - /** - * Timestamp of the message for which we last did a message notification. - */ - public static final String LATEST_NOTIFICATION_MESSAGE_TIMESTAMP - = "latest_notification_message_timestamp"; - - /** - * The last selected chooser index in the media picker. - */ - public static final String SELECTED_MEDIA_PICKER_CHOOSER_INDEX - = "selected_media_picker_chooser_index"; - public static final int SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT - = -1; - - /** - * The attempt number when retrying ProcessPendingMessagesAction - */ - public static final String PROCESS_PENDING_MESSAGES_RETRY_COUNT - = "process_pending_retry"; - -} diff --git a/src/com/android/messaging/util/BugleSubscriptionPrefs.java b/src/com/android/messaging/util/BugleSubscriptionPrefs.java deleted file mode 100644 index 039712a..0000000 --- a/src/com/android/messaging/util/BugleSubscriptionPrefs.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.content.res.Resources; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; - -/** - * Provides interface to access per-subscription shared preferences. We have one instance of - * this per active subscription. - */ -public class BugleSubscriptionPrefs extends BuglePrefsImpl { - private final int mSubId; - - public BugleSubscriptionPrefs(final Context context, final int subId) { - super(context); - mSubId = subId; - } - - @Override - public String getSharedPreferencesName() { - return SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX + String.valueOf(mSubId); - } - - @Override - protected void validateKey(String key) { - super.validateKey(key); - // Callers should only access per-subscription preferences from this class - Assert.isTrue(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX)); - } - - @Override - public void onUpgrade(final int oldVersion, final int newVersion) { - switch (oldVersion) { - case BuglePrefs.NO_SHARED_PREFERENCES_VERSION: - // Upgrade to version 1. Adding per-subscription shared prefs. - // Migrate values from the application-wide settings. - migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "delivery_reports", - R.string.delivery_reports_pref_key, R.bool.delivery_reports_pref_default); - migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "auto_retrieve_mms", - R.string.auto_retrieve_mms_pref_key, R.bool.auto_retrieve_mms_pref_default); - migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), - "auto_retrieve_mms_when_roaming", - R.string.auto_retrieve_mms_when_roaming_pref_key, - R.bool.auto_retrieve_mms_when_roaming_pref_default); - migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "group_messaging", - R.string.group_mms_pref_key, R.bool.group_mms_pref_default); - - if (PhoneUtils.getDefault().getActiveSubscriptionCount() == 1) { - migratePrefStringInternal(BuglePrefs.getApplicationPrefs(), "mms_phone_number", - R.string.mms_phone_number_pref_key, null); - } - } - } - - private void migratePrefBooleanInternal(final BuglePrefs oldPrefs, final String oldKey, - final int newKeyResId, final int defaultValueResId) { - final Resources resources = Factory.get().getApplicationContext().getResources(); - final boolean defaultValue = resources.getBoolean(defaultValueResId); - final boolean oldValue = oldPrefs.getBoolean(oldKey, defaultValue); - - // Only migrate pref value if it's different than the default. - if (oldValue != defaultValue) { - putBoolean(resources.getString(newKeyResId), oldValue); - } - } - - private void migratePrefStringInternal(final BuglePrefs oldPrefs, final String oldKey, - final int newKeyResId, final String defaultValue) { - final Resources resources = Factory.get().getApplicationContext().getResources(); - final String oldValue = oldPrefs.getString(oldKey, defaultValue); - - // Only migrate pref value if it's different than the default. - if (!TextUtils.equals(oldValue, defaultValue)) { - putString(resources.getString(newKeyResId), oldValue); - } - } -} diff --git a/src/com/android/messaging/util/BugleWidgetPrefs.java b/src/com/android/messaging/util/BugleWidgetPrefs.java deleted file mode 100644 index 63ba567..0000000 --- a/src/com/android/messaging/util/BugleWidgetPrefs.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; - -/** - * Provides interface to access shared preferences used by bugle widgets. - */ -public class BugleWidgetPrefs extends BuglePrefsImpl { - /** - * Shared preferences name for preferences applicable to the entire app. - */ - public static final String SHARED_PREFERENCES_WIDGET_NAME = "bugle_widgets"; - - public BugleWidgetPrefs(Context context) { - super(context); - } - - @Override - public String getSharedPreferencesName() { - return SHARED_PREFERENCES_WIDGET_NAME; - } - - @Override - public void onUpgrade(int oldVersion, int newVersion) { - } -} diff --git a/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java b/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java deleted file mode 100644 index 6cf2f25..0000000 --- a/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.app.Activity; -import android.app.Fragment; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.ui.SnackBar; -import com.android.messaging.ui.UIIntents; - -public class ChangeDefaultSmsAppHelper { - private Runnable mRunAfterMadeDefault; - private ChangeSmsAppSettingRunnable mChangeSmsAppSettingRunnable; - - private static final int REQUEST_SET_DEFAULT_SMS_APP = 1; - - /** - * When there's some condition that prevents an operation, such as sending a message, - * call warnOfMissingActionConditions to put up a toast and allow the user to repair - * that condition. - * @param sending - true if we're called during a sending operation - * @param runAfterMadeDefault - a runnable to run after the user responds - * positively to the condition prompt and resolves the condition. It is - * preferable to specify the value in {@link #handleChangeDefaultSmsResult} - * as that handles the case where the process gets restarted. - * If null, the user will be shown a generic toast message. - * @param composeView - compose view that may have the keyboard opened and focused - * @param rootView - if non-null, use this to attach a snackBar - * @param activity - calling activity - * @param fragment - calling fragment, may be null if called directly from an activity - */ - public void warnOfMissingActionConditions(final boolean sending, - final Runnable runAfterMadeDefault, - final View composeView, final View rootView, - final Activity activity, final Fragment fragment) { - final PhoneUtils phoneUtils = PhoneUtils.getDefault(); - final boolean isSmsCapable = phoneUtils.isSmsCapable(); - final boolean hasPreferredSmsSim = phoneUtils.getHasPreferredSmsSim(); - final boolean isDefaultSmsApp = phoneUtils.isDefaultSmsApp(); - - // Supports SMS? - if (!isSmsCapable) { - UiUtils.showToast(R.string.sms_disabled); - - // Has a preferred sim? - } else if (!hasPreferredSmsSim) { - UiUtils.showToast(R.string.no_preferred_sim_selected); - - // Is the default sms app? - } else if (!isDefaultSmsApp) { - mChangeSmsAppSettingRunnable = new ChangeSmsAppSettingRunnable(activity, fragment); - promptToChangeDefaultSmsApp(sending, runAfterMadeDefault, - composeView, rootView, activity); - } - - LogUtil.w(LogUtil.BUGLE_TAG, "Unsatisfied action condition: " - + "isSmsCapable=" + isSmsCapable + ", " - + "hasPreferredSmsSim=" + hasPreferredSmsSim + ", " - + "isDefaultSmsApp=" + isDefaultSmsApp); - } - - private void promptToChangeDefaultSmsApp(final boolean sending, - final Runnable runAfterMadeDefault, - final View composeView, final View rootView, - final Activity activity) { - if (composeView != null) { - // Avoid bug in system which puts soft keyboard over dialog after orientation change - ImeUtil.hideSoftInput(activity, composeView); - } - mRunAfterMadeDefault = runAfterMadeDefault; - - if (rootView == null) { - // Immediately open the system "Change default SMS app?" dialog setting. - mChangeSmsAppSettingRunnable.run(); - } else { - UiUtils.showSnackBarWithCustomAction(activity, - rootView, - activity.getString(sending ? R.string.requires_default_sms_app_to_send : - R.string.requires_default_sms_app), - SnackBar.Action.createCustomAction(mChangeSmsAppSettingRunnable, - activity.getString(R.string.requires_default_sms_change_button)), - null /* interactions */, - SnackBar.Placement.above(composeView)); - } - } - - private class ChangeSmsAppSettingRunnable implements Runnable { - private final Activity mActivity; - private final Fragment mFragment; - - public ChangeSmsAppSettingRunnable(final Activity activity, final Fragment fragment) { - mActivity = activity; - mFragment = fragment; - } - - @Override - public void run() { - try { - final Intent intent = UIIntents.get().getChangeDefaultSmsAppIntent(mActivity); - if (mFragment != null) { - mFragment.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP); - } else { - mActivity.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP); - } - } catch (final ActivityNotFoundException ex) { - // We shouldn't get here, but the monkey on JB MR0 can trigger it. - LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex); - UiUtils.showToastAtBottom(R.string.activity_not_found_message); - } - } - } - - public void handleChangeDefaultSmsResult( - final int requestCode, - final int resultCode, - Runnable runAfterMadeDefault) { - Assert.isTrue(mRunAfterMadeDefault == null || runAfterMadeDefault == null); - if (runAfterMadeDefault == null) { - runAfterMadeDefault = mRunAfterMadeDefault; - } - - if (requestCode == REQUEST_SET_DEFAULT_SMS_APP) { - if (resultCode == Activity.RESULT_OK) { - // mRunAfterMadeDefault can be null if it was set only in - // promptToChangeDefaultSmsApp, and the process subsequently restarted when the - // user momentarily switched to another app. In that case, we'll simply show a - // generic toast since we do not know what the runnable was supposed to do. - if (runAfterMadeDefault != null) { - runAfterMadeDefault.run(); - } else { - UiUtils.showToast(R.string.toast_after_setting_default_sms_app); - } - } - mRunAfterMadeDefault = null; // don't want to accidentally run it again - } - } -} - - diff --git a/src/com/android/messaging/util/CircularArray.java b/src/com/android/messaging/util/CircularArray.java deleted file mode 100644 index db6cf12..0000000 --- a/src/com/android/messaging/util/CircularArray.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -/** - * Very simple circular array implementation. - * - * @param The element type of this list. - * @LibraryInternal - */ -public class CircularArray { - private int mNextWriter; - private boolean mHasWrapped; - private int mMaxCount; - Object mList[]; - - /** - * Constructor for CircularArray. - * - * @param count Max elements to hold in the list. - */ - public CircularArray(int count) { - mMaxCount = count; - clear(); - } - - /** - * Reset the list. - */ - public void clear() { - mNextWriter = 0; - mHasWrapped = false; - mList = new Object[mMaxCount]; - } - - /** - * Add an element to the end of the list. - * - * @param object The object to add. - */ - public void add(E object) { - mList[mNextWriter] = object; - ++mNextWriter; - if (mNextWriter == mMaxCount) { - mNextWriter = 0; - mHasWrapped = true; - } - } - - /** - * Get the number of elements in the list. This will be 0 <= returned count <= max count - * - * @return Elements in the circular list. - */ - public int count() { - if (mHasWrapped) { - return mMaxCount; - } else { - return mNextWriter; - } - } - - /** - * Return null if the list hasn't wrapped yet. Otherwise return the next object that would be - * overwritten. Can be useful to avoid extra allocations. - * - * @return - */ - @SuppressWarnings("unchecked") - public E getFree() { - if (!mHasWrapped) { - return null; - } else { - return (E) mList[mNextWriter]; - } - } - - /** - * Get the object at index. Index 0 is the oldest item inserted into the list. Index (count() - - * 1) is the newest. - * - * @param index Index to retrieve. - * @return Object at index. - */ - @SuppressWarnings("unchecked") - public E get(int index) { - if (mHasWrapped) { - int wrappedIndex = index + mNextWriter; - if (wrappedIndex >= mMaxCount) { - wrappedIndex -= mMaxCount; - } - return (E) mList[wrappedIndex]; - } else { - return (E) mList[index]; - } - } -} diff --git a/src/com/android/messaging/util/ConnectivityUtil.java b/src/com/android/messaging/util/ConnectivityUtil.java deleted file mode 100644 index 49f6e0a..0000000 --- a/src/com/android/messaging/util/ConnectivityUtil.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; -import android.telephony.TelephonyManager; - -public class ConnectivityUtil { - // Assume not connected until informed differently - private volatile int mCurrentServiceState = ServiceState.STATE_POWER_OFF; - - private final TelephonyManager mTelephonyManager; - private final Context mContext; - private final ConnectivityBroadcastReceiver mReceiver; - private final ConnectivityManager mConnMgr; - - private ConnectivityListener mListener; - private final IntentFilter mIntentFilter; - - public interface ConnectivityListener { - public void onConnectivityStateChanged(final Context context, final Intent intent); - public void onPhoneStateChanged(final Context context, int serviceState); - } - - public ConnectivityUtil(final Context context) { - mContext = context; - mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - mReceiver = new ConnectivityBroadcastReceiver(); - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - } - - public int getCurrentServiceState() { - return mCurrentServiceState; - } - - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onServiceStateChanged(final ServiceState serviceState) { - if (mCurrentServiceState != serviceState.getState()) { - mCurrentServiceState = serviceState.getState(); - onPhoneStateChanged(mCurrentServiceState); - } - } - - @Override - public void onDataConnectionStateChanged(final int state) { - mCurrentServiceState = (state == TelephonyManager.DATA_DISCONNECTED) ? - ServiceState.STATE_OUT_OF_SERVICE : ServiceState.STATE_IN_SERVICE; - } - }; - - private void onPhoneStateChanged(final int serviceState) { - final ConnectivityListener listener = mListener; - if (listener != null) { - listener.onPhoneStateChanged(mContext, serviceState); - } - } - - private void onConnectivityChanged(final Context context, final Intent intent) { - final ConnectivityListener listener = mListener; - if (listener != null) { - listener.onConnectivityStateChanged(context, intent); - } - } - - public void register(final ConnectivityListener listener) { - Assert.isTrue(mListener == null || mListener == listener); - if (mListener == null) { - if (mTelephonyManager != null) { - mCurrentServiceState = (PhoneUtils.getDefault().isAirplaneModeOn() ? - ServiceState.STATE_POWER_OFF : ServiceState.STATE_IN_SERVICE); - mTelephonyManager.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE); - } - if (mConnMgr != null) { - mContext.registerReceiver(mReceiver, mIntentFilter); - } - } - mListener = listener; - } - - public void unregister() { - if (mListener != null) { - if (mTelephonyManager != null) { - mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); - mCurrentServiceState = ServiceState.STATE_POWER_OFF; - } - if (mConnMgr != null) { - mContext.unregisterReceiver(mReceiver); - } - } - mListener = null; - } - - /** - * Connectivity change broadcast receiver. This gets the network connectivity updates. - * In case we don't get the active connectivity when we first acquire the network, - * this receiver will notify us when it is connected, so to unblock the waiting thread - * which is sending the message. - */ - public class ConnectivityBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - return; - } - - onConnectivityChanged(context, intent); - } - } - - private int mSignalLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - - // We use a separate instance than mPhoneStateListener because the lifetimes are different. - private final PhoneStateListener mSignalStrengthListener = new PhoneStateListener() { - @Override - public void onSignalStrengthsChanged(final SignalStrength signalStrength) { - mSignalLevel = getLevel(signalStrength); - } - }; - - public void registerForSignalStrength() { - mTelephonyManager.listen( - mSignalStrengthListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); - } - - public void unregisterForSignalStrength() { - mTelephonyManager.listen(mSignalStrengthListener, PhoneStateListener.LISTEN_NONE); - } - - /** - * @param subId This is ignored because TelephonyManager does not support it. - * @return Signal strength as level 0..4 - */ - public int getSignalLevel(final int subId) { - return mSignalLevel; - } - - private static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; - private static final int SIGNAL_STRENGTH_POOR = 1; - private static final int SIGNAL_STRENGTH_MODERATE = 2; - private static final int SIGNAL_STRENGTH_GOOD = 3; - private static final int SIGNAL_STRENGTH_GREAT = 4; - - private static final int GSM_SIGNAL_STRENGTH_GREAT = 12; - private static final int GSM_SIGNAL_STRENGTH_GOOD = 8; - private static final int GSM_SIGNAL_STRENGTH_MODERATE = 8; - - private static int getLevel(final SignalStrength signalStrength) { - if (signalStrength.isGsm()) { - // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthGsm.java - - // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 - // asu = 0 (-113dB or less) is very weak - // signal, its better to show 0 bars to the user in such cases. - // asu = 99 is a special case, where the signal strength is unknown. - final int asu = signalStrength.getGsmSignalStrength(); - if (asu <= 2 || asu == 99) return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - else if (asu >= GSM_SIGNAL_STRENGTH_GREAT) return SIGNAL_STRENGTH_GREAT; - else if (asu >= GSM_SIGNAL_STRENGTH_GOOD) return SIGNAL_STRENGTH_GOOD; - else if (asu >= GSM_SIGNAL_STRENGTH_MODERATE) return SIGNAL_STRENGTH_MODERATE; - else return SIGNAL_STRENGTH_POOR; - } else { - // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthCdma.java - - final int cdmaLevel = getCdmaLevel(signalStrength); - final int evdoLevel = getEvdoLevel(signalStrength); - if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { - /* We don't know evdo, use cdma */ - return getCdmaLevel(signalStrength); - } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { - /* We don't know cdma, use evdo */ - return getEvdoLevel(signalStrength); - } else { - /* We know both, use the lowest level */ - return cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel; - } - } - } - - /** - * Get cdma as level 0..4 - */ - private static int getCdmaLevel(final SignalStrength signalStrength) { - final int cdmaDbm = signalStrength.getCdmaDbm(); - final int cdmaEcio = signalStrength.getCdmaEcio(); - int levelDbm; - int levelEcio; - if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT; - else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD; - else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE; - else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR; - else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - // Ec/Io are in dB*10 - if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT; - else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD; - else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE; - else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR; - else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - final int level = (levelDbm < levelEcio) ? levelDbm : levelEcio; - return level; - } - /** - * Get Evdo as level 0..4 - */ - private static int getEvdoLevel(final SignalStrength signalStrength) { - final int evdoDbm = signalStrength.getEvdoDbm(); - final int evdoSnr = signalStrength.getEvdoSnr(); - int levelEvdoDbm; - int levelEvdoSnr; - if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT; - else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD; - else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE; - else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR; - else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT; - else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD; - else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE; - else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR; - else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - final int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; - return level; - } -} diff --git a/src/com/android/messaging/util/ContactRecipientEntryUtils.java b/src/com/android/messaging/util/ContactRecipientEntryUtils.java deleted file mode 100644 index 78c6ffd..0000000 --- a/src/com/android/messaging/util/ContactRecipientEntryUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.net.Uri; -import android.provider.ContactsContract.DisplayNameSources; -import android.text.TextUtils; - -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.BugleRecipientEntry; -import com.android.messaging.datamodel.data.ParticipantData; - -/** - * Provides utility methods around creating RecipientEntry instance specific to Bugle's needs. - */ -public class ContactRecipientEntryUtils { - /** - * A special contact id for generated contacts with no display name (number only) and avatar. - * By default, the chips UI doesn't load any avatar for chips with no display name, or where - * the display name is the same as phone number (which is true for unknown contacts). - * Since Bugle always generate a default avatar for all contacts, this is used to replace - * those default generated chips with a phone number and no avatars. - */ - private static final long CONTACT_ID_NUMBER_WITH_AVATAR = -1000; - - /** - * A generated special contact which says "Send to xxx" in the contact list, which allows - * a user to direct send an SMS to a number that was manually typed in. - */ - private static final long CONTACT_ID_SENDTO_DESTINATION = -1001; - - /** - * Construct a special "Send to xxx" entry for a given destination. - */ - public static RecipientEntry constructSendToDestinationEntry(final String destination) { - return constructSpecialRecipientEntry(destination, CONTACT_ID_SENDTO_DESTINATION); - } - - /** - * Construct a generated contact entry but with rendered avatar. - */ - public static RecipientEntry constructNumberWithAvatarEntry(final String destination) { - return constructSpecialRecipientEntry(destination, CONTACT_ID_NUMBER_WITH_AVATAR); - } - - private static RecipientEntry constructSpecialRecipientEntry(final String destination, - final long contactId) { - // For the send-to-destination (e.g. "Send to xxx" in the auto-complete drop-down) - // we want to show a default avatar with a static background so that it doesn't flicker - // as the user types. - final Uri avatarUri = contactId == CONTACT_ID_SENDTO_DESTINATION ? - AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR : null; - return BugleRecipientEntry.constructTopLevelEntry(null, DisplayNameSources.STRUCTURED_NAME, - destination, RecipientEntry.INVALID_DESTINATION_TYPE, null, contactId, - null, contactId, avatarUri, true, null); - } - - /** - * Gets the display name for contact list only. For most cases this is the same as the normal - * contact name, but there are cases where these two differ. For example, for the - * send to typed number item, we'd like to show "Send to xxx" in the contact list. However, - * when this item is actually added to the chips edit box, we would like to show just the - * phone number (i.e. no display name). - */ - public static String getDisplayNameForContactList(final RecipientEntry entry) { - if (entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION) { - return Factory.get().getApplicationContext().getResources().getString( - R.string.contact_list_send_to_text, formatDestination(entry)); - } else if (!TextUtils.isEmpty(entry.getDisplayName())) { - return entry.getDisplayName(); - } else { - return formatDestination(entry); - } - } - - public static String formatDestination(final RecipientEntry entry) { - return PhoneUtils.getDefault().formatForDisplay(entry.getDestination()); - } - - /** - * Returns true if the given entry has only avatar and number - */ - public static boolean isAvatarAndNumberOnlyContact(final RecipientEntry entry) { - return entry.getContactId() == CONTACT_ID_NUMBER_WITH_AVATAR; - } - - /** - * Returns true if the given entry is a special send to number item. - */ - public static boolean isSendToDestinationContact(final RecipientEntry entry) { - return entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION; - } - - /** - * Returns true if the given participant is a special send to number item. - */ - public static boolean isSendToDestinationContact(final ParticipantData participant) { - return participant.getContactId() == CONTACT_ID_SENDTO_DESTINATION; - } -} diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java deleted file mode 100644 index 8555889..0000000 --- a/src/com/android/messaging/util/ContactUtil.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Email; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.CommonDataKinds.StructuredName; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Directory; -import android.provider.ContactsContract.DisplayNameSources; -import android.provider.ContactsContract.PhoneLookup; -import android.provider.ContactsContract.Profile; -import android.text.TextUtils; -import android.view.View; - -import com.android.ex.chips.RecipientEntry; -import com.android.messaging.Factory; -import com.android.messaging.datamodel.CursorQueryData; -import com.android.messaging.datamodel.FrequentContactsCursorQueryData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsSmsUtils; -import com.android.messaging.ui.contact.AddContactsConfirmationDialog; -import com.google.common.annotations.VisibleForTesting; - -/** - * Utility class including logic to list, filter, and lookup phone and emails in CP2. - */ -@VisibleForTesting -public class ContactUtil { - - /** - * Index of different columns in phone or email queries. All queries below should confirm to - * this column content and ordering so that caller can use the uniformed way to process - * returned cursors. - */ - public static final int INDEX_CONTACT_ID = 0; - public static final int INDEX_DISPLAY_NAME = 1; - public static final int INDEX_PHOTO_URI = 2; - public static final int INDEX_PHONE_EMAIL = 3; - public static final int INDEX_PHONE_EMAIL_TYPE = 4; - public static final int INDEX_PHONE_EMAIL_LABEL = 5; - - // An optional lookup_id column used by PhoneLookupQuery that is needed when querying for - // contact information. - public static final int INDEX_LOOKUP_KEY = 6; - - // An optional _id column to query results that need to be displayed in a list view. - public static final int INDEX_DATA_ID = 7; - - // An optional sort_key column for displaying contact section labels. - public static final int INDEX_SORT_KEY = 8; - - // Lookup key column index specific to frequent contacts query. - public static final int INDEX_LOOKUP_KEY_FREQUENT = 3; - - /** - * Constants for listing and filtering phones. - */ - public static class PhoneQuery { - public static final String SORT_KEY = Phone.SORT_KEY_PRIMARY; - - public static final String[] PROJECTION = new String[] { - Phone.CONTACT_ID, // 0 - Phone.DISPLAY_NAME_PRIMARY, // 1 - Phone.PHOTO_THUMBNAIL_URI, // 2 - Phone.NUMBER, // 3 - Phone.TYPE, // 4 - Phone.LABEL, // 5 - Phone.LOOKUP_KEY, // 6 - Phone._ID, // 7 - PhoneQuery.SORT_KEY, // 8 - }; - } - - /** - * Constants for looking up phone numbers. - */ - public static class PhoneLookupQuery { - public static final String[] PROJECTION = new String[] { - // The _ID field points to the contact id of the content - PhoneLookup._ID, // 0 - PhoneLookup.DISPLAY_NAME, // 1 - PhoneLookup.PHOTO_THUMBNAIL_URI, // 2 - PhoneLookup.NUMBER, // 3 - PhoneLookup.TYPE, // 4 - PhoneLookup.LABEL, // 5 - PhoneLookup.LOOKUP_KEY, // 6 - // The data id is not included as part of the projection since it's not part of - // PhoneLookup. This is okay because the _id field serves as both the data id and - // contact id. Also we never show the results directly in a list view so we are not - // concerned about duplicated _id's (namely, the same contact has two same phone - // numbers) - }; - } - - public static class FrequentContactQuery { - public static final String[] PROJECTION = new String[] { - Contacts._ID, // 0 - Contacts.DISPLAY_NAME, // 1 - Contacts.PHOTO_URI, // 2 - Phone.LOOKUP_KEY, // 3 - }; - } - - /** - * Constants for listing and filtering emails. - */ - public static class EmailQuery { - public static final String SORT_KEY = Email.SORT_KEY_PRIMARY; - - public static final String[] PROJECTION = new String[] { - Email.CONTACT_ID, // 0 - Email.DISPLAY_NAME_PRIMARY, // 1 - Email.PHOTO_THUMBNAIL_URI, // 2 - Email.ADDRESS, // 3 - Email.TYPE, // 4 - Email.LABEL, // 5 - Email.LOOKUP_KEY, // 6 - Email._ID, // 7 - EmailQuery.SORT_KEY, // 8 - }; - } - - public static final int INDEX_SELF_QUERY_LOOKUP_KEY = 3; - - /** - * Constants for querying self from CP2. - */ - public static class SelfQuery { - public static final String[] PROJECTION = new String[] { - Profile._ID, // 0 - Profile.DISPLAY_NAME_PRIMARY, // 1 - Profile.PHOTO_THUMBNAIL_URI, // 2 - Profile.LOOKUP_KEY // 3 - // Phone number, type, label and data_id is not provided in this projection since - // Profile CONTENT_URI doesn't include this information. Also, we don't need it - // we just need the name and avatar url. - }; - } - - public static class StructuredNameQuery { - public static final String[] PROJECTION = new String[] { - StructuredName.DISPLAY_NAME, - StructuredName.GIVEN_NAME, - StructuredName.FAMILY_NAME, - StructuredName.PREFIX, - StructuredName.MIDDLE_NAME, - StructuredName.SUFFIX - }; - } - - public static final int INDEX_STRUCTURED_NAME_DISPLAY_NAME = 0; - public static final int INDEX_STRUCTURED_NAME_GIVEN_NAME = 1; - public static final int INDEX_STRUCTURED_NAME_FAMILY_NAME = 2; - public static final int INDEX_STRUCTURED_NAME_PREFIX = 3; - public static final int INDEX_STRUCTURED_NAME_MIDDLE_NAME = 4; - public static final int INDEX_STRUCTURED_NAME_SUFFIX = 5; - - public static final long INVALID_CONTACT_ID = -1; - - /** - * This class is static. No need to create an instance. - */ - private ContactUtil() { - } - - /** - * Shows a contact card or add to contacts dialog for the given contact info - * @param view The view whose click triggered this to show - * @param contactId The id of the contact in the android contacts DB - * @param contactLookupKey The lookup key from contacts DB - * @param avatarUri Uri to the avatar image if available - * @param normalizedDestination The normalized phone number or email - */ - public static void showOrAddContact(final View view, final long contactId, - final String contactLookupKey, final Uri avatarUri, - final String normalizedDestination) { - if (contactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED - && !TextUtils.isEmpty(contactLookupKey)) { - final Uri lookupUri = - ContactsContract.Contacts.getLookupUri(contactId, contactLookupKey); - ContactsContract.QuickContact.showQuickContact(view.getContext(), view, lookupUri, - ContactsContract.QuickContact.MODE_LARGE, null); - } else if (!TextUtils.isEmpty(normalizedDestination) && !TextUtils.equals( - normalizedDestination, ParticipantData.getUnknownSenderDestination())) { - final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog( - view.getContext(), avatarUri, normalizedDestination); - dialog.show(); - } - } - - @VisibleForTesting - public static CursorQueryData getSelf(final Context context) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - return new CursorQueryData(context, Profile.CONTENT_URI, SelfQuery.PROJECTION, null, null, - null); - } - - /** - * Get a list of phones sorted by contact name. One contact may have multiple phones. - * In that case, each phone will be returned as a separate record in the result cursor. - */ - @VisibleForTesting - public static CursorQueryData getPhones(final Context context) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - // The AOSP Contacts provider allows adding a ContactsContract.REMOVE_DUPLICATE_ENTRIES - // query parameter that removes duplicate (raw) numbers. Unfortunately, we can't use that - // because it causes the some phones' contacts provider to return incorrect sections. - final Uri uri = Phone.CONTENT_URI.buildUpon().appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) - .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true") - .build(); - - return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null, - PhoneQuery.SORT_KEY); - } - - /** - * Lookup a destination (phone, email). Supplied destination should be a relatively complete - * one for this to succeed. PhoneLookup / EmailLookup URI will apply some smartness to do a - * loose match to see whether there is a contact that matches this destination. - */ - public static CursorQueryData lookupDestination(final Context context, - final String destination) { - if (MmsSmsUtils.isEmailAddress(destination)) { - return ContactUtil.lookupEmail(context, destination); - } else { - return ContactUtil.lookupPhone(context, destination); - } - } - - /** - * Returns whether the search text indicates an email based search or a phone number based one. - */ - private static boolean shouldFilterForEmail(final String searchText) { - return searchText != null && searchText.contains("@"); - } - - /** - * Get a list of destinations (phone, email) matching the partial destination. - */ - public static CursorQueryData filterDestination(final Context context, - final String destination) { - if (shouldFilterForEmail(destination)) { - return ContactUtil.filterEmails(context, destination); - } else { - return ContactUtil.filterPhones(context, destination); - } - } - - /** - * Get a list of phones matching a search criteria. The search may be on contact name or - * phone number. In case search is on contact name, all matching contact's phone number - * will be returned. - * NOTE: This is visible for testing only, clients should only call filterDestination() since - * we support email addresses as well. - */ - @VisibleForTesting - public static CursorQueryData filterPhones(final Context context, final String query) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - final Uri uri = Phone.CONTENT_FILTER_URI.buildUpon() - .appendPath(query).appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) - .build(); - - return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null, - PhoneQuery.SORT_KEY); - } - - /** - * Lookup a phone based on a phone number. Supplied phone should be a relatively complete - * phone number for this to succeed. PhoneLookup URI will apply some smartness to do a - * loose match to see whether there is a contact that matches this phone. - * NOTE: This is visible for testing only, clients should only call lookupDestination() since - * we support email addresses as well. - */ - @VisibleForTesting - public static CursorQueryData lookupPhone(final Context context, final String phone) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - final Uri uri = getPhoneLookupUri().buildUpon() - .appendPath(phone).build(); - - return new CursorQueryData(context, uri, PhoneLookupQuery.PROJECTION, null, null, null); - } - - /** - * Get frequently contacted people. This queries for Contacts.CONTENT_STREQUENT_URI, which - * includes both starred or frequently contacted people. - */ - public static CursorQueryData getFrequentContacts(final Context context) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - return new FrequentContactsCursorQueryData(context, FrequentContactQuery.PROJECTION, - null, null, null); - } - - /** - * Get a list of emails matching a search criteria. In Bugle, since email is not a common - * usage scenario, we should only do email search after user typed in a query indicating - * an intention to search by email (for example, "joe@"). - * NOTE: This is visible for testing only, clients should only call filterDestination() since - * we support email addresses as well. - */ - @VisibleForTesting - public static CursorQueryData filterEmails(final Context context, final String query) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - final Uri uri = Email.CONTENT_FILTER_URI.buildUpon() - .appendPath(query).appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) - .build(); - - return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null, - EmailQuery.SORT_KEY); - } - - /** - * Lookup emails based a complete email address. Since there is no special logic needed for - * email lookup, this simply calls filterEmails. - * NOTE: This is visible for testing only, clients should only call lookupDestination() since - * we support email addresses as well. - */ - @VisibleForTesting - public static CursorQueryData lookupEmail(final Context context, final String email) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - final Uri uri = getEmailContentLookupUri().buildUpon() - .appendPath(email).appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) - .build(); - - return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null, - EmailQuery.SORT_KEY); - } - - /** - * Looks up the structured name for a contact. - * - * @param primaryOnly If there are multiple raw contacts, set this flag to return only the - * name used as the primary display name. Otherwise, this method returns all names. - */ - private static CursorQueryData lookupStructuredName(final Context context, final long contactId, - final boolean primaryOnly) { - if (!ContactUtil.hasReadContactsPermission()) { - return CursorQueryData.getEmptyQueryData(); - } - - // TODO: Handle enterprise contacts - final Uri uri = ContactsContract.Contacts.CONTENT_URI.buildUpon() - .appendPath(String.valueOf(contactId)) - .appendPath(ContactsContract.Contacts.Data.CONTENT_DIRECTORY).build(); - - String selection = ContactsContract.Data.MIMETYPE + "=?"; - final String[] selectionArgs = { - StructuredName.CONTENT_ITEM_TYPE - }; - if (primaryOnly) { - selection += " AND " + Contacts.DISPLAY_NAME_PRIMARY + "=" - + StructuredName.DISPLAY_NAME; - } - - return new CursorQueryData(context, uri, - StructuredNameQuery.PROJECTION, selection, selectionArgs, null); - } - - /** - * Looks up the first name for a contact. If there are multiple raw - * contacts, this returns the name that is associated with the contact's - * primary display name. The name is null when contact id does not exist - * (possibly because it is a corp contact) or it does not have a first name. - */ - public static String lookupFirstName(final Context context, final long contactId) { - if (isEnterpriseContactId(contactId)) { - return null; - } - String firstName = null; - Cursor nameCursor = null; - try { - nameCursor = ContactUtil.lookupStructuredName(context, contactId, true) - .performSynchronousQuery(); - if (nameCursor != null && nameCursor.moveToFirst()) { - firstName = nameCursor.getString(ContactUtil.INDEX_STRUCTURED_NAME_GIVEN_NAME); - } - } finally { - if (nameCursor != null) { - nameCursor.close(); - } - } - return firstName; - } - - /** - * Creates a RecipientEntry from the provided data fields (from the contacts cursor). - * @param firstLevel whether this item is the first entry of this contact in the list. - */ - public static RecipientEntry createRecipientEntry(final String displayName, - final int displayNameSource, final String destination, final int destinationType, - final String destinationLabel, final long contactId, final String lookupKey, - final long dataId, final String photoThumbnailUri, final boolean firstLevel) { - if (firstLevel) { - return RecipientEntry.constructTopLevelEntry(displayName, displayNameSource, - destination, destinationType, destinationLabel, contactId, null, dataId, - photoThumbnailUri, true, lookupKey); - } else { - return RecipientEntry.constructSecondLevelEntry(displayName, displayNameSource, - destination, destinationType, destinationLabel, contactId, null, dataId, - photoThumbnailUri, true, lookupKey); - } - } - - /** - * Creates a RecipientEntry for PhoneQuery result. The result is then displayed in the - * contact search drop down or as replacement chips in the chips edit box. - */ - public static RecipientEntry createRecipientEntryForPhoneQuery(final Cursor cursor, - final boolean isFirstLevel) { - final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID); - final String displayName = cursor.getString( - ContactUtil.INDEX_DISPLAY_NAME); - final String photoThumbnailUri = cursor.getString( - ContactUtil.INDEX_PHOTO_URI); - final String destination = cursor.getString( - ContactUtil.INDEX_PHONE_EMAIL); - final int destinationType = cursor.getInt( - ContactUtil.INDEX_PHONE_EMAIL_TYPE); - final String destinationLabel = cursor.getString( - ContactUtil.INDEX_PHONE_EMAIL_LABEL); - final String lookupKey = cursor.getString( - ContactUtil.INDEX_LOOKUP_KEY); - - // PhoneQuery uses the contact id as the data id ("_id"). - final long dataId = contactId; - - return createRecipientEntry(displayName, - DisplayNameSources.STRUCTURED_NAME, destination, destinationType, - destinationLabel, contactId, lookupKey, dataId, photoThumbnailUri, - isFirstLevel); - } - - /** - * Returns if a given contact id is valid. - */ - public static boolean isValidContactId(final long contactId) { - return contactId >= 0; - } - - /** - * Returns if a given contact id belongs to managed profile. - */ - public static boolean isEnterpriseContactId(final long contactId) { - return isWorkProfileSupported() - && ContactsContract.Contacts.isEnterpriseContactId(contactId); - } - - /** - * Returns if managed profile is supported. - */ - public static boolean isWorkProfileSupported() { - final PackageManager pm = Factory.get().getApplicationContext().getPackageManager(); - return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS); - } - - /** - * Returns Email lookup uri that will query both primary and corp profile - */ - private static Uri getEmailContentLookupUri() { - if (isWorkProfileSupported() && OsUtil.isAtLeastM()) { - // TODO: use Email.ENTERPRISE_CONTENT_LOOKUP_URI, which will be available in M SDK API - return Uri.parse("content://com.android.contacts/data/emails/lookup_enterprise"); - } - return Email.CONTENT_LOOKUP_URI; - } - - /** - * Returns PhoneLookup URI. - */ - public static Uri getPhoneLookupUri() { - // Apply it to M only - if (isWorkProfileSupported() && OsUtil.isAtLeastM()) { - return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI; - } - return PhoneLookup.CONTENT_FILTER_URI; - } - - public static boolean hasReadContactsPermission() { - return OsUtil.hasPermission(Manifest.permission.READ_CONTACTS); - } -} diff --git a/src/com/android/messaging/util/ContentType.java b/src/com/android/messaging/util/ContentType.java deleted file mode 100644 index bb4a7b2..0000000 --- a/src/com/android/messaging/util/ContentType.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2007-2008 Esmertec AG. - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.webkit.MimeTypeMap; - -public final class ContentType { - public static String THREE_GPP_EXTENSION = "3gp"; - public static String VIDEO_MP4_EXTENSION = "mp4"; - // Default extension used when we don't know one. - public static String DEFAULT_EXTENSION = "dat"; - - public static final int TYPE_IMAGE = 0; - public static final int TYPE_VIDEO = 1; - public static final int TYPE_AUDIO = 2; - public static final int TYPE_VCARD = 3; - public static final int TYPE_OTHER = 4; - - public static final String ANY_TYPE = "*/*"; - public static final String MMS_MESSAGE = "application/vnd.wap.mms-message"; - // The phony content type for generic PDUs (e.g. ReadOrig.ind, - // Notification.ind, Delivery.ind). - public static final String MMS_GENERIC = "application/vnd.wap.mms-generic"; - public static final String MMS_MULTIPART_MIXED = "application/vnd.wap.multipart.mixed"; - public static final String MMS_MULTIPART_RELATED = "application/vnd.wap.multipart.related"; - public static final String MMS_MULTIPART_ALTERNATIVE = - "application/vnd.wap.multipart.alternative"; - - public static final String TEXT_PLAIN = "text/plain"; - public static final String TEXT_HTML = "text/html"; - public static final String TEXT_VCALENDAR = "text/x-vCalendar"; - public static final String TEXT_VCARD = "text/x-vCard"; - - public static final String IMAGE_PREFIX = "image/"; - public static final String IMAGE_UNSPECIFIED = "image/*"; - public static final String IMAGE_JPEG = "image/jpeg"; - public static final String IMAGE_JPG = "image/jpg"; - public static final String IMAGE_GIF = "image/gif"; - public static final String IMAGE_WBMP = "image/vnd.wap.wbmp"; - public static final String IMAGE_PNG = "image/png"; - public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp"; - - public static final String AUDIO_UNSPECIFIED = "audio/*"; - public static final String AUDIO_AAC = "audio/aac"; - public static final String AUDIO_AMR = "audio/amr"; - public static final String AUDIO_IMELODY = "audio/imelody"; - public static final String AUDIO_MID = "audio/mid"; - public static final String AUDIO_MIDI = "audio/midi"; - public static final String AUDIO_MP3 = "audio/mp3"; - public static final String AUDIO_MPEG3 = "audio/mpeg3"; - public static final String AUDIO_MPEG = "audio/mpeg"; - public static final String AUDIO_MPG = "audio/mpg"; - public static final String AUDIO_MP4 = "audio/mp4"; - public static final String AUDIO_MP4_LATM = "audio/mp4-latm"; - public static final String AUDIO_X_MID = "audio/x-mid"; - public static final String AUDIO_X_MIDI = "audio/x-midi"; - public static final String AUDIO_X_MP3 = "audio/x-mp3"; - public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3"; - public static final String AUDIO_X_MPEG = "audio/x-mpeg"; - public static final String AUDIO_X_MPG = "audio/x-mpg"; - public static final String AUDIO_3GPP = "audio/3gpp"; - public static final String AUDIO_X_WAV = "audio/x-wav"; - public static final String AUDIO_OGG = "application/ogg"; - - public static final String MULTIPART_MIXED = "multipart/mixed"; - - public static final String VIDEO_UNSPECIFIED = "video/*"; - public static final String VIDEO_3GP = "video/3gp"; - public static final String VIDEO_3GPP = "video/3gpp"; - public static final String VIDEO_3G2 = "video/3gpp2"; - public static final String VIDEO_H263 = "video/h263"; - public static final String VIDEO_M4V = "video/m4v"; - public static final String VIDEO_MP4 = "video/mp4"; - public static final String VIDEO_MPEG = "video/mpeg"; - public static final String VIDEO_MPEG4 = "video/mpeg4"; - public static final String VIDEO_WEBM = "video/webm"; - - public static final String APP_SMIL = "application/smil"; - public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml"; - public static final String APP_XHTML = "application/xhtml+xml"; - - public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content"; - public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message"; - - // This class should never be instantiated. - private ContentType() { - } - - public static boolean isTextType(final String contentType) { - return TEXT_PLAIN.equals(contentType) - || TEXT_HTML.equals(contentType) - || APP_WAP_XHTML.equals(contentType); - } - - public static boolean isMediaType(final String contentType) { - return isImageType(contentType) - || isVideoType(contentType) - || isAudioType(contentType) - || isVCardType(contentType); - } - - public static boolean isImageType(final String contentType) { - return (null != contentType) && contentType.startsWith(IMAGE_PREFIX); - } - - public static boolean isAudioType(final String contentType) { - return (null != contentType) && - (contentType.startsWith("audio/") || contentType.equalsIgnoreCase(AUDIO_OGG)); - } - - public static boolean isVideoType(final String contentType) { - return (null != contentType) && contentType.startsWith("video/"); - } - - public static boolean isVCardType(final String contentType) { - return (null != contentType) && contentType.equalsIgnoreCase(TEXT_VCARD); - } - - public static boolean isDrmType(final String contentType) { - return (null != contentType) - && (contentType.equals(APP_DRM_CONTENT) - || contentType.equals(APP_DRM_MESSAGE)); - } - - public static boolean isUnspecified(final String contentType) { - return (null != contentType) && contentType.endsWith("*"); - } - - /** - * If the content type is a type which can be displayed in the conversation list as a preview. - */ - public static boolean isConversationListPreviewableType(final String contentType) { - return ContentType.isAudioType(contentType) || ContentType.isVideoType(contentType) || - ContentType.isImageType(contentType) || ContentType.isVCardType(contentType); - } - - /** - * Given a filename, look at the extension and try and determine the mime type. - * - * @param fileName a filename to determine the type from, such as img1231.jpg - * @param contentTypeDefault type to use when the content type can't be determined from the file - * extension. It can be null or a type such as ContentType.IMAGE_UNSPECIFIED - * @return Content type of the extension. - */ - public static String getContentTypeFromExtension(final String fileName, - final String contentTypeDefault) { - final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName); - String contentType = mimeTypeMap.getMimeTypeFromExtension(extension); - if (contentType == null) { - contentType = contentTypeDefault; - } - return contentType; - } - - /** - * Get the common file extension for a given content type - * @param contentType The content type - * @return The extension without the . - */ - public static String getExtension(final String contentType) { - if (VIDEO_MP4.equals(contentType)) { - return VIDEO_MP4_EXTENSION; - } else if (VIDEO_3GPP.equals(contentType)) { - return THREE_GPP_EXTENSION; - } else { - return DEFAULT_EXTENSION; - } - } -} diff --git a/src/com/android/messaging/util/ConversationIdSet.java b/src/com/android/messaging/util/ConversationIdSet.java deleted file mode 100644 index 75bf634..0000000 --- a/src/com/android/messaging/util/ConversationIdSet.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; - -/** - * Utility class to make it easy to store multiple conversation id strings in a single string - * with delimeters. - */ -public class ConversationIdSet extends HashSet { - private static final String JOIN_DELIMITER = "|"; - private static final String SPLIT_DELIMITER = "\\|"; - - public ConversationIdSet() { - super(); - } - - public ConversationIdSet(final Collection asList) { - super(asList); - } - - public String first() { - if (size() > 0) { - return iterator().next(); - } else { - return null; - } - } - - public static ConversationIdSet createSet(final String conversationIdSetString) { - ConversationIdSet set = null; - if (conversationIdSetString != null) { - set = new ConversationIdSet(Arrays.asList(conversationIdSetString.split( - SPLIT_DELIMITER))); - } - return set; - } - - public String getDelimitedString() { - return OsUtil.joinFromSetWithDelimiter(this, JOIN_DELIMITER); - } - - public static String join(final String conversationIdSet1, final String conversationIdSet2) { - String joined = null; - if (conversationIdSet1 == null) { - joined = conversationIdSet2; - } else if (conversationIdSet2 != null) { - joined = conversationIdSet1 + JOIN_DELIMITER + conversationIdSet2; - } - return joined; - } - -} diff --git a/src/com/android/messaging/util/CubicBezierInterpolator.java b/src/com/android/messaging/util/CubicBezierInterpolator.java deleted file mode 100644 index 317b3e6..0000000 --- a/src/com/android/messaging/util/CubicBezierInterpolator.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.view.animation.Interpolator; - -/** - * Class that acts as an interpolator to match the cubic-bezier css timing function where p0 is - * fixed at 0,0 and p3 is fixed at 1,1 - */ -public class CubicBezierInterpolator implements Interpolator { - private final float mX1; - private final float mY1; - private final float mX2; - private final float mY2; - - public CubicBezierInterpolator(final float x1, final float y1, final float x2, final float y2) { - mX1 = x1; - mY1 = y1; - mX2 = x2; - mY2 = y2; - } - - @Override - public float getInterpolation(float v) { - return getY(getTForXValue(v)); - } - - private float getX(final float t) { - return getCoordinate(t, mX1, mX2); - } - - private float getY(final float t) { - return getCoordinate(t, mY1, mY2); - } - - private float getCoordinate(float t, float p1, float p2) { - // Special case start and end. - if (t == 0.0f || t == 1.0f) { - return t; - } - - // Step one - from 4 points to 3. - float ip0 = linearInterpolate(0, p1, t); - float ip1 = linearInterpolate(p1, p2, t); - float ip2 = linearInterpolate(p2, 1, t); - - // Step two - from 3 points to 2. - ip0 = linearInterpolate(ip0, ip1, t); - ip1 = linearInterpolate(ip1, ip2, t); - - // Final step - last point. - return linearInterpolate(ip0, ip1, t); - } - - private float linearInterpolate(float a, float b, float progress) { - return a + (b - a) * progress; - } - - private float getTForXValue(final float x) { - final float epsilon = 1e-6f; - final int iterations = 8; - - if (x <= 0.0f) { - return 0.0f; - } else if (x >= 1.0f) { - return 1.0f; - } - - // Try gradient descent to solve for t. If it works, it is very fast. - float t = x; - float minT = 0.0f; - float maxT = 1.0f; - float value = 0.0f; - for (int i = 0; i < iterations; i++) { - value = getX(t); - double derivative = (getX(t + epsilon) - value) / epsilon; - if (Math.abs(value - x) < epsilon) { - return t; - } else if (Math.abs(derivative) < epsilon) { - break; - } else { - if (value < x) { - minT = t; - } else { - maxT = t; - } - t -= (value - x) / derivative; - } - } - - // If the gradient descent got stuck in a local minimum, e.g. because the - // derivative was close to 0, use an interval bisection instead. - for (int i = 0; Math.abs(value - x) > epsilon && i < iterations; i++) { - if (value < x) { - minT = t; - t = (t + maxT) / 2.0f; - } else { - maxT = t; - t = (t + minT) / 2.0f; - } - value = getX(t); - } - return t; - } -} diff --git a/src/com/android/messaging/util/Dates.java b/src/com/android/messaging/util/Dates.java deleted file mode 100644 index d012dfd..0000000 --- a/src/com/android/messaging/util/Dates.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.text.format.DateUtils; -import android.text.format.Time; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.google.common.annotations.VisibleForTesting; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** - * Collection of date utilities. - */ -public class Dates { - public static final long SECOND_IN_MILLIS = 1000; - public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; - public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; - public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; - public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; - - // Flags to specify whether or not to use 12 or 24 hour mode. - // Callers of methods in this class should never have to specify these; this is really - // intended only for unit tests. - @SuppressWarnings("deprecation") - @VisibleForTesting public static final int FORCE_12_HOUR = DateUtils.FORMAT_12HOUR; - @SuppressWarnings("deprecation") - @VisibleForTesting public static final int FORCE_24_HOUR = DateUtils.FORMAT_24HOUR; - - /** - * Private default constructor - */ - private Dates() { - } - - private static Context getContext() { - return Factory.get().getApplicationContext(); - } - /** - * Get the relative time as a string - * - * @param time The time - * - * @return The relative time - */ - public static CharSequence getRelativeTimeSpanString(final long time) { - final long now = System.currentTimeMillis(); - if (now - time < DateUtils.MINUTE_IN_MILLIS) { - // Also fixes bug where posts appear in the future - return getContext().getResources().getText(R.string.posted_just_now); - } - - // Workaround for b/5657035. The platform method {@link DateUtils#getRelativeTimeSpan()} - // passes a null context to other platform methods. However, on some devices, this - // context is dereferenced when it shouldn't be and an NPE is thrown. We catch that - // here and use a slightly less precise time. - try { - return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE).toString(); - } catch (final NullPointerException npe) { - return getShortRelativeTimeSpanString(time); - } - } - - public static CharSequence getConversationTimeString(final long time) { - return getTimeString(time, true /*abbreviated*/, false /*minPeriodToday*/); - } - - public static CharSequence getMessageTimeString(final long time) { - return getTimeString(time, false /*abbreviated*/, false /*minPeriodToday*/); - } - - public static CharSequence getWidgetTimeString(final long time, final boolean abbreviated) { - return getTimeString(time, abbreviated, true /*minPeriodToday*/); - } - - public static CharSequence getFastScrollPreviewTimeString(final long time) { - return getTimeString(time, true /* abbreviated */, true /* minPeriodToday */); - } - - public static CharSequence getMessageDetailsTimeString(final long time) { - final Context context = getContext(); - int flags; - if (android.text.format.DateFormat.is24HourFormat(context)) { - flags = FORCE_24_HOUR; - } else { - flags = FORCE_12_HOUR; - } - return getOlderThanAYearTimestamp(time, - context.getResources().getConfiguration().locale, false /*abbreviated*/, - flags); - } - - private static CharSequence getTimeString(final long time, final boolean abbreviated, - final boolean minPeriodToday) { - final Context context = getContext(); - int flags; - if (android.text.format.DateFormat.is24HourFormat(context)) { - flags = FORCE_24_HOUR; - } else { - flags = FORCE_12_HOUR; - } - return getTimestamp(time, System.currentTimeMillis(), abbreviated, - context.getResources().getConfiguration().locale, flags, minPeriodToday); - } - - @VisibleForTesting - public static CharSequence getTimestamp(final long time, final long now, - final boolean abbreviated, final Locale locale, final int flags, - final boolean minPeriodToday) { - final long timeDiff = now - time; - - if (!minPeriodToday && timeDiff < DateUtils.MINUTE_IN_MILLIS) { - return getLessThanAMinuteOldTimeString(abbreviated); - } else if (!minPeriodToday && timeDiff < DateUtils.HOUR_IN_MILLIS) { - return getLessThanAnHourOldTimeString(timeDiff, flags); - } else if (getNumberOfDaysPassed(time, now) == 0) { - return getTodayTimeStamp(time, flags); - } else if (timeDiff < DateUtils.WEEK_IN_MILLIS) { - return getThisWeekTimestamp(time, locale, abbreviated, flags); - } else if (timeDiff < DateUtils.YEAR_IN_MILLIS) { - return getThisYearTimestamp(time, locale, abbreviated, flags); - } else { - return getOlderThanAYearTimestamp(time, locale, abbreviated, flags); - } - } - - private static CharSequence getLessThanAMinuteOldTimeString( - final boolean abbreviated) { - return getContext().getResources().getText( - abbreviated ? R.string.posted_just_now : R.string.posted_now); - } - - private static CharSequence getLessThanAnHourOldTimeString(final long timeDiff, - final int flags) { - final long count = (timeDiff / MINUTE_IN_MILLIS); - final String format = getContext().getResources().getQuantityString( - R.plurals.num_minutes_ago, (int) count); - return String.format(format, count); - } - - private static CharSequence getTodayTimeStamp(final long time, final int flags) { - return DateUtils.formatDateTime(getContext(), time, - DateUtils.FORMAT_SHOW_TIME | flags); - } - - private static CharSequence getExplicitFormattedTime(final long time, final int flags, - final String format24, final String format12) { - SimpleDateFormat formatter; - if ((flags & FORCE_24_HOUR) == FORCE_24_HOUR) { - formatter = new SimpleDateFormat(format24); - } else { - formatter = new SimpleDateFormat(format12); - } - return formatter.format(new Date(time)); - } - - private static CharSequence getThisWeekTimestamp(final long time, - final Locale locale, final boolean abbreviated, final int flags) { - final Context context = getContext(); - if (abbreviated) { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY | flags); - } else { - if (locale.equals(Locale.US)) { - return getExplicitFormattedTime(time, flags, "EEE HH:mm", "EEE h:mmaa"); - } else { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_ABBREV_WEEKDAY - | flags); - } - } - } - - private static CharSequence getThisYearTimestamp(final long time, final Locale locale, - final boolean abbreviated, final int flags) { - final Context context = getContext(); - if (abbreviated) { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH - | DateUtils.FORMAT_NO_YEAR | flags); - } else { - if (locale.equals(Locale.US)) { - return getExplicitFormattedTime(time, flags, "MMM d, HH:mm", "MMM d, h:mmaa"); - } else { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_ABBREV_MONTH - | DateUtils.FORMAT_NO_YEAR - | flags); - } - } - } - - private static CharSequence getOlderThanAYearTimestamp(final long time, - final Locale locale, final boolean abbreviated, final int flags) { - final Context context = getContext(); - if (abbreviated) { - if (locale.equals(Locale.US)) { - return getExplicitFormattedTime(time, flags, "M/d/yy", "M/d/yy"); - } else { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_NUMERIC_DATE); - } - } else { - if (locale.equals(Locale.US)) { - return getExplicitFormattedTime(time, flags, "M/d/yy, HH:mm", "M/d/yy, h:mmaa"); - } else { - return DateUtils.formatDateTime(context, time, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR - | flags); - } - } - } - - public static CharSequence getShortRelativeTimeSpanString(final long time) { - final long now = System.currentTimeMillis(); - final long duration = Math.abs(now - time); - - int resId; - long count; - - final Context context = getContext(); - - if (duration < HOUR_IN_MILLIS) { - count = duration / MINUTE_IN_MILLIS; - resId = R.plurals.num_minutes_ago; - } else if (duration < DAY_IN_MILLIS) { - count = duration / HOUR_IN_MILLIS; - resId = R.plurals.num_hours_ago; - } else if (duration < WEEK_IN_MILLIS) { - count = getNumberOfDaysPassed(time, now); - resId = R.plurals.num_days_ago; - } else { - // Although we won't be showing a time, there is a bug on some devices that use - // the passed in context. On these devices, passing in a {@code null} context - // here will generate an NPE. See b/5657035. - return DateUtils.formatDateRange(context, time, time, - DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_ABBREV_RELATIVE); - } - - final String format = context.getResources().getQuantityString(resId, (int) count); - return String.format(format, count); - } - - private static synchronized long getNumberOfDaysPassed(final long date1, final long date2) { - if (sThenTime == null) { - sThenTime = new Time(); - } - sThenTime.set(date1); - final int day1 = Time.getJulianDay(date1, sThenTime.gmtoff); - sThenTime.set(date2); - final int day2 = Time.getJulianDay(date2, sThenTime.gmtoff); - return Math.abs(day2 - day1); - } - - private static Time sThenTime; -} diff --git a/src/com/android/messaging/util/DebugUtils.java b/src/com/android/messaging/util/DebugUtils.java deleted file mode 100644 index f2c1d65..0000000 --- a/src/com/android/messaging/util/DebugUtils.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.Context; -import android.content.DialogInterface; -import android.media.MediaPlayer; -import android.os.Environment; -import android.telephony.SmsMessage; -import android.text.TextUtils; -import android.widget.ArrayAdapter; - -import com.android.messaging.R; -import com.android.messaging.datamodel.SyncManager; -import com.android.messaging.datamodel.action.DumpDatabaseAction; -import com.android.messaging.datamodel.action.LogTelephonyDatabaseAction; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.debug.DebugSmsMmsFromDumpFileDialogFragment; -import com.google.common.io.ByteStreams; - -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.StreamCorruptedException; - -public class DebugUtils { - private static final String TAG = "bugle.util.DebugUtils"; - - private static boolean sDebugNoise; - private static boolean sDebugClassZeroSms; - private static MediaPlayer [] sMediaPlayer; - private static final Object sLock = new Object(); - - public static final int DEBUG_SOUND_SERVER_REQUEST = 0; - public static final int DEBUG_SOUND_DB_OP = 1; - - public static void maybePlayDebugNoise(final Context context, final int sound) { - if (sDebugNoise) { - synchronized (sLock) { - try { - if (sMediaPlayer == null) { - sMediaPlayer = new MediaPlayer[2]; - sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST] = - MediaPlayer.create(context, R.raw.server_request_debug); - sMediaPlayer[DEBUG_SOUND_DB_OP] = - MediaPlayer.create(context, R.raw.db_op_debug); - sMediaPlayer[DEBUG_SOUND_DB_OP].setVolume(1.0F, 1.0F); - sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST].setVolume(0.3F, 0.3F); - } - if (sMediaPlayer[sound] != null) { - sMediaPlayer[sound].start(); - } - } catch (final IllegalArgumentException e) { - LogUtil.e(TAG, "MediaPlayer exception", e); - } catch (final SecurityException e) { - LogUtil.e(TAG, "MediaPlayer exception", e); - } catch (final IllegalStateException e) { - LogUtil.e(TAG, "MediaPlayer exception", e); - } - } - } - } - - public static boolean isDebugEnabled() { - return BugleGservices.get().getBoolean(BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES, - BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES_DEFAULT); - } - - public abstract static class DebugAction { - String mTitle; - public DebugAction(final String title) { - mTitle = title; - } - - @Override - public String toString() { - return mTitle; - } - - public abstract void run(); - } - - public static void showDebugOptions(final Activity host) { - final AlertDialog.Builder builder = new AlertDialog.Builder(host); - - final ArrayAdapter arrayAdapter = new ArrayAdapter( - host, android.R.layout.simple_list_item_1); - - arrayAdapter.add(new DebugAction("Dump Database") { - @Override - public void run() { - DumpDatabaseAction.dumpDatabase(); - } - }); - - arrayAdapter.add(new DebugAction("Log Telephony Data") { - @Override - public void run() { - LogTelephonyDatabaseAction.dumpDatabase(); - } - }); - - arrayAdapter.add(new DebugAction("Toggle Noise") { - @Override - public void run() { - sDebugNoise = !sDebugNoise; - } - }); - - arrayAdapter.add(new DebugAction("Force sync SMS") { - @Override - public void run() { - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1); - SyncManager.forceSync(); - } - }); - - arrayAdapter.add(new DebugAction("Sync SMS") { - @Override - public void run() { - SyncManager.sync(); - } - }); - - arrayAdapter.add(new DebugAction("Load SMS/MMS from dump file") { - @Override - public void run() { - new DebugSmsMmsDumpTask(host, - DebugSmsMmsFromDumpFileDialogFragment.ACTION_LOAD).executeOnThreadPool(); - } - }); - - arrayAdapter.add(new DebugAction("Email SMS/MMS dump file") { - @Override - public void run() { - new DebugSmsMmsDumpTask(host, - DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL).executeOnThreadPool(); - } - }); - - arrayAdapter.add(new DebugAction("MMS Config...") { - @Override - public void run() { - UIIntents.get().launchDebugMmsConfigActivity(host); - } - }); - - arrayAdapter.add(new DebugAction(sDebugClassZeroSms ? "Turn off Class 0 sms test" : - "Turn on Class Zero test") { - @Override - public void run() { - sDebugClassZeroSms = !sDebugClassZeroSms; - } - }); - - builder.setAdapter(arrayAdapter, - new android.content.DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface arg0, final int pos) { - arrayAdapter.getItem(pos).run(); - } - }); - - builder.create().show(); - } - - /** - * Task to list all the dump files and perform an action on it - */ - private static class DebugSmsMmsDumpTask extends SafeAsyncTask { - private final String mAction; - private final Activity mHost; - - public DebugSmsMmsDumpTask(final Activity host, final String action) { - mHost = host; - mAction = action; - } - - @Override - protected void onPostExecute(final String[] result) { - if (result == null || result.length < 1) { - return; - } - final FragmentManager fragmentManager = mHost.getFragmentManager(); - final FragmentTransaction ft = fragmentManager.beginTransaction(); - final DebugSmsMmsFromDumpFileDialogFragment dialog = - DebugSmsMmsFromDumpFileDialogFragment.newInstance(result, mAction); - dialog.show(fragmentManager, ""/*tag*/); - } - - @Override - protected String[] doInBackgroundTimed(final Void... params) { - final File dir = DebugUtils.getDebugFilesDir(); - return dir.list(new FilenameFilter() { - @Override - public boolean accept(final File dir, final String filename) { - return filename != null - && ((mAction == DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL - && filename.equals(DumpDatabaseAction.DUMP_NAME)) - || filename.startsWith(MmsUtils.MMS_DUMP_PREFIX) - || filename.startsWith(MmsUtils.SMS_DUMP_PREFIX)); - } - }); - } - } - - /** - * Dump the received raw SMS data into a file on external storage - * - * @param id The ID to use as part of the dump file name - * @param messages The raw SMS data - */ - public static void dumpSms(final long id, final android.telephony.SmsMessage[] messages, - final String format) { - try { - final String dumpFileName = MmsUtils.SMS_DUMP_PREFIX + Long.toString(id); - final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true); - if (dumpFile != null) { - final FileOutputStream fos = new FileOutputStream(dumpFile); - final DataOutputStream dos = new DataOutputStream(fos); - try { - final int chars = (TextUtils.isEmpty(format) ? 0 : format.length()); - dos.writeInt(chars); - if (chars > 0) { - dos.writeUTF(format); - } - dos.writeInt(messages.length); - for (final android.telephony.SmsMessage message : messages) { - final byte[] pdu = message.getPdu(); - dos.writeInt(pdu.length); - dos.write(pdu, 0, pdu.length); - } - dos.flush(); - } finally { - dos.close(); - ensureReadable(dumpFile); - } - } - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "dumpSms: " + e, e); - } - } - - /** - * Load MMS/SMS from the dump file - */ - public static SmsMessage[] retreiveSmsFromDumpFile(final String dumpFileName) { - SmsMessage[] messages = null; - final File inputFile = DebugUtils.getDebugFile(dumpFileName, false); - if (inputFile != null) { - FileInputStream fis = null; - DataInputStream dis = null; - try { - fis = new FileInputStream(inputFile); - dis = new DataInputStream(fis); - - // SMS dump - final int chars = dis.readInt(); - if (chars > 0) { - final String format = dis.readUTF(); - } - final int count = dis.readInt(); - final SmsMessage[] messagesTemp = new SmsMessage[count]; - for (int i = 0; i < count; i++) { - final int length = dis.readInt(); - final byte[] pdu = new byte[length]; - dis.read(pdu, 0, length); - messagesTemp[i] = SmsMessage.createFromPdu(pdu); - } - messages = messagesTemp; - } catch (final FileNotFoundException e) { - // Nothing to do - } catch (final StreamCorruptedException e) { - // Nothing to do - } catch (final IOException e) { - // Nothing to do - } finally { - if (dis != null) { - try { - dis.close(); - } catch (final IOException e) { - // Nothing to do - } - } - } - } - return messages; - } - - public static File getDebugFile(final String fileName, final boolean create) { - final File dir = getDebugFilesDir(); - final File file = new File(dir, fileName); - if (create && file.exists()) { - file.delete(); - } - return file; - } - - public static File getDebugFilesDir() { - final File dir = Environment.getExternalStorageDirectory(); - return dir; - } - - /** - * Load MMS/SMS from the dump file - */ - public static byte[] receiveFromDumpFile(final String dumpFileName) { - byte[] data = null; - try { - final File inputFile = getDebugFile(dumpFileName, false); - if (inputFile != null) { - final FileInputStream fis = new FileInputStream(inputFile); - final BufferedInputStream bis = new BufferedInputStream(fis); - try { - // dump file - data = ByteStreams.toByteArray(bis); - if (data == null || data.length < 1) { - LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: empty data"); - } - } finally { - bis.close(); - } - } - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: " + e, e); - } - return data; - } - - public static void ensureReadable(final File file) { - if (file.exists()){ - file.setReadable(true, false); - } - } - - /** - * Logs the name of the method that is currently executing, e.g. "MyActivity.onCreate". This is - * useful for surgically adding logs for tracing execution while debugging. - *

- * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead. - * However, this method is only executed on eng builds if DEBUG logs are loggable. - */ - public static void logCurrentMethod(String tag) { - if (!LogUtil.isLoggable(tag, LogUtil.DEBUG)) { - return; - } - StackTraceElement caller = getCaller(1); - if (caller == null) { - return; - } - String className = caller.getClassName(); - // Strip off the package name - int lastDot = className.lastIndexOf('.'); - if (lastDot > -1) { - className = className.substring(lastDot + 1); - } - LogUtil.d(tag, className + "." + caller.getMethodName()); - } - - /** - * Returns info about the calling method. The {@code depth} parameter controls how far back to - * go. For example, if foo() calls bar(), and bar() calls getCaller(0), it returns info about - * bar(). If bar() instead called getCaller(1), it would return info about foo(). And so on. - *

- * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead. - * It should only be used in production where necessary to gather context about an error or - * unexpected event (e.g. the {@link Assert} class uses it). - * - * @return stack frame information for the caller (if found); otherwise {@code null}. - */ - public static StackTraceElement getCaller(int depth) { - // If the signature of this method is changed, proguard.flags must be updated! - if (depth < 0) { - throw new IllegalArgumentException("depth cannot be negative"); - } - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - if (trace == null || trace.length < (depth + 2)) { - return null; - } - // The stack trace includes some methods we don't care about (e.g. this method). - // Walk down until we find this method, and then back up to the caller we're looking for. - for (int i = 0; i < trace.length - 1; i++) { - String methodName = trace[i].getMethodName(); - if ("getCaller".equals(methodName)) { - return trace[i + depth + 1]; - } - } - // Never found ourself in the stack?! - return null; - } - - /** - * Returns a boolean indicating whether ClassZero debugging is enabled. If enabled, any received - * sms is treated as if it were a class zero message and displayed by the ClassZeroActivity. - */ - public static boolean debugClassZeroSmsEnabled() { - return sDebugClassZeroSms; - } -} diff --git a/src/com/android/messaging/util/EmailAddress.java b/src/com/android/messaging/util/EmailAddress.java deleted file mode 100644 index 0c0dab9..0000000 --- a/src/com/android/messaging/util/EmailAddress.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import com.google.common.base.CharMatcher; - -/** - * Parsing the email address - */ -public final class EmailAddress { - private static final CharMatcher ANY_WHITESPACE = CharMatcher.anyOf( - " \t\n\r\f\u000B\u0085\u2028\u2029\u200D\uFFEF\uFFFD\uFFFE\uFFFF"); - private static final CharMatcher EMAIL_ALLOWED_CHARS = CharMatcher.inRange((char) 0, (char) 31) - .or(CharMatcher.is((char) 127)) - .or(CharMatcher.anyOf(" @,:<>")) - .negate(); - - /** - * Helper method that checks whether the input text is valid email address. - * TODO: This creates a new EmailAddress object each time - * Need to make it more lightweight by pulling out the validation code into a static method. - */ - public static boolean isValidEmail(final String emailText) { - return new EmailAddress(emailText).isValid(); - } - - /** - * Parses the specified email address. Internationalized addresses are treated as invalid. - * - * @param emailString A string representing just an email address. It should - * not contain any other tokens. "Name<foo@example.org>" won't be valid. - */ - public EmailAddress(final String emailString) { - this(emailString, false); - } - - /** - * Parses the specified email address. - * - * @param emailString A string representing just an email address. It should - * not contain any other tokens. "Name<foo@example.org>" won't be valid. - * @param i18n Accept an internationalized address if it is true. - */ - public EmailAddress(final String emailString, final boolean i18n) { - allowI18n = i18n; - valid = parseEmail(emailString); - } - - /** - * Parses the specified email address. Internationalized addresses are treated as invalid. - * - * @param user A string representing the username in the email prior to the '@' symbol - * @param host A string representing the host following the '@' symbol - */ - public EmailAddress(final String user, final String host) { - this(user, host, false); - } - - /** - * Parses the specified email address. - * - * @param user A string representing the username in the email prior to the '@' symbol - * @param host A string representing the host following the '@' symbol - * @param i18n Accept an internationalized address if it is true. - */ - public EmailAddress(final String user, final String host, final boolean i18n) { - allowI18n = i18n; - this.user = user; - setHost(host); - } - - protected boolean parseEmail(final String emailString) { - // check for null - if (emailString == null) { - return false; - } - - // Check for an '@' character. Get the last one, in case the local part is - // quoted. See http://b/1944742. - final int atIndex = emailString.lastIndexOf('@'); - if ((atIndex <= 0) || // no '@' character in the email address - // or @ on the first position - (atIndex == (emailString.length() - 1))) { // last character, no host - return false; - } - - user = emailString.substring(0, atIndex); - host = emailString.substring(atIndex + 1); - - return isValidInternal(); - } - - @Override - public String toString() { - return user + "@" + host; - } - - /** - * Ensure the email address is valid, conforming to current RFC2821 and - * RFC2822 guidelines (although some iffy characters, like ! and ;, are - * allowed because they are not technically prohibited in the RFC) - */ - private boolean isValidInternal() { - if ((user == null) || (host == null)) { - return false; - } - - if ((user.length() == 0) || (host.length() == 0)) { - return false; - } - - // check for white space in the host - if (ANY_WHITESPACE.indexIn(host) >= 0) { - return false; - } - - // ensure the host is above the minimum length - if (host.length() < 4) { - return false; - } - - final int firstDot = host.indexOf('.'); - - // ensure host contains at least one dot - if (firstDot == -1) { - return false; - } - - // check if the host contains two continuous dots. - if (host.indexOf("..") >= 0) { - return false; - } - - // check if the first host char is a dot. - if (host.charAt(0) == '.') { - return false; - } - - final int secondDot = host.indexOf(".", firstDot + 1); - - // if there's a dot at the end, there needs to be a second dot - if (host.charAt(host.length() - 1) == '.' && secondDot == -1) { - return false; - } - - // Host must not have any disallowed characters; allowI18n dictates whether - // host must be ASCII. - if (!EMAIL_ALLOWED_CHARS.matchesAllOf(host) - || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(host))) { - return false; - } - - if (user.startsWith("\"")) { - if (!isQuotedUserValid()) { - return false; - } - } else { - // check for white space in the user - if (ANY_WHITESPACE.indexIn(user) >= 0) { - return false; - } - - // the user cannot contain two continuous dots - if (user.indexOf("..") >= 0) { - return false; - } - - // User must not have any disallowed characters; allow I18n dictates whether - // user must be ASCII. - if (!EMAIL_ALLOWED_CHARS.matchesAllOf(user) - || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(user))) { - return false; - } - } - return true; - } - - private boolean isQuotedUserValid() { - final int limit = user.length() - 1; - if (limit < 1 || !user.endsWith("\"")) { - return false; - } - - // Unusual loop bounds (looking only at characters between the outer quotes, - // not at either quote character). Plus, i is manipulated within the loop. - for (int i = 1; i < limit; ++i) { - final char ch = user.charAt(i); - if (ch == '"' || ch == 127 - // No non-whitespace control chars: - || (ch < 32 && !ANY_WHITESPACE.matches(ch)) - // No non-ASCII chars, unless i18n is in effect: - || (ch >= 128 && !allowI18n)) { - return false; - } else if (ch == '\\') { - if (i + 1 < limit) { - ++i; // Skip the quoted character - } else { - // We have a trailing backslash -- so it can't be quoting anything. - return false; - } - } - } - - return true; - } - - @Override - public boolean equals(final Object otherObject) { - // Do an instance check first as an optimization. - if (this == otherObject) { - return true; - } - if (otherObject instanceof EmailAddress) { - final EmailAddress otherAddress = (EmailAddress) otherObject; - return toString().equals(otherAddress.toString()); - } - return false; - } - - @Override - public int hashCode() { - // Arbitrary hash code as a function of both host and user. - return toString().hashCode(); - } - - // accessors - public boolean isValid() { - return valid; - } - - public String getUser() { - return user; - } - - public String getHost() { - return host; - } - - // used to change the host on an email address and rechecks validity - - /** - * Changes the host name of the email address and rechecks the address' - * validity. Exercise caution when storing EmailAddress instances in - * hash-keyed collections. Calling setHost() with a different host name will - * change the return value of hashCode. - * - * @param hostName The new host name of the email address. - */ - public void setHost(final String hostName) { - host = hostName; - valid = isValidInternal(); - } - - protected boolean valid = false; - protected String user = null; - protected String host = null; - protected boolean allowI18n = false; -} diff --git a/src/com/android/messaging/util/FallbackStrategies.java b/src/com/android/messaging/util/FallbackStrategies.java deleted file mode 100644 index 57c208f..0000000 --- a/src/com/android/messaging/util/FallbackStrategies.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import java.util.ArrayList; -import java.util.List; - -/** - * Provides a generic and loose-coupled framework to execute one primary and multiple fallback - * strategies for solving a given task.

- * Basically, what would have been a nasty try-catch that's hard to separate and maintain: - *


- * try {
- *     // doSomething() that may fail.
- * } catch (Exception ex) {
- *     try {
- *         // fallback1() that may fail.
- *     } catch (Exception ex2) {
- *         try {
- *             // fallback2() that may fail.
- *         } catch (Exception ex3) {
- *             // ...
- *         }
- *     }
- * }
- * 
- * Now becomes:
- *

- * FallbackStrategies
- *      .startWith(something)
- *      .thenTry(fallback1)
- *      .thenTry(fallback2)
- *      .execute();
- * 
- */ -public class FallbackStrategies { - public interface Strategy { - Output execute(Input params) throws Exception; - } - - private final List> mChainedStrategies; - - private FallbackStrategies(final Strategy primaryStrategy) { - mChainedStrategies = new ArrayList>(); - mChainedStrategies.add(primaryStrategy); - } - - public static FallbackStrategies startWith( - final Strategy primaryStrategy) { - return new FallbackStrategies(primaryStrategy); - } - - public FallbackStrategies thenTry(final Strategy strategy) { - Assert.isFalse(mChainedStrategies.isEmpty()); - mChainedStrategies.add(strategy); - return this; - } - - public Output execute(final Input params) { - final int count = mChainedStrategies.size(); - for (int i = 0; i < count; i++) { - final Strategy strategy = mChainedStrategies.get(i); - try { - // If succeeds, this will directly return. - return strategy.execute(params); - } catch (Exception ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Exceptions occured when executing strategy " + - strategy + (i < count - 1 ? - ", attempting fallback " + mChainedStrategies.get(i + 1) : - ", and running out of fallbacks."), ex); - // This will fall through and continue with the next strategy (if any). - } - } - // Running out of strategies, return null. - // TODO: Should this accept user-defined fallback value other than null? - return null; - } -} diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java deleted file mode 100644 index 7c47ae9..0000000 --- a/src/com/android/messaging/util/FileUtil.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.webkit.MimeTypeMap; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.google.common.io.Files; - -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public class FileUtil { - /** Returns a new file name, ensuring that such a file does not already exist. */ - private static synchronized File getNewFile(File directory, String extension, - String fileNameFormat) throws IOException { - final Date date = new Date(System.currentTimeMillis()); - final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat); - final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension; - for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name. - final String newName = String.format(Locale.US, numberedFileNameFormat, i); - File testFile = new File(directory, newName); - if (!testFile.exists()) { - testFile.createNewFile(); - return testFile; - } - } - LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat); - return null; - } - - /** - * Creates an unused name to use for creating a new file. The format happens to be similar - * to that used by the Android camera application. - * - * @param directory directory that the file should be saved to - * @param contentType of the media being saved - * @return file name to be used for creating the new file. The caller is responsible for - * actually creating the file. - */ - public static File getNewFile(File directory, String contentType) throws IOException { - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String fileExtension = mimeTypeMap.getExtensionFromMimeType(contentType); - - final Context context = Factory.get().getApplicationContext(); - String fileNameFormat = context.getString(ContentType.isImageType(contentType) - ? R.string.new_image_file_name_format : R.string.new_file_name_format); - return getNewFile(directory, fileExtension, fileNameFormat); - } - - /** Delete everything below and including root */ - public static void removeFileOrDirectory(File root) { - removeFileOrDirectoryExcept(root, null); - } - - /** Delete everything below and including root except for the given file */ - public static void removeFileOrDirectoryExcept(File root, File exclude) { - if (root.exists()) { - if (root.isDirectory()) { - for (File file : root.listFiles()) { - if (exclude == null || !file.equals(exclude)) { - removeFileOrDirectoryExcept(file, exclude); - } - } - root.delete(); - } else if (root.isFile()) { - root.delete(); - } - } - } - - /** - * Move all files and folders under a directory into the target. - */ - public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) { - if (sourceDir.isDirectory() && targetDir.isDirectory()) { - if (isSameOrSubDirectory(sourceDir, targetDir)) { - LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " + - "directory is a parent of the target"); - return; - } - for (File file : sourceDir.listFiles()) { - if (file.isDirectory()) { - final File dirTarget = new File(targetDir, file.getName()); - dirTarget.mkdirs(); - moveAllContentUnderDirectory(file, dirTarget); - } else { - try { - final File fileTarget = new File(targetDir, file.getName()); - Files.move(file, fileTarget); - } catch (IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e); - // Try proceed with the next file. - } - } - } - } - } - - /** - * Checks, whether the child directory is the same as, or a sub-directory of the base - * directory. - */ - private static boolean isSameOrSubDirectory(File base, File child) { - try { - base = base.getCanonicalFile(); - child = child.getCanonicalFile(); - File parentFile = child; - while (parentFile != null) { - if (base.equals(parentFile)) { - return true; - } - parentFile = parentFile.getParentFile(); - } - return false; - } catch (IOException ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex); - return false; - } - } -} diff --git a/src/com/android/messaging/util/GifTranscoder.java b/src/com/android/messaging/util/GifTranscoder.java deleted file mode 100644 index 65413a0..0000000 --- a/src/com/android/messaging/util/GifTranscoder.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.text.format.Formatter; - -import com.google.common.base.Stopwatch; - -import java.io.File; -import java.util.concurrent.TimeUnit; - -/** - * Compresses a GIF so it can be sent via MMS. - *

- * The entry point lives in its own class, we can defer loading the native GIF transcoding library - * into memory until we actually need it. - */ -public class GifTranscoder { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static int MIN_HEIGHT = 100; - private static int MIN_WIDTH = 100; - - static { - System.loadLibrary("giftranscode"); - } - - public static boolean transcode(Context context, String filePath, String outFilePath) { - if (!isEnabled()) { - return false; - } - final long inputSize = new File(filePath).length(); - Stopwatch stopwatch = Stopwatch.createStarted(); - final boolean success = transcodeInternal(filePath, outFilePath); - stopwatch.stop(); - final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS); - final long outputSize = new File(outFilePath).length(); - final float compression = (inputSize > 0) ? ((float) outputSize / inputSize) : 0; - - if (success) { - LogUtil.i(TAG, String.format("Resized GIF (%s) in %d ms, %s => %s (%.0f%%)", - LogUtil.sanitizePII(filePath), - elapsedMs, - Formatter.formatShortFileSize(context, inputSize), - Formatter.formatShortFileSize(context, outputSize), - compression * 100.0f)); - } - return success; - } - - private static native boolean transcodeInternal(String filePath, String outFilePath); - - /** - * Estimates the size of a GIF transcoded from a GIF with the specified size. - */ - public static long estimateFileSizeAfterTranscode(long fileSize) { - // I tested transcoding on ~70 GIFs and found that the transcoded files are in general - // about 25-35% the size of the original. This compression ratio is very consistent for the - // class of GIFs we care about most: those converted from video clips and 1-3 MB in size. - return (long) (fileSize * 0.35f); - } - - public static boolean canBeTranscoded(int width, int height) { - if (!isEnabled()) { - return false; - } - return width >= MIN_WIDTH && height >= MIN_HEIGHT; - } - - private static boolean isEnabled() { - final boolean enabled = BugleGservices.get().getBoolean( - BugleGservicesKeys.ENABLE_GIF_TRANSCODING, - BugleGservicesKeys.ENABLE_GIF_TRANSCODING_DEFAULT); - if (!enabled) { - LogUtil.w(TAG, "GIF transcoding is disabled"); - } - return enabled; - } -} diff --git a/src/com/android/messaging/util/ImageUtils.java b/src/com/android/messaging/util/ImageUtils.java deleted file mode 100644 index 05d3678..0000000 --- a/src/com/android/messaging/util/ImageUtils.java +++ /dev/null @@ -1,908 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.app.ActivityManager; -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader.TileMode; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.MediaStore; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.view.View; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.media.ImageRequest; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.android.messaging.util.exif.ExifInterface; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.io.Files; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Arrays; - -public class ImageUtils { - private static final String TAG = LogUtil.BUGLE_TAG; - private static final int MAX_OOM_COUNT = 1; - private static final byte[] GIF87_HEADER = "GIF87a".getBytes(Charset.forName("US-ASCII")); - private static final byte[] GIF89_HEADER = "GIF89a".getBytes(Charset.forName("US-ASCII")); - - // Used for drawBitmapWithCircleOnCanvas. - // Default color is transparent for both circle background and stroke. - public static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = 0; - public static final int DEFAULT_CIRCLE_STROKE_COLOR = 0; - - private static volatile ImageUtils sInstance; - - public static ImageUtils get() { - if (sInstance == null) { - synchronized (ImageUtils.class) { - if (sInstance == null) { - sInstance = new ImageUtils(); - } - } - } - return sInstance; - } - - @VisibleForTesting - public static void set(final ImageUtils imageUtils) { - sInstance = imageUtils; - } - - /** - * Transforms a bitmap into a byte array. - * - * @param quality Value between 0 and 100 that the compressor uses to discern what quality the - * resulting bytes should be - * @param bitmap Bitmap to convert into bytes - * @return byte array of bitmap - */ - public static byte[] bitmapToBytes(final Bitmap bitmap, final int quality) - throws OutOfMemoryError { - boolean done = false; - int oomCount = 0; - byte[] imageBytes = null; - while (!done) { - try { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os); - imageBytes = os.toByteArray(); - done = true; - } catch (final OutOfMemoryError e) { - LogUtil.w(TAG, "OutOfMemory converting bitmap to bytes."); - oomCount++; - if (oomCount <= MAX_OOM_COUNT) { - Factory.get().reclaimMemory(); - } else { - done = true; - LogUtil.w(TAG, "Failed to convert bitmap to bytes. Out of Memory."); - } - throw e; - } - } - return imageBytes; - } - - /** - * Given the source bitmap and a canvas, draws the bitmap through a circular - * mask. Only draws a circle with diameter equal to the destination width. - * - * @param bitmap The source bitmap to draw. - * @param canvas The canvas to draw it on. - * @param source The source bound of the bitmap. - * @param dest The destination bound on the canvas. - * @param bitmapPaint Optional Paint object for the bitmap - * @param fillBackground when set, fill the circle with backgroundColor - * @param strokeColor draw a border outside the circle with strokeColor - */ - public static void drawBitmapWithCircleOnCanvas(final Bitmap bitmap, final Canvas canvas, - final RectF source, final RectF dest, @Nullable Paint bitmapPaint, - final boolean fillBackground, final int backgroundColor, int strokeColor) { - // Draw bitmap through shader first. - final BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP); - final Matrix matrix = new Matrix(); - - // Fit bitmap to bounds. - matrix.setRectToRect(source, dest, Matrix.ScaleToFit.CENTER); - - shader.setLocalMatrix(matrix); - - if (bitmapPaint == null) { - bitmapPaint = new Paint(); - } - - bitmapPaint.setAntiAlias(true); - if (fillBackground) { - bitmapPaint.setColor(backgroundColor); - canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint); - } - - bitmapPaint.setShader(shader); - canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint); - bitmapPaint.setShader(null); - - if (strokeColor != 0) { - final Paint stroke = new Paint(); - stroke.setAntiAlias(true); - stroke.setColor(strokeColor); - stroke.setStyle(Paint.Style.STROKE); - final float strokeWidth = 6f; - stroke.setStrokeWidth(strokeWidth); - canvas.drawCircle(dest.centerX(), - dest.centerX(), - dest.width() / 2f - stroke.getStrokeWidth() / 2f, - stroke); - } - } - - /** - * Sets a drawable to the background of a view. setBackgroundDrawable() is deprecated since - * JB and replaced by setBackground(). - */ - @SuppressWarnings("deprecation") - public static void setBackgroundDrawableOnView(final View view, final Drawable drawable) { - if (OsUtil.isAtLeastJB()) { - view.setBackground(drawable); - } else { - view.setBackgroundDrawable(drawable); - } - } - - /** - * Based on the input bitmap bounds given by BitmapFactory.Options, compute the required - * sub-sampling size for loading a scaled down version of the bitmap to the required size - * @param options a BitmapFactory.Options instance containing the bounds info of the bitmap - * @param reqWidth the desired width of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE. - * @param reqHeight the desired height of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE. - * @return - */ - public int calculateInSampleSize( - final BitmapFactory.Options options, final int reqWidth, final int reqHeight) { - // Raw height and width of image - final int height = options.outHeight; - final int width = options.outWidth; - int inSampleSize = 1; - - final boolean checkHeight = reqHeight != ImageRequest.UNSPECIFIED_SIZE; - final boolean checkWidth = reqWidth != ImageRequest.UNSPECIFIED_SIZE; - if ((checkHeight && height > reqHeight) || - (checkWidth && width > reqWidth)) { - - final int halfHeight = height / 2; - final int halfWidth = width / 2; - - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. - while ((!checkHeight || (halfHeight / inSampleSize) > reqHeight) - && (!checkWidth || (halfWidth / inSampleSize) > reqWidth)) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - private static final String[] MEDIA_CONTENT_PROJECTION = new String[] { - MediaStore.MediaColumns.MIME_TYPE - }; - - private static final int INDEX_CONTENT_TYPE = 0; - - @DoesNotRunOnMainThread - public static String getContentType(final ContentResolver cr, final Uri uri) { - // Figure out the content type of media. - String contentType = null; - Cursor cursor = null; - if (UriUtil.isMediaStoreUri(uri)) { - try { - cursor = cr.query(uri, MEDIA_CONTENT_PROJECTION, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - contentType = cursor.getString(INDEX_CONTENT_TYPE); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - if (contentType == null) { - // Last ditch effort to get the content type. Look at the file extension. - contentType = ContentType.getContentTypeFromExtension(uri.toString(), - ContentType.IMAGE_UNSPECIFIED); - } - return contentType; - } - - /** - * @param context Android context - * @param uri Uri to the image data - * @return The exif orientation value for the image in the specified uri - */ - public static int getOrientation(final Context context, final Uri uri) { - try { - return getOrientation(context.getContentResolver().openInputStream(uri)); - } catch (FileNotFoundException e) { - LogUtil.e(TAG, "getOrientation couldn't open: " + uri, e); - } - return android.media.ExifInterface.ORIENTATION_UNDEFINED; - } - - /** - * @param inputStream The stream to the image file. Closed on completion - * @return The exif orientation value for the image in the specified stream - */ - public static int getOrientation(final InputStream inputStream) { - int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED; - if (inputStream != null) { - try { - final ExifInterface exifInterface = new ExifInterface(); - exifInterface.readExif(inputStream); - final Integer orientationValue = - exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION); - if (orientationValue != null) { - orientation = orientationValue.intValue(); - } - } catch (IOException e) { - // If the image if GIF, PNG, or missing exif header, just use the defaults - } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } catch (IOException e) { - LogUtil.e(TAG, "getOrientation error closing input stream", e); - } - } - } - return orientation; - } - - /** - * Returns whether the resource is a GIF image. - */ - public static boolean isGif(String contentType, Uri contentUri) { - if (TextUtils.equals(contentType, ContentType.IMAGE_GIF)) { - return true; - } - if (ContentType.isImageType(contentType)) { - try { - ContentResolver contentResolver = Factory.get().getApplicationContext() - .getContentResolver(); - InputStream inputStream = contentResolver.openInputStream(contentUri); - return ImageUtils.isGif(inputStream); - } catch (Exception e) { - LogUtil.w(TAG, "Could not open GIF input stream", e); - } - } - // Assume anything with a non-image content type is not a GIF - return false; - } - - /** - * @param inputStream The stream to the image file. Closed on completion - * @return Whether the image stream represents a GIF - */ - public static boolean isGif(InputStream inputStream) { - if (inputStream != null) { - try { - byte[] gifHeaderBytes = new byte[6]; - int value = inputStream.read(gifHeaderBytes, 0, 6); - if (value == 6) { - return Arrays.equals(gifHeaderBytes, GIF87_HEADER) - || Arrays.equals(gifHeaderBytes, GIF89_HEADER); - } - } catch (IOException e) { - return false; - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // Ignore - } - } - } - return false; - } - - /** - * Read an image and compress it to particular max dimensions and size. - * Used to ensure images can fit in an MMS. - * TODO: This uses memory very inefficiently as it processes the whole image as a unit - * (rather than slice by slice) but system JPEG functions do not support slicing and dicing. - */ - public static class ImageResizer { - - /** - * The quality parameter which is used to compress JPEG images. - */ - private static final int IMAGE_COMPRESSION_QUALITY = 95; - /** - * The minimum quality parameter which is used to compress JPEG images. - */ - private static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50; - - /** - * Minimum factor to reduce quality value - */ - private static final double QUALITY_SCALE_DOWN_RATIO = 0.85f; - - /** - * Maximum passes through the resize loop before failing permanently - */ - private static final int NUMBER_OF_RESIZE_ATTEMPTS = 6; - - /** - * Amount to scale down the picture when it doesn't fit - */ - private static final float MIN_SCALE_DOWN_RATIO = 0.75f; - - /** - * When computing sampleSize target scaling of no more than this ratio - */ - private static final float MAX_TARGET_SCALE_FACTOR = 1.5f; - - - // Current sample size for subsampling image during initial decode - private int mSampleSize; - // Current bitmap holding initial decoded source image - private Bitmap mDecoded; - // If scaling is needed this holds the scaled bitmap (else should equal mDecoded) - private Bitmap mScaled; - // Current JPEG compression quality to use when compressing image - private int mQuality; - // Current factor to scale down decoded image before compressing - private float mScaleFactor; - // Flag keeping track of whether cache memory has been reclaimed - private boolean mHasReclaimedMemory; - - // Initial size of the image (typically provided but can be UNSPECIFIED_SIZE) - private int mWidth; - private int mHeight; - // Orientation params of image as read from EXIF data - private final ExifInterface.OrientationParams mOrientationParams; - // Matrix to undo orientation and scale at the same time - private final Matrix mMatrix; - // Size limit as provided by MMS library - private final int mWidthLimit; - private final int mHeightLimit; - private final int mByteLimit; - // Uri from which to read source image - private final Uri mUri; - // Application context - private final Context mContext; - // Cached value of bitmap factory options - private final BitmapFactory.Options mOptions; - private final String mContentType; - - private final int mMemoryClass; - - /** - * Return resized (compressed) image (else null) - * - * @param width The width of the image (if known) - * @param height The height of the image (if known) - * @param orientation The orientation of the image as an ExifInterface constant - * @param widthLimit The width limit, in pixels - * @param heightLimit The height limit, in pixels - * @param byteLimit The binary size limit, in bytes - * @param uri Uri to the image data - * @param context Needed to open the image - * @param contentType of image - * @return encoded image meeting size requirements else null - */ - public static byte[] getResizedImageData(final int width, final int height, - final int orientation, final int widthLimit, final int heightLimit, - final int byteLimit, final Uri uri, final Context context, - final String contentType) { - final ImageResizer resizer = new ImageResizer(width, height, orientation, - widthLimit, heightLimit, byteLimit, uri, context, contentType); - return resizer.resize(); - } - - /** - * Create and initialize an image resizer - */ - private ImageResizer(final int width, final int height, final int orientation, - final int widthLimit, final int heightLimit, final int byteLimit, final Uri uri, - final Context context, final String contentType) { - mWidth = width; - mHeight = height; - mOrientationParams = ExifInterface.getOrientationParams(orientation); - mMatrix = new Matrix(); - mWidthLimit = widthLimit; - mHeightLimit = heightLimit; - mByteLimit = byteLimit; - mUri = uri; - mWidth = width; - mContext = context; - mQuality = IMAGE_COMPRESSION_QUALITY; - mScaleFactor = 1.0f; - mHasReclaimedMemory = false; - mOptions = new BitmapFactory.Options(); - mOptions.inScaled = false; - mOptions.inDensity = 0; - mOptions.inTargetDensity = 0; - mOptions.inSampleSize = 1; - mOptions.inJustDecodeBounds = false; - mOptions.inMutable = false; - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - mMemoryClass = Math.max(16, am.getMemoryClass()); - mContentType = contentType; - } - - /** - * Try to compress the image - * - * @return encoded image meeting size requirements else null - */ - private byte[] resize() { - return ImageUtils.isGif(mContentType, mUri) ? resizeGifImage() : resizeStaticImage(); - } - - private byte[] resizeGifImage() { - byte[] bytesToReturn = null; - final String inputFilePath; - if (MediaScratchFileProvider.isMediaScratchSpaceUri(mUri)) { - inputFilePath = MediaScratchFileProvider.getFileFromUri(mUri).getAbsolutePath(); - } else { - if (!TextUtils.equals(mUri.getScheme(), ContentResolver.SCHEME_FILE)) { - Assert.fail("Expected a GIF file uri, but actual uri = " + mUri.toString()); - } - inputFilePath = mUri.getPath(); - } - - if (GifTranscoder.canBeTranscoded(mWidth, mHeight)) { - // Needed to perform the transcoding so that the gif can continue to play in the - // conversation while the sending is taking place - final Uri tmpUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("gif"); - final File outputFile = MediaScratchFileProvider.getFileFromUri(tmpUri); - final String outputFilePath = outputFile.getAbsolutePath(); - - final boolean success = - GifTranscoder.transcode(mContext, inputFilePath, outputFilePath); - if (success) { - try { - bytesToReturn = Files.toByteArray(outputFile); - } catch (IOException e) { - LogUtil.e(TAG, "Could not create FileInputStream with path of " - + outputFilePath, e); - } - } - - // Need to clean up the new file created to compress the gif - mContext.getContentResolver().delete(tmpUri, null, null); - } else { - // We don't want to transcode the gif because its image dimensions would be too - // small so just return the bytes of the original gif - try { - bytesToReturn = Files.toByteArray(new File(inputFilePath)); - } catch (IOException e) { - LogUtil.e(TAG, - "Could not create FileInputStream with path of " + inputFilePath, e); - } - } - - return bytesToReturn; - } - - private byte[] resizeStaticImage() { - if (!ensureImageSizeSet()) { - // Cannot read image size - return null; - } - // Find incoming image size - if (!canBeCompressed()) { - return null; - } - - // Decode image - if out of memory - reclaim memory and retry - try { - for (int attempts = 0; attempts < NUMBER_OF_RESIZE_ATTEMPTS; attempts++) { - final byte[] encoded = recodeImage(attempts); - - // Only return data within the limit - if (encoded != null && encoded.length <= mByteLimit) { - return encoded; - } else { - final int currentSize = (encoded == null ? 0 : encoded.length); - updateRecodeParameters(currentSize); - } - } - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "File disappeared during resizing"); - } finally { - // Release all bitmaps - if (mScaled != null && mScaled != mDecoded) { - mScaled.recycle(); - } - if (mDecoded != null) { - mDecoded.recycle(); - } - } - return null; - } - - /** - * Ensure that the width and height of the source image are known - * @return flag indicating whether size is known - */ - private boolean ensureImageSizeSet() { - if (mWidth == MessagingContentProvider.UNSPECIFIED_SIZE || - mHeight == MessagingContentProvider.UNSPECIFIED_SIZE) { - // First get the image data (compressed) - final ContentResolver cr = mContext.getContentResolver(); - InputStream inputStream = null; - // Find incoming image size - try { - mOptions.inJustDecodeBounds = true; - inputStream = cr.openInputStream(mUri); - BitmapFactory.decodeStream(inputStream, null, mOptions); - - mWidth = mOptions.outWidth; - mHeight = mOptions.outHeight; - mOptions.inJustDecodeBounds = false; - - return true; - } catch (final FileNotFoundException e) { - LogUtil.e(TAG, "Could not open file corresponding to uri " + mUri, e); - } catch (final NullPointerException e) { - LogUtil.e(TAG, "NPE trying to open the uri " + mUri, e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (final IOException e) { - // Nothing to do - } - } - } - - return false; - } - return true; - } - - /** - * Choose an initial subsamplesize that ensures the decoded image is no more than - * MAX_TARGET_SCALE_FACTOR bigger than largest supported image and that it is likely to - * compress to smaller than the target size (assuming compression down to 1 bit per pixel). - * @return whether the image can be down subsampled - */ - private boolean canBeCompressed() { - final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE); - - int imageHeight = mHeight; - int imageWidth = mWidth; - - // Assume can use half working memory to decode the initial image (4 bytes per pixel) - final int workingMemoryPixelLimit = (mMemoryClass * 1024 * 1024 / 8); - // Target 1 bits per pixel in final compressed image - final int finalSizePixelLimit = mByteLimit * 8; - // When choosing to halve the resolution - only do so the image will still be too big - // after scaling by MAX_TARGET_SCALE_FACTOR - final int heightLimitWithSlop = (int) (mHeightLimit * MAX_TARGET_SCALE_FACTOR); - final int widthLimitWithSlop = (int) (mWidthLimit * MAX_TARGET_SCALE_FACTOR); - final int pixelLimitWithSlop = (int) (finalSizePixelLimit * - MAX_TARGET_SCALE_FACTOR * MAX_TARGET_SCALE_FACTOR); - final int pixelLimit = Math.min(pixelLimitWithSlop, workingMemoryPixelLimit); - - int sampleSize = 1; - boolean fits = (imageHeight < heightLimitWithSlop && - imageWidth < widthLimitWithSlop && - imageHeight * imageWidth < pixelLimit); - - // Compare sizes to compute sub-sampling needed - while (!fits) { - sampleSize = sampleSize * 2; - // Note that recodeImage may try using mSampleSize * 2. Hence we use the factor of 4 - if (sampleSize >= (Integer.MAX_VALUE / 4)) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, String.format( - "Cannot resize image: widthLimit=%d heightLimit=%d byteLimit=%d " + - "imageWidth=%d imageHeight=%d", mWidthLimit, mHeightLimit, mByteLimit, - mWidth, mHeight)); - Assert.fail("Image cannot be resized"); // http://b/18926934 - return false; - } - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "computeInitialSampleSize: Increasing sampleSize to " + sampleSize - + " as h=" + imageHeight + " vs " + heightLimitWithSlop - + " w=" + imageWidth + " vs " + widthLimitWithSlop - + " p=" + imageHeight * imageWidth + " vs " + pixelLimit); - } - imageHeight = mHeight / sampleSize; - imageWidth = mWidth / sampleSize; - fits = (imageHeight < heightLimitWithSlop && - imageWidth < widthLimitWithSlop && - imageHeight * imageWidth < pixelLimit); - } - - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "computeInitialSampleSize: Initial sampleSize " + sampleSize - + " for h=" + imageHeight + " vs " + heightLimitWithSlop - + " w=" + imageWidth + " vs " + widthLimitWithSlop - + " p=" + imageHeight * imageWidth + " vs " + pixelLimit); - } - - mSampleSize = sampleSize; - return true; - } - - /** - * Recode the image from initial Uri to encoded JPEG - * @param attempt Attempt number - * @return encoded image - */ - private byte[] recodeImage(final int attempt) throws FileNotFoundException { - byte[] encoded = null; - try { - final ContentResolver cr = mContext.getContentResolver(); - final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE); - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: attempt=" + attempt - + " limit (w=" + mWidthLimit + " h=" + mHeightLimit + ") quality=" - + mQuality + " scale=" + mScaleFactor + " sampleSize=" + mSampleSize); - } - if (mScaled == null) { - if (mDecoded == null) { - mOptions.inSampleSize = mSampleSize; - final InputStream inputStream = cr.openInputStream(mUri); - mDecoded = BitmapFactory.decodeStream(inputStream, null, mOptions); - if (mDecoded == null) { - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: got empty decoded bitmap"); - } - return null; - } - } - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: decoded w,h=" - + mDecoded.getWidth() + "," + mDecoded.getHeight()); - } - // Make sure to scale the decoded image if dimension is not within limit - final int decodedWidth = mDecoded.getWidth(); - final int decodedHeight = mDecoded.getHeight(); - if (decodedWidth > mWidthLimit || decodedHeight > mHeightLimit) { - final float minScaleFactor = Math.max( - mWidthLimit == 0 ? 1.0f : - (float) decodedWidth / (float) mWidthLimit, - mHeightLimit == 0 ? 1.0f : - (float) decodedHeight / (float) mHeightLimit); - if (mScaleFactor < minScaleFactor) { - mScaleFactor = minScaleFactor; - } - } - if (mScaleFactor > 1.0 || mOrientationParams.rotation != 0) { - mMatrix.reset(); - mMatrix.postRotate(mOrientationParams.rotation); - mMatrix.postScale(mOrientationParams.scaleX / mScaleFactor, - mOrientationParams.scaleY / mScaleFactor); - mScaled = Bitmap.createBitmap(mDecoded, 0, 0, decodedWidth, decodedHeight, - mMatrix, false /* filter */); - if (mScaled == null) { - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: got empty scaled bitmap"); - } - return null; - } - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: scaled w,h=" - + mScaled.getWidth() + "," + mScaled.getHeight()); - } - } else { - mScaled = mDecoded; - } - } - // Now encode it at current quality - encoded = ImageUtils.bitmapToBytes(mScaled, mQuality); - if (encoded != null && logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: Encoded down to " + encoded.length + "@" - + mScaled.getWidth() + "/" + mScaled.getHeight() + "~" - + mQuality); - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData - image too big (OutOfMemoryError), will try " - + " with smaller scale factor"); - // fall through and keep trying with more compression - } - return encoded; - } - - /** - * When image recode fails this method updates compression parameters for the next attempt - * @param currentSize encoded image size (will be 0 if OOM) - */ - private void updateRecodeParameters(final int currentSize) { - final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE); - // Only return data within the limit - if (currentSize > 0 && - mQuality > MINIMUM_IMAGE_COMPRESSION_QUALITY) { - // First if everything succeeded but failed to hit target size - // Try quality proportioned to sqrt of size over size limit - mQuality = Math.max(MINIMUM_IMAGE_COMPRESSION_QUALITY, - Math.min((int) (mQuality * Math.sqrt((1.0 * mByteLimit) / currentSize)), - (int) (mQuality * QUALITY_SCALE_DOWN_RATIO))); - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: Retrying at quality " + mQuality); - } - } else if (currentSize > 0 && - mScaleFactor < 2.0 * MIN_SCALE_DOWN_RATIO * MIN_SCALE_DOWN_RATIO) { - // JPEG compression failed to hit target size - need smaller image - // First try scaling by a little (< factor of 2) just so long resulting scale down - // ratio is still significantly bigger than next subsampling step - // i.e. mScaleFactor/MIN_SCALE_DOWN_RATIO (new scaling factor) < - // 2.0 / MIN_SCALE_DOWN_RATIO (arbitrary limit) - mQuality = IMAGE_COMPRESSION_QUALITY; - mScaleFactor = mScaleFactor / MIN_SCALE_DOWN_RATIO; - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: Retrying at scale " + mScaleFactor); - } - // Release scaled bitmap to trigger rescaling - if (mScaled != null && mScaled != mDecoded) { - mScaled.recycle(); - } - mScaled = null; - } else if (currentSize <= 0 && !mHasReclaimedMemory) { - // Then before we subsample try cleaning up our cached memory - Factory.get().reclaimMemory(); - mHasReclaimedMemory = true; - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: Retrying after reclaiming memory "); - } - } else { - // Last resort - subsample image by another factor of 2 and try again - mSampleSize = mSampleSize * 2; - mQuality = IMAGE_COMPRESSION_QUALITY; - mScaleFactor = 1.0f; - if (logv) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, - "getResizedImageData: Retrying at sampleSize " + mSampleSize); - } - // Release all bitmaps to trigger subsampling - if (mScaled != null && mScaled != mDecoded) { - mScaled.recycle(); - } - mScaled = null; - if (mDecoded != null) { - mDecoded.recycle(); - mDecoded = null; - } - } - } - } - - /** - * Scales and center-crops a bitmap to the size passed in and returns the new bitmap. - * - * @param source Bitmap to scale and center-crop - * @param newWidth destination width - * @param newHeight destination height - * @return Bitmap scaled and center-cropped bitmap - */ - public static Bitmap scaleCenterCrop(final Bitmap source, final int newWidth, - final int newHeight) { - final int sourceWidth = source.getWidth(); - final int sourceHeight = source.getHeight(); - - // Compute the scaling factors to fit the new height and width, respectively. - // To cover the final image, the final scaling will be the bigger - // of these two. - final float xScale = (float) newWidth / sourceWidth; - final float yScale = (float) newHeight / sourceHeight; - final float scale = Math.max(xScale, yScale); - - // Now get the size of the source bitmap when scaled - final float scaledWidth = scale * sourceWidth; - final float scaledHeight = scale * sourceHeight; - - // Let's find out the upper left coordinates if the scaled bitmap - // should be centered in the new size give by the parameters - final float left = (newWidth - scaledWidth) / 2; - final float top = (newHeight - scaledHeight) / 2; - - // The target rectangle for the new, scaled version of the source bitmap will now - // be - final RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); - - // Finally, we create a new bitmap of the specified size and draw our new, - // scaled bitmap onto it. - final Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig()); - final Canvas canvas = new Canvas(dest); - canvas.drawBitmap(source, null, targetRect, null); - - return dest; - } - - /** - * The drawable can be a Nine-Patch. If we directly use the same drawable instance for each - * drawable of different sizes, then the drawable sizes would interfere with each other. The - * solution here is to create a new drawable instance for every time with the SAME - * ConstantState (i.e. sharing the same common state such as the bitmap, so that we don't have - * to recreate the bitmap resource), and apply the different properties on top (nine-patch - * size and color tint). - * - * TODO: we are creating new drawable instances here, but there are optimizations that - * can be made. For example, message bubbles shouldn't need the mutate() call and the - * play/pause buttons shouldn't need to create new drawable from the constant state. - */ - public static Drawable getTintedDrawable(final Context context, final Drawable drawable, - final int color) { - // For some reason occassionally drawables on JB has a null constant state - final Drawable.ConstantState constantStateDrawable = drawable.getConstantState(); - final Drawable retDrawable = (constantStateDrawable != null) - ? constantStateDrawable.newDrawable(context.getResources()).mutate() - : drawable; - retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - return retDrawable; - } - - /** - * Decodes image resource header and returns the image size. - */ - public static Rect decodeImageBounds(final Context context, final Uri imageUri) { - final ContentResolver cr = context.getContentResolver(); - try { - final InputStream inputStream = cr.openInputStream(imageUri); - if (inputStream != null) { - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(inputStream, null, options); - return new Rect(0, 0, options.outWidth, options.outHeight); - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // Do nothing. - } - } - } - } catch (FileNotFoundException e) { - LogUtil.e(TAG, "Couldn't open input stream for uri = " + imageUri); - } - return new Rect(0, 0, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE); - } -} diff --git a/src/com/android/messaging/util/ImeUtil.java b/src/com/android/messaging/util/ImeUtil.java deleted file mode 100644 index ab0df13..0000000 --- a/src/com/android/messaging/util/ImeUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.view.View; -import android.view.inputmethod.InputMethodManager; - -import com.google.common.annotations.VisibleForTesting; - -public class ImeUtil { - public interface ImeStateObserver { - void onImeStateChanged(boolean imeOpen); - } - - public interface ImeStateHost { - void onDisplayHeightChanged(int heightMeasureSpec); - void registerImeStateObserver(ImeUtil.ImeStateObserver observer); - void unregisterImeStateObserver(ImeUtil.ImeStateObserver observer); - boolean isImeOpen(); - } - - private static volatile ImeUtil sInstance; - - // Used to clear the static cached instance of ImeUtil during testing. This is necessary - // because a previous test may have installed a mocked instance (or vice versa). - public static void clearInstance() { - sInstance = null; - } - public static ImeUtil get() { - if (sInstance == null) { - synchronized (ImeUtil.class) { - if (sInstance == null) { - sInstance = new ImeUtil(); - } - } - } - return sInstance; - } - - @VisibleForTesting - public static void set(final ImeUtil imeUtil) { - sInstance = imeUtil; - } - - public void hideImeKeyboard(@NonNull final Context context, @NonNull final View v) { - Assert.notNull(context); - Assert.notNull(v); - - final InputMethodManager inputMethodManager = - (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0 /* flags */); - } - } - - public void showImeKeyboard(@NonNull final Context context, @NonNull final View v) { - Assert.notNull(context); - Assert.notNull(v); - - final InputMethodManager inputMethodManager = - (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - v.requestFocus(); - inputMethodManager.showSoftInput(v, 0 /* flags */); - } - } - - public static void hideSoftInput(@NonNull final Context context, @NonNull final View v) { - final InputMethodManager inputMethodManager = - (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0); - } -} diff --git a/src/com/android/messaging/util/LogSaver.java b/src/com/android/messaging/util/LogSaver.java deleted file mode 100644 index 7d1f2fd..0000000 --- a/src/com/android/messaging/util/LogSaver.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.os.Process; -import android.util.Log; - -import com.android.messaging.Factory; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.logging.FileHandler; -import java.util.logging.Formatter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Save the app's own log to dump along with adb bugreport - */ -public abstract class LogSaver { - /** - * Writes the accumulated log entries, from oldest to newest, to the specified PrintWriter. - * Log lines are emitted in much the same form as logcat -v threadtime -- specifically, - * lines will include a timestamp, pid, tid, level, and tag. - * - * @param writer The PrintWriter to output - */ - public abstract void dump(PrintWriter writer); - - /** - * Log a line - * - * @param level The log level to use - * @param tag The log tag - * @param msg The message of the log line - */ - public abstract void log(int level, String tag, String msg); - - /** - * Check if the LogSaver still matches the current Gservices settings - * - * @return true if matches, false otherwise - */ - public abstract boolean isCurrent(); - - private LogSaver() { - } - - public static LogSaver newInstance() { - final boolean persistent = BugleGservices.get().getBoolean( - BugleGservicesKeys.PERSISTENT_LOGSAVER, - BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT); - if (persistent) { - final int setSize = BugleGservices.get().getInt( - BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE, - BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT); - final int fileLimitBytes = BugleGservices.get().getInt( - BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES, - BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT); - return new DiskLogSaver(setSize, fileLimitBytes); - } else { - final int size = BugleGservices.get().getInt( - BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT, - BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT); - return new MemoryLogSaver(size); - } - } - - /** - * A circular in-memory log to be used to log potentially verbose logs. The logs will be - * persisted in memory in the application and can be dumped by various dump() methods. - * For example, adb shell dumpsys activity provider com.android.messaging. - * The dump will also show up in bugreports. - */ - private static final class MemoryLogSaver extends LogSaver { - /** - * Record to store a single log entry. Stores timestamp, tid, level, tag, and message. - * It can be reused when the circular log rolls over. This avoids creating new objects. - */ - private static class LogRecord { - int mTid; - String mLevelString; - long mTimeMillis; // from System.currentTimeMillis - String mTag; - String mMessage; - - LogRecord() { - } - - void set(int tid, int level, long time, String tag, String message) { - this.mTid = tid; - this.mTimeMillis = time; - this.mTag = tag; - this.mMessage = message; - this.mLevelString = getLevelString(level); - } - } - - private final int mSize; - private final CircularArray mLogList; - private final Object mLock; - - private final SimpleDateFormat mSdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - - public MemoryLogSaver(final int size) { - mSize = size; - mLogList = new CircularArray(size); - mLock = new Object(); - } - - @Override - public void dump(PrintWriter writer) { - int pid = Process.myPid(); - synchronized (mLock) { - for (int i = 0; i < mLogList.count(); i++) { - LogRecord rec = mLogList.get(i); - writer.println(String.format("%s %5d %5d %s %s: %s", - mSdf.format(rec.mTimeMillis), - pid, rec.mTid, rec.mLevelString, rec.mTag, rec.mMessage)); - } - } - } - - @Override - public void log(int level, String tag, String msg) { - synchronized (mLock) { - LogRecord rec = mLogList.getFree(); - if (rec == null) { - rec = new LogRecord(); - } - rec.set(Process.myTid(), level, System.currentTimeMillis(), tag, msg); - mLogList.add(rec); - } - } - - @Override - public boolean isCurrent() { - final boolean persistent = BugleGservices.get().getBoolean( - BugleGservicesKeys.PERSISTENT_LOGSAVER, - BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT); - if (persistent) { - return false; - } - final int size = BugleGservices.get().getInt( - BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT, - BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT); - return size == mSize; - } - } - - /** - * A persistent, on-disk log saver. It uses the standard Java util logger along with - * a rotation log file set to store the logs in app's local file directory "app_logs". - */ - private static final class DiskLogSaver extends LogSaver { - private static final String DISK_LOG_DIR_NAME = "logs"; - - private final int mSetSize; - private final int mFileLimitBytes; - private Logger mDiskLogger; - - public DiskLogSaver(final int setSize, final int fileLimitBytes) { - Assert.isTrue(setSize > 0); - Assert.isTrue(fileLimitBytes > 0); - mSetSize = setSize; - mFileLimitBytes = fileLimitBytes; - initDiskLog(); - } - - private static void clearDefaultHandlers(Logger logger) { - Assert.notNull(logger); - for (Handler handler : logger.getHandlers()) { - logger.removeHandler(handler); - } - } - - private void initDiskLog() { - mDiskLogger = Logger.getLogger(LogUtil.BUGLE_TAG); - // We don't want the default console handler - clearDefaultHandlers(mDiskLogger); - // Don't want duplicate print in system log - mDiskLogger.setUseParentHandlers(false); - // FileHandler manages the log files in a fixed rotation set - final File logDir = Factory.get().getApplicationContext().getDir( - DISK_LOG_DIR_NAME, 0/*mode*/); - FileHandler handler = null; - try { - handler = new FileHandler( - logDir + "/%g.log", mFileLimitBytes, mSetSize, true/*append*/); - } catch (Exception e) { - Log.e(LogUtil.BUGLE_TAG, "LogSaver: fail to init disk logger", e); - return; - } - final Formatter formatter = new Formatter() { - @Override - public String format(java.util.logging.LogRecord r) { - return r.getMessage(); - } - }; - handler.setFormatter(formatter); - handler.setLevel(Level.ALL); - mDiskLogger.addHandler(handler); - } - - @Override - public void dump(PrintWriter writer) { - for (int i = mSetSize - 1; i >= 0; i--) { - final File logDir = Factory.get().getApplicationContext().getDir( - DISK_LOG_DIR_NAME, 0/*mode*/); - final String logFilePath = logDir + "/" + i + ".log"; - try { - final File logFile = new File(logFilePath); - if (!logFile.exists()) { - continue; - } - final BufferedReader reader = new BufferedReader(new FileReader(logFile)); - for (String line; (line = reader.readLine()) != null;) { - line = line.trim(); - writer.println(line); - } - } catch (FileNotFoundException e) { - Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not find log file " + logFilePath); - } catch (IOException e) { - Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not read log file", e); - } - } - } - - @Override - public void log(int level, String tag, String msg) { - final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - mDiskLogger.info(String.format("%s %5d %5d %s %s: %s\n", - sdf.format(System.currentTimeMillis()), - Process.myPid(), Process.myTid(), getLevelString(level), tag, msg)); - } - - @Override - public boolean isCurrent() { - final boolean persistent = BugleGservices.get().getBoolean( - BugleGservicesKeys.PERSISTENT_LOGSAVER, - BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT); - if (!persistent) { - return false; - } - final int setSize = BugleGservices.get().getInt( - BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE, - BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT); - final int fileLimitBytes = BugleGservices.get().getInt( - BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES, - BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT); - return setSize == mSetSize && fileLimitBytes == mFileLimitBytes; - } - } - - private static String getLevelString(final int level) { - switch (level) { - case android.util.Log.DEBUG: - return "D"; - case android.util.Log.WARN: - return "W"; - case android.util.Log.INFO: - return "I"; - case android.util.Log.VERBOSE: - return "V"; - case android.util.Log.ERROR: - return "E"; - case android.util.Log.ASSERT: - return "A"; - default: - return "?"; - } - } -} diff --git a/src/com/android/messaging/util/LogUtil.java b/src/com/android/messaging/util/LogUtil.java deleted file mode 100644 index 021f39b..0000000 --- a/src/com/android/messaging/util/LogUtil.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -/** - * Log utility class. - */ -public class LogUtil { - public static final String BUGLE_TAG = "MessagingApp"; - public static final String PROFILE_TAG = "MessagingAppProf"; - public static final String BUGLE_DATABASE_TAG = "MessagingAppDb"; - public static final String BUGLE_DATABASE_PERF_TAG = "MessagingAppDbPerf"; - public static final String BUGLE_DATAMODEL_TAG = "MessagingAppDataModel"; - public static final String BUGLE_IMAGE_TAG = "MessagingAppImage"; - public static final String BUGLE_NOTIFICATIONS_TAG = "MessagingAppNotif"; - public static final String BUGLE_WIDGET_TAG = "MessagingAppWidget"; - - public static final int DEBUG = android.util.Log.DEBUG; - public static final int WARN = android.util.Log.WARN; - public static final int VERBOSE = android.util.Log.VERBOSE; - public static final int INFO = android.util.Log.INFO; - public static final int ERROR = android.util.Log.ERROR; - - // If this is non-null, DEBUG and higher logs will be tracked in-memory. It will not include - // VERBOSE logs. - private static LogSaver sDebugLogSaver; - private static volatile boolean sCaptureDebugLogs; - - /** - * Read Gservices to see if logging should be enabled. - */ - public static void refreshGservices(final BugleGservices gservices) { - sCaptureDebugLogs = gservices.getBoolean( - BugleGservicesKeys.ENABLE_LOG_SAVER, - BugleGservicesKeys.ENABLE_LOG_SAVER_DEFAULT); - if (sCaptureDebugLogs && (sDebugLogSaver == null || !sDebugLogSaver.isCurrent())) { - // We were not capturing logs before. We are now. - sDebugLogSaver = LogSaver.newInstance(); - } else if (!sCaptureDebugLogs && sDebugLogSaver != null) { - // We were capturing logs. We aren't anymore. - sDebugLogSaver = null; - } - } - - // This is called from FactoryImpl once the Gservices class is initialized. - public static void initializeGservices (final BugleGservices gservices) { - gservices.registerForChanges(new Runnable() { - @Override - public void run() { - refreshGservices(gservices); - } - }); - refreshGservices(gservices); - } - - /** - * Send a {@link #VERBOSE} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void v(final String tag, final String msg) { - println(android.util.Log.VERBOSE, tag, msg); - } - - /** - * Send a {@link #VERBOSE} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void v(final String tag, final String msg, final Throwable tr) { - println(android.util.Log.VERBOSE, tag, msg + '\n' - + android.util.Log.getStackTraceString(tr)); - } - - /** - * Send a {@link #DEBUG} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void d(final String tag, final String msg) { - println(android.util.Log.DEBUG, tag, msg); - } - - /** - * Send a {@link #DEBUG} log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void d(final String tag, final String msg, final Throwable tr) { - println(android.util.Log.DEBUG, tag, msg + '\n' - + android.util.Log.getStackTraceString(tr)); - } - - /** - * Send an {@link #INFO} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void i(final String tag, final String msg) { - println(android.util.Log.INFO, tag, msg); - } - - /** - * Send a {@link #INFO} log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void i(final String tag, final String msg, final Throwable tr) { - println(android.util.Log.INFO, tag, msg + '\n' - + android.util.Log.getStackTraceString(tr)); - } - - /** - * Send a {@link #WARN} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void w(final String tag, final String msg) { - println(android.util.Log.WARN, tag, msg); - } - - /** - * Send a {@link #WARN} log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void w(final String tag, final String msg, final Throwable tr) { - println(android.util.Log.WARN, tag, msg); - println(android.util.Log.WARN, tag, android.util.Log.getStackTraceString(tr)); - } - - /** - * Send an {@link #ERROR} log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void e(final String tag, final String msg) { - println(android.util.Log.ERROR, tag, msg); - } - - /** - * Send a {@link #ERROR} log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void e(final String tag, final String msg, final Throwable tr) { - println(android.util.Log.ERROR, tag, msg); - println(android.util.Log.ERROR, tag, android.util.Log.getStackTraceString(tr)); - } - - /** - * What a Terrible Failure: Report a condition that should never happen. - * The error will always be logged at level ASSERT with the call stack. - * Depending on system configuration, a report may be added to the - * {@link android.os.DropBoxManager} and/or the process may be terminated - * immediately with an error dialog. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - */ - public static void wtf(final String tag, final String msg) { - // Make sure this goes into our log buffer - println(android.util.Log.ASSERT, tag, "wtf\n" + msg); - android.util.Log.wtf(tag, msg, new Exception()); - } - - /** - * What a Terrible Failure: Report a condition that should never happen. - * The error will always be logged at level ASSERT with the call stack. - * Depending on system configuration, a report may be added to the - * {@link android.os.DropBoxManager} and/or the process may be terminated - * immediately with an error dialog. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - public static void wtf(final String tag, final String msg, final Throwable tr) { - // Make sure this goes into our log buffer - println(android.util.Log.ASSERT, tag, "wtf\n" + msg + '\n' + - android.util.Log.getStackTraceString(tr)); - android.util.Log.wtf(tag, msg, tr); - } - - /** - * Low-level logging call. - * @param level The priority/type of this log message - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - private static void println(final int level, final String tag, final String msg) { - android.util.Log.println(level, tag, msg); - - LogSaver serviceLog = sDebugLogSaver; - if (serviceLog != null && level >= android.util.Log.DEBUG) { - serviceLog.log(level, tag, msg); - } - } - - /** - * Save logging into LogSaver only, for dumping to bug report - * - * @param level The priority/type of this log message - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - public static void save(final int level, final String tag, final String msg) { - LogSaver serviceLog = sDebugLogSaver; - if (serviceLog != null) { - serviceLog.log(level, tag, msg); - } - } - - /** - * Checks to see whether or not a log for the specified tag is loggable at the specified level. - * See {@link android.util.Log#isLoggable(String, int)} for more discussion. - */ - public static boolean isLoggable(final String tag, final int level) { - return android.util.Log.isLoggable(tag, level); - } - - /** - * Returns text as is if {@value #BUGLE_TAG}'s log level is set to DEBUG or VERBOSE; - * returns "--" otherwise. Useful for log statements where we don't want to log - * various strings (e.g., usernames) with default logging to avoid leaking PII in logcat. - */ - public static String sanitizePII(final String text) { - if (text == null) { - return null; - } - - if (android.util.Log.isLoggable(BUGLE_TAG, android.util.Log.DEBUG)) { - return text; - } else { - return "Redacted-" + text.length(); - } - } - - public static void dump(java.io.PrintWriter out) { - final LogSaver logsaver = sDebugLogSaver; - if (logsaver != null) { - logsaver.dump(out); - } - } -} diff --git a/src/com/android/messaging/util/LoggingTimer.java b/src/com/android/messaging/util/LoggingTimer.java deleted file mode 100644 index d0d41ac..0000000 --- a/src/com/android/messaging/util/LoggingTimer.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.os.SystemClock; - -/** - * A utility timer that logs the execution time of operations - */ -public class LoggingTimer { - private static final int NO_WARN_LIMIT = -1; - - private final String mTag; - private final String mName; - private final long mWarnLimitMillis; - private long mStartMillis; - - public LoggingTimer(final String tag, final String name) { - this(tag, name, NO_WARN_LIMIT); - } - - public LoggingTimer(final String tag, final String name, final long warnLimitMillis) { - mTag = tag; - mName = name; - mWarnLimitMillis = warnLimitMillis; - } - - /** - * This method should be called at the start of the operation to be timed. - */ - public void start() { - mStartMillis = SystemClock.elapsedRealtime(); - - if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) { - LogUtil.v(mTag, "Timer start for " + mName); - } - } - - /** - * This method should be called at the end of the operation to be timed. It logs the time since - * the last call to {@link #start} - */ - public void stopAndLog() { - final long elapsedMs = SystemClock.elapsedRealtime() - mStartMillis; - - final String logMessage = String.format("Used %dms for %s", elapsedMs, mName); - - LogUtil.save(LogUtil.DEBUG, mTag, logMessage); - - if (mWarnLimitMillis != NO_WARN_LIMIT && elapsedMs > mWarnLimitMillis) { - LogUtil.w(mTag, logMessage); - } else if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) { - LogUtil.v(mTag, logMessage); - } - } -} diff --git a/src/com/android/messaging/util/LongSparseSet.java b/src/com/android/messaging/util/LongSparseSet.java deleted file mode 100644 index 8e2cfca..0000000 --- a/src/com/android/messaging/util/LongSparseSet.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.support.v4.util.LongSparseArray; - -/** - * A space saving set for long values using v4 compat LongSparseArray - */ -public class LongSparseSet { - private static final Object THE_ONLY_VALID_VALUE = new Object(); - private final LongSparseArray mSet = new LongSparseArray(); - - public LongSparseSet() { - } - - /** - * @param key The element to check - * @return True if the element is in the set, false otherwise - */ - public boolean contains(long key) { - if (mSet.get(key, null/*default*/) == THE_ONLY_VALID_VALUE) { - return true; - } - return false; - } - - /** - * Add an element to the set - * - * @param key The element to add - */ - public void add(long key) { - mSet.put(key, THE_ONLY_VALID_VALUE); - } - - /** - * Remove an element from the set - * - * @param key The element to remove - */ - public void remove(long key) { - mSet.delete(key); - } -} diff --git a/src/com/android/messaging/util/MaterialPalette.java b/src/com/android/messaging/util/MaterialPalette.java deleted file mode 100644 index 8dbacbf..0000000 --- a/src/com/android/messaging/util/MaterialPalette.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - - -public class MaterialPalette{ - public final int mPrimaryColor; - public final int mSecondaryColor; - - public MaterialPalette(final int primaryColor, final int secondaryColor) { - mPrimaryColor = primaryColor; - mSecondaryColor = secondaryColor; - } -} diff --git a/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java b/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java deleted file mode 100644 index b1078d1..0000000 --- a/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.ContentResolver; -import android.content.res.AssetFileDescriptor; -import android.graphics.Bitmap; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.text.TextUtils; - -import com.android.messaging.Factory; - -import java.io.IOException; - -/** - * Convenience wrapper for {@link MediaMetadataRetriever} to help with its eccentric error handling. - */ -public class MediaMetadataRetrieverWrapper { - private final MediaMetadataRetriever mRetriever = new MediaMetadataRetriever(); - - public MediaMetadataRetrieverWrapper() { - } - - public void setDataSource(Uri uri) throws IOException { - ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver(); - AssetFileDescriptor fd = resolver.openAssetFileDescriptor(uri, "r"); - if (fd == null) { - throw new IOException("openAssetFileDescriptor returned null for " + uri); - } - try { - mRetriever.setDataSource(fd.getFileDescriptor()); - } catch (RuntimeException e) { - release(); - throw new IOException(e); - } finally { - fd.close(); - } - } - - public int extractInteger(final int key, final int defaultValue) { - final String s = mRetriever.extractMetadata(key); - if (TextUtils.isEmpty(s)) { - return defaultValue; - } - return Integer.parseInt(s); - } - - public String extractMetadata(final int key) { - return mRetriever.extractMetadata(key); - } - - public Bitmap getFrameAtTime() { - return mRetriever.getFrameAtTime(); - } - - public Bitmap getFrameAtTime(final long timeUs) { - return mRetriever.getFrameAtTime(timeUs); - } - - public void release() { - try { - mRetriever.release(); - } catch (RuntimeException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "MediaMetadataRetriever.release failed", e); - } - } -} diff --git a/src/com/android/messaging/util/MediaUtil.java b/src/com/android/messaging/util/MediaUtil.java deleted file mode 100644 index f25354c..0000000 --- a/src/com/android/messaging/util/MediaUtil.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; - -import com.android.messaging.Factory; - -public abstract class MediaUtil { - public static interface OnCompletionListener { - public void onCompletion(); - } - - public static MediaUtil get() { - return Factory.get().getMediaUtil(); - } - - /** - * Play sound from local resources given a resource id. - */ - public abstract void playSound(final Context context, final int resId, - final OnCompletionListener completionListener); -} diff --git a/src/com/android/messaging/util/MediaUtilImpl.java b/src/com/android/messaging/util/MediaUtilImpl.java deleted file mode 100644 index 272a057..0000000 --- a/src/com/android/messaging/util/MediaUtilImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.AudioManager; -import android.media.MediaPlayer; - -/** - * Default implementation of MediaUtil - */ -public class MediaUtilImpl extends MediaUtil { - - @Override - public void playSound(final Context context, final int resId, - final OnCompletionListener completionListener) { - // We want to play at the media volume and not the ringer volume, but we do want to - // avoid playing sound when the ringer/notifications are silenced. This is used for - // in app sounds that are not critical and should not impact running silent but also - // shouldn't play at full ring volume if you want to hear your ringer but don't want - // to be annoyed with in-app volume. - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - try { - final MediaPlayer mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); - final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); - mediaPlayer.setDataSource( - afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); - afd.close(); - mediaPlayer.prepare(); - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(final MediaPlayer mp) { - if (completionListener != null) { - completionListener.onCompletion(); - } - mp.stop(); - mp.release(); - } - }); - mediaPlayer.seekTo(0); - mediaPlayer.start(); - return; - } catch (final Exception e) { - LogUtil.w("MediaUtilImpl", "Error playing sound id: " + resId, e); - } - if (completionListener != null) { - // Call the completion handler to not block functionality if audio play fails - completionListener.onCompletion(); - } - } -} \ No newline at end of file diff --git a/src/com/android/messaging/util/NotificationPlayer.java b/src/com/android/messaging/util/NotificationPlayer.java deleted file mode 100644 index a4ed44e..0000000 --- a/src/com/android/messaging/util/NotificationPlayer.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.net.Uri; -import android.os.Looper; -import android.os.PowerManager; -import android.os.SystemClock; - -import com.android.messaging.Factory; - -import java.util.LinkedList; - -/** - * This class is provides the same interface and functionality as android.media.AsyncPlayer - * with the following differences: - * - whenever audio is played, audio focus is requested, - * - whenever audio playback is stopped or the playback completed, audio focus is abandoned. - * - * This file has been copied from com.android.server.NotificationPlayer. The only modification is - * the addition of a volume parameter. Hopefully the framework will adapt AsyncPlayer to support - * all the functionality in this class, at which point this one can be deleted. - */ -public class NotificationPlayer implements OnCompletionListener { - private static final int PLAY = 1; - private static final int STOP = 2; - private static final boolean mDebug = false; - - private static final class Command { - int code; - Uri uri; - boolean looping; - int stream; - float volume; - long requestTime; - boolean releaseFocus; - - @Override - public String toString() { - return "{ code=" + code + " looping=" + looping + " stream=" + stream - + " uri=" + uri + " }"; - } - } - - private final LinkedList mCmdQueue = new LinkedList(); - - private Looper mLooper; - - /* - * Besides the use of audio focus, the only implementation difference between AsyncPlayer and - * NotificationPlayer resides in the creation of the MediaPlayer. For the completion callback, - * OnCompletionListener, to be called at the end of the playback, the MediaPlayer needs to - * be created with a looper running so its event handler is not null. - */ - private final class CreationAndCompletionThread extends Thread { - public Command mCmd; - public CreationAndCompletionThread(final Command cmd) { - super(); - mCmd = cmd; - } - - @Override - public void run() { - Looper.prepare(); - mLooper = Looper.myLooper(); - synchronized (this) { - final AudioManager audioManager = - (AudioManager) Factory.get().getApplicationContext() - .getSystemService(Context.AUDIO_SERVICE); - try { - final MediaPlayer player = new MediaPlayer(); - player.setAudioStreamType(mCmd.stream); - player.setDataSource(Factory.get().getApplicationContext(), mCmd.uri); - player.setLooping(mCmd.looping); - player.setVolume(mCmd.volume, mCmd.volume); - player.prepare(); - if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null) - && (mCmd.uri.getEncodedPath().length() > 0)) { - audioManager.requestAudioFocus(null, mCmd.stream, - mCmd.looping ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT - : AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); - } - player.setOnCompletionListener(NotificationPlayer.this); - player.start(); - if (mPlayer != null) { - mPlayer.release(); - } - mPlayer = player; - } catch (final Exception e) { - LogUtil.w(mTag, "error loading sound for " + mCmd.uri, e); - } - mAudioManager = audioManager; - this.notify(); - } - Looper.loop(); - } - } - - private void startSound(final Command cmd) { - // Preparing can be slow, so if there is something else - // is playing, let it continue until we're done, so there - // is less of a glitch. - try { - if (mDebug) { - LogUtil.d(mTag, "Starting playback"); - } - //----------------------------------- - // This is were we deviate from the AsyncPlayer implementation and create the - // MediaPlayer in a new thread with which we're synchronized - synchronized (mCompletionHandlingLock) { - // if another sound was already playing, it doesn't matter we won't get notified - // of the completion, since only the completion notification of the last sound - // matters - if ((mLooper != null) - && (mLooper.getThread().getState() != Thread.State.TERMINATED)) { - mLooper.quit(); - } - mCompletionThread = new CreationAndCompletionThread(cmd); - synchronized (mCompletionThread) { - mCompletionThread.start(); - mCompletionThread.wait(); - } - } - //----------------------------------- - - final long delay = SystemClock.elapsedRealtime() - cmd.requestTime; - if (delay > 1000) { - LogUtil.w(mTag, "Notification sound delayed by " + delay + "msecs"); - } - } catch (final Exception e) { - LogUtil.w(mTag, "error loading sound for " + cmd.uri, e); - } - } - - private void stopSound(final Command cmd) { - if (mPlayer == null) { - return; - } - final long delay = SystemClock.elapsedRealtime() - cmd.requestTime; - if (delay > 1000) { - LogUtil.w(mTag, "Notification stop delayed by " + delay + "msecs"); - } - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; - if (cmd.releaseFocus && mAudioManager != null) { - mAudioManager.abandonAudioFocus(null); - } - mAudioManager = null; - if ((mLooper != null) && (mLooper.getThread().getState() != Thread.State.TERMINATED)) { - mLooper.quit(); - } - } - - private final class CmdThread extends java.lang.Thread { - CmdThread() { - super("NotificationPlayer-" + mTag); - } - - @Override - public void run() { - while (true) { - Command cmd = null; - - synchronized (mCmdQueue) { - if (mDebug) { - LogUtil.d(mTag, "RemoveFirst"); - } - cmd = mCmdQueue.removeFirst(); - } - - switch (cmd.code) { - case PLAY: - if (mDebug) { - LogUtil.d(mTag, "PLAY"); - } - startSound(cmd); - break; - case STOP: - if (mDebug) { - LogUtil.d(mTag, "STOP"); - } - stopSound(cmd); - break; - } - - synchronized (mCmdQueue) { - if (mCmdQueue.size() == 0) { - // nothing left to do, quit - // doing this check after we're done prevents the case where they - // added it during the operation from spawning two threads and - // trying to do them in parallel. - mThread = null; - releaseWakeLock(); - return; - } - } - } - } - } - - @Override - public void onCompletion(final MediaPlayer mp) { - if (mAudioManager != null) { - mAudioManager.abandonAudioFocus(null); - } - // if there are no more sounds to play, end the Looper to listen for media completion - synchronized (mCmdQueue) { - if (mCmdQueue.size() == 0) { - synchronized (mCompletionHandlingLock) { - if (mLooper != null) { - mLooper.quit(); - } - mCompletionThread = null; - } - } - } - } - - private String mTag; - private CmdThread mThread; - private CreationAndCompletionThread mCompletionThread; - private final Object mCompletionHandlingLock = new Object(); - private MediaPlayer mPlayer; - private PowerManager.WakeLock mWakeLock; - private AudioManager mAudioManager; - - // The current state according to the caller. Reality lags behind - // because of the asynchronous nature of this class. - private int mState = STOP; - - /** - * Construct a NotificationPlayer object. - * - * @param tag a string to use for debugging - */ - public NotificationPlayer(final String tag) { - if (tag != null) { - mTag = tag; - } else { - mTag = "NotificationPlayer"; - } - } - - /** - * Start playing the sound. It will actually start playing at some - * point in the future. There are no guarantees about latency here. - * Calling this before another audio file is done playing will stop - * that one and start the new one. - * - * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)}) - * @param looping Whether the audio should loop forever. - * (see {@link MediaPlayer#setLooping(boolean)}) - * @param stream the AudioStream to use. - * (see {@link MediaPlayer#setAudioStreamType(int)}) - * @param volume The volume at which to play this sound, as a fraction of the system volume for - * the relevant stream type. A value of 1 is the maximum and means play at the system - * volume with no attenuation. - */ - public void play(final Uri uri, final boolean looping, final int stream, final float volume) { - final Command cmd = new Command(); - cmd.requestTime = SystemClock.elapsedRealtime(); - cmd.code = PLAY; - cmd.uri = uri; - cmd.looping = looping; - cmd.stream = stream; - cmd.volume = volume; - synchronized (mCmdQueue) { - enqueueLocked(cmd); - mState = PLAY; - } - } - - /** Same as calling stop(true) */ - public void stop() { - stop(true); - } - - /** - * Stop a previously played sound. It can't be played again or unpaused - * at this point. Calling this multiple times has no ill effects. - * @param releaseAudioFocus whether to release audio focus - */ - public void stop(final boolean releaseAudioFocus) { - synchronized (mCmdQueue) { - // This check allows stop to be called multiple times without starting - // a thread that ends up doing nothing. - if (mState != STOP) { - final Command cmd = new Command(); - cmd.requestTime = SystemClock.elapsedRealtime(); - cmd.code = STOP; - cmd.releaseFocus = releaseAudioFocus; - enqueueLocked(cmd); - mState = STOP; - } - } - } - - private void enqueueLocked(final Command cmd) { - mCmdQueue.add(cmd); - if (mThread == null) { - acquireWakeLock(); - mThread = new CmdThread(); - mThread.start(); - } - } - - /** - * We want to hold a wake lock while we do the prepare and play. The stop probably is - * optional, but it won't hurt to have it too. The problem is that if you start a sound - * while you're holding a wake lock (e.g. an alarm starting a notification), you want the - * sound to play, but if the CPU turns off before mThread gets to work, it won't. The - * simplest way to deal with this is to make it so there is a wake lock held while the - * thread is starting or running. You're going to need the WAKE_LOCK permission if you're - * going to call this. - * - * This must be called before the first time play is called. - * - * @hide - */ - public void setUsesWakeLock() { - if (mWakeLock != null || mThread != null) { - // if either of these has happened, we've already played something. - // and our releases will be out of sync. - throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock - + " mThread=" + mThread); - } - final PowerManager pm = (PowerManager) Factory.get().getApplicationContext() - .getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag); - } - - private void acquireWakeLock() { - if (mWakeLock != null) { - mWakeLock.acquire(); - } - } - - private void releaseWakeLock() { - if (mWakeLock != null) { - mWakeLock.release(); - } - } -} - diff --git a/src/com/android/messaging/util/OsUtil.java b/src/com/android/messaging/util/OsUtil.java deleted file mode 100644 index e45a63c..0000000 --- a/src/com/android/messaging/util/OsUtil.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.UserHandle; -import android.os.UserManager; - -import com.android.messaging.Factory; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.Set; - -/** - * Android OS version utilities - */ -public class OsUtil { - private static boolean sIsAtLeastICS_MR1; - private static boolean sIsAtLeastJB; - private static boolean sIsAtLeastJB_MR1; - private static boolean sIsAtLeastJB_MR2; - private static boolean sIsAtLeastKLP; - private static boolean sIsAtLeastL; - private static boolean sIsAtLeastL_MR1; - private static boolean sIsAtLeastM; - - private static Boolean sIsSecondaryUser = null; - - static { - final int v = getApiVersion(); - sIsAtLeastICS_MR1 = v >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; - sIsAtLeastJB = v >= android.os.Build.VERSION_CODES.JELLY_BEAN; - sIsAtLeastJB_MR1 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - sIsAtLeastJB_MR2 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; - sIsAtLeastKLP = v >= android.os.Build.VERSION_CODES.KITKAT; - sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP; - sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1; - sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M; - } - - /** - * @return True if the version of Android that we're running on is at least Ice Cream Sandwich - * MR1 (API level 15). - */ - public static boolean isAtLeastICS_MR1() { - return sIsAtLeastICS_MR1; - } - - /** - * @return True if the version of Android that we're running on is at least Jelly Bean - * (API level 16). - */ - public static boolean isAtLeastJB() { - return sIsAtLeastJB; - } - - /** - * @return True if the version of Android that we're running on is at least Jelly Bean MR1 - * (API level 17). - */ - public static boolean isAtLeastJB_MR1() { - return sIsAtLeastJB_MR1; - } - - /** - * @return True if the version of Android that we're running on is at least Jelly Bean MR2 - * (API level 18). - */ - public static boolean isAtLeastJB_MR2() { - return sIsAtLeastJB_MR2; - } - - /** - * @return True if the version of Android that we're running on is at least KLP - * (API level 19). - */ - public static boolean isAtLeastKLP() { - return sIsAtLeastKLP; - } - - /** - * @return True if the version of Android that we're running on is at least L - * (API level 21). - */ - public static boolean isAtLeastL() { - return sIsAtLeastL; - } - - /** - * @return True if the version of Android that we're running on is at least L MR1 - * (API level 22). - */ - public static boolean isAtLeastL_MR1() { - return sIsAtLeastL_MR1; - } - - /** - * @return True if the version of Android that we're running on is at least M - * (API level 23). - */ - public static boolean isAtLeastM() { - return sIsAtLeastM; - } - - /** - * @return The Android API version of the OS that we're currently running on. - */ - public static int getApiVersion() { - return android.os.Build.VERSION.SDK_INT; - } - - public static boolean isSecondaryUser() { - if (sIsSecondaryUser == null) { - final Context context = Factory.get().getApplicationContext(); - boolean isSecondaryUser = false; - - // Only check for newer devices (but not the nexus 10) - if (OsUtil.sIsAtLeastJB_MR1 && !"Nexus 10".equals(Build.MODEL)) { - final UserHandle uh = android.os.Process.myUserHandle(); - final UserManager userManager = - (UserManager) context.getSystemService(Context.USER_SERVICE); - if (userManager != null) { - final long userSerialNumber = userManager.getSerialNumberForUser(uh); - isSecondaryUser = (0 != userSerialNumber); - } - } - sIsSecondaryUser = isSecondaryUser; - } - return sIsSecondaryUser; - } - - /** - * Creates a joined string from a Set using the given delimiter. - * @param values - * @param delimiter - * @return - */ - public static String joinFromSetWithDelimiter( - final Set values, final String delimiter) { - if (values != null) { - final StringBuilder joinedStringBuilder = new StringBuilder(); - boolean firstValue = true; - for (final String value : values) { - if (firstValue) { - firstValue = false; - } else { - joinedStringBuilder.append(delimiter); - } - joinedStringBuilder.append(value); - } - return joinedStringBuilder.toString(); - } - return null; - } - - private static Hashtable sPermissions = new Hashtable(); - - /** - * Check if the app has the specified permission. If it does not, the app needs to use - * {@link android.app.Activity#requestPermission}. Note that if it - * returns true, it cannot return false in the same process as the OS kills the process when - * any permission is revoked. - * @param permission A permission from {@link android.Manifest.permission} - */ - public static boolean hasPermission(final String permission) { - if (OsUtil.isAtLeastM()) { - // It is safe to cache the PERMISSION_GRANTED result as the process gets killed if the - // user revokes the permission setting. However, PERMISSION_DENIED should not be - // cached as the process does not get killed if the user enables the permission setting. - if (!sPermissions.containsKey(permission) - || sPermissions.get(permission) == PackageManager.PERMISSION_DENIED) { - final Context context = Factory.get().getApplicationContext(); - final int permissionState = context.checkSelfPermission(permission); - sPermissions.put(permission, permissionState); - } - return sPermissions.get(permission) == PackageManager.PERMISSION_GRANTED; - } else { - return true; - } - } - - /** Does the app have all the specified permissions */ - public static boolean hasPermissions(final String[] permissions) { - for (final String permission : permissions) { - if (!hasPermission(permission)) { - return false; - } - } - return true; - } - - public static boolean hasPhonePermission() { - return hasPermission(Manifest.permission.READ_PHONE_STATE); - } - - public static boolean hasSmsPermission() { - return hasPermission(Manifest.permission.READ_SMS); - } - - public static boolean hasLocationPermission() { - return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION); - } - - - public static boolean hasStoragePermission() { - // Note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are granted or denied - // together. - return OsUtil.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE); - } - - public static boolean hasRecordAudioPermission() { - return OsUtil.hasPermission(Manifest.permission.RECORD_AUDIO); - } - - /** - * Returns array with the set of permissions that have not been granted from the given set. - * The array will be empty if the app has all of the specified permissions. Note that calling - * {@link Activity#requestPermissions} for an already granted permission can prompt the user - * again, and its up to the app to only request permissions that are missing. - */ - public static String[] getMissingPermissions(final String[] permissions) { - final ArrayList missingList = new ArrayList(); - for (final String permission : permissions) { - if (!hasPermission(permission)) { - missingList.add(permission); - } - } - - final String[] missingArray = new String[missingList.size()]; - missingList.toArray(missingArray); - return missingArray; - } - - private static String[] sRequiredPermissions = new String[] { - // Required to read existing SMS threads - Manifest.permission.READ_SMS, - // Required for knowing the phone number, number of SIMs, etc. - Manifest.permission.READ_PHONE_STATE, - // This is not strictly required, but simplifies the contact picker scenarios - Manifest.permission.READ_CONTACTS, - }; - - /** Does the app have the minimum set of permissions required to operate. */ - public static boolean hasRequiredPermissions() { - return hasPermissions(sRequiredPermissions); - } - - public static String[] getMissingRequiredPermissions() { - return getMissingPermissions(sRequiredPermissions); - } -} diff --git a/src/com/android/messaging/util/PendingIntentConstants.java b/src/com/android/messaging/util/PendingIntentConstants.java deleted file mode 100644 index 1a594c5..0000000 --- a/src/com/android/messaging/util/PendingIntentConstants.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - - -public class PendingIntentConstants { - // Notifications - public static final int SMS_NOTIFICATION_ID = 0; - public static final int SMS_SECONDARY_USER_NOTIFICATION_ID = 1; - public static final int MSG_SEND_ERROR = 2; - public static final int SMS_STORAGE_LOW_NOTIFICATION_ID = 3; - - // Request codes - public static final int UPDATE_NOTIFICATIONS_ALARM_ACTION_ID = 100; - - public static final int MIN_ASSIGNED_REQUEST_CODE = 1001; - - // Logging - private static final String TAG = LogUtil.BUGLE_TAG; - private static final boolean VERBOSE = false; - - // Internal Constants - private static final String NOTIFICATION_REQUEST_CODE_PREFS = "notificationRequestCodes.v1"; - private static final String REQUEST_CODE_DELIMITER = "|"; - private static final String MAX_REQUEST_CODE_KEY = "maxRequestCode"; -} diff --git a/src/com/android/messaging/util/PhoneUtils.java b/src/com/android/messaging/util/PhoneUtils.java deleted file mode 100644 index 726f083..0000000 --- a/src/com/android/messaging/util/PhoneUtils.java +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.provider.Settings; -import android.provider.Telephony; -import android.support.v4.util.ArrayMap; -import android.telephony.PhoneNumberUtils; -import android.telephony.SmsManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.sms.MmsSmsUtils; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; - -/** - * This class abstracts away platform dependency of calling telephony related - * platform APIs, mostly involving TelephonyManager, SubscriptionManager and - * a bit of SmsManager. - * - * The class instance can only be obtained via the get(int subId) method parameterized - * by a SIM subscription ID. On pre-L_MR1, the subId is not used and it has to be - * the default subId (-1). - * - * A convenient getDefault() method is provided for default subId (-1) on any platform - */ -public abstract class PhoneUtils { - private static final String TAG = LogUtil.BUGLE_TAG; - - private static final int MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT = 6; - - private static final List EMPTY_SUBSCRIPTION_LIST = new ArrayList<>(); - - // The canonical phone number cache - // Each country gets its own cache. The following maps from ISO country code to - // the country's cache. Each cache maps from original phone number to canonicalized phone - private static final ArrayMap> sCanonicalPhoneNumberCache = - new ArrayMap<>(); - - protected final Context mContext; - protected final TelephonyManager mTelephonyManager; - protected final int mSubId; - - public PhoneUtils(int subId) { - mSubId = subId; - mContext = Factory.get().getApplicationContext(); - mTelephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - } - - /** - * Get the SIM's country code - * - * @return the country code on the SIM - */ - public abstract String getSimCountry(); - - /** - * Get number of SIM slots - * - * @return the SIM slot count - */ - public abstract int getSimSlotCount(); - - /** - * Get SIM's carrier name - * - * @return the carrier name of the SIM - */ - public abstract String getCarrierName(); - - /** - * Check if there is SIM inserted on the device - * - * @return true if there is SIM inserted, false otherwise - */ - public abstract boolean hasSim(); - - /** - * Check if the SIM is roaming - * - * @return true if the SIM is in romaing state, false otherwise - */ - public abstract boolean isRoaming(); - - /** - * Get the MCC and MNC in integer of the SIM's provider - * - * @return an array of two ints, [0] is the MCC code and [1] is the MNC code - */ - public abstract int[] getMccMnc(); - - /** - * Get the mcc/mnc string - * - * @return the text of mccmnc string - */ - public abstract String getSimOperatorNumeric(); - - /** - * Get the SIM's self raw number, i.e. not canonicalized - * - * @param allowOverride Whether to use the app's setting to override the self number - * @return the original self number - * @throws IllegalStateException if no active subscription on L-MR1+ - */ - public abstract String getSelfRawNumber(final boolean allowOverride); - - /** - * Returns the "effective" subId, or the subId used in the context of actual messages, - * conversations and subscription-specific settings, for the given "nominal" sub id. - * - * For pre-L-MR1 platform, this should always be - * {@value com.android.messaging.datamodel.data.ParticipantData#DEFAULT_SELF_SUB_ID}; - * - * On the other hand, for L-MR1 and above, DEFAULT_SELF_SUB_ID will be mapped to the system - * default subscription id for SMS. - * - * @param subId The input subId - * @return the real subId if we can convert - */ - public abstract int getEffectiveSubId(int subId); - - /** - * Returns the number of active subscriptions in the device. - */ - public abstract int getActiveSubscriptionCount(); - - /** - * Get {@link SmsManager} instance - * - * @return the relevant SmsManager instance based on OS version and subId - */ - public abstract SmsManager getSmsManager(); - - /** - * Get the default SMS subscription id - * - * @return the default sub ID - */ - public abstract int getDefaultSmsSubscriptionId(); - - /** - * Returns if there's currently a system default SIM selected for sending SMS. - */ - public abstract boolean getHasPreferredSmsSim(); - - /** - * For L_MR1, system may return a negative subId. Convert this into our own - * subId, so that we consistently use -1 for invalid or default. - * - * see b/18629526 and b/18670346 - * - * @param intent The push intent from system - * @param extraName The name of the sub id extra - * @return the subId that is valid and meaningful for the app - */ - public abstract int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName); - - /** - * Get the subscription_id column value from a telephony provider cursor - * - * @param cursor The database query cursor - * @param subIdIndex The index of the subId column in the cursor - * @return the subscription_id column value from the cursor - */ - public abstract int getSubIdFromTelephony(Cursor cursor, int subIdIndex); - - /** - * Check if data roaming is enabled - * - * @return true if data roaming is enabled, false otherwise - */ - public abstract boolean isDataRoamingEnabled(); - - /** - * Check if mobile data is enabled - * - * @return true if mobile data is enabled, false otherwise - */ - public abstract boolean isMobileDataEnabled(); - - /** - * Get the set of self phone numbers, all normalized - * - * @return the set of normalized self phone numbers - */ - public abstract HashSet getNormalizedSelfNumbers(); - - /** - * This interface packages methods should only compile on L_MR1. - * This is needed to make unit tests happy when mockito tries to - * mock these methods. Calling on these methods on L_MR1 requires - * an extra invocation of toMr1(). - */ - public interface LMr1 { - /** - * Get this SIM's information. Only applies to L_MR1 above - * - * @return the subscription info of the SIM - */ - public abstract SubscriptionInfo getActiveSubscriptionInfo(); - - /** - * Get the list of active SIMs in system. Only applies to L_MR1 above - * - * @return the list of subscription info for all inserted SIMs - */ - public abstract List getActiveSubscriptionInfoList(); - - /** - * Register subscription change listener. Only applies to L_MR1 above - * - * @param listener The listener to register - */ - public abstract void registerOnSubscriptionsChangedListener( - SubscriptionManager.OnSubscriptionsChangedListener listener); - } - - /** - * The PhoneUtils class for pre L_MR1 - */ - public static class PhoneUtilsPreLMR1 extends PhoneUtils { - private final ConnectivityManager mConnectivityManager; - - public PhoneUtilsPreLMR1() { - super(ParticipantData.DEFAULT_SELF_SUB_ID); - mConnectivityManager = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - } - - @Override - public String getSimCountry() { - final String country = mTelephonyManager.getSimCountryIso(); - if (TextUtils.isEmpty(country)) { - return null; - } - return country.toUpperCase(); - } - - @Override - public int getSimSlotCount() { - // Don't support MSIM pre-L_MR1 - return 1; - } - - @Override - public String getCarrierName() { - return mTelephonyManager.getNetworkOperatorName(); - } - - @Override - public boolean hasSim() { - return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT; - } - - @Override - public boolean isRoaming() { - return mTelephonyManager.isNetworkRoaming(); - } - - @Override - public int[] getMccMnc() { - final String mccmnc = mTelephonyManager.getSimOperator(); - int mcc = 0; - int mnc = 0; - try { - mcc = Integer.parseInt(mccmnc.substring(0, 3)); - mnc = Integer.parseInt(mccmnc.substring(3)); - } catch (Exception e) { - LogUtil.w(TAG, "PhoneUtils.getMccMnc: invalid string " + mccmnc, e); - } - return new int[]{mcc, mnc}; - } - - @Override - public String getSimOperatorNumeric() { - return mTelephonyManager.getSimOperator(); - } - - @Override - public String getSelfRawNumber(final boolean allowOverride) { - if (allowOverride) { - final String userDefinedNumber = getNumberFromPrefs(mContext, - ParticipantData.DEFAULT_SELF_SUB_ID); - if (!TextUtils.isEmpty(userDefinedNumber)) { - return userDefinedNumber; - } - } - return mTelephonyManager.getLine1Number(); - } - - @Override - public int getEffectiveSubId(int subId) { - Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId); - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @Override - public SmsManager getSmsManager() { - return SmsManager.getDefault(); - } - - @Override - public int getDefaultSmsSubscriptionId() { - Assert.fail("PhoneUtils.getDefaultSmsSubscriptionId(): not supported before L MR1"); - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @Override - public boolean getHasPreferredSmsSim() { - // SIM selection is not supported pre-L_MR1. - return true; - } - - @Override - public int getActiveSubscriptionCount() { - return hasSim() ? 1 : 0; - } - - @Override - public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) { - // Pre-L_MR1 always returns the default id - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @Override - public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) { - // No subscription_id column before L_MR1 - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @Override - @SuppressWarnings("deprecation") - public boolean isDataRoamingEnabled() { - boolean dataRoamingEnabled = false; - final ContentResolver cr = mContext.getContentResolver(); - if (OsUtil.isAtLeastJB_MR1()) { - dataRoamingEnabled = - (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0); - } else { - dataRoamingEnabled = - (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0); - } - return dataRoamingEnabled; - } - - @Override - public boolean isMobileDataEnabled() { - boolean mobileDataEnabled = false; - try { - final Class cmClass = mConnectivityManager.getClass(); - final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled"); - method.setAccessible(true); // Make the method callable - // get the setting for "mobile data" - mobileDataEnabled = (Boolean) method.invoke(mConnectivityManager); - } catch (final Exception e) { - LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e); - } - return mobileDataEnabled; - } - - @Override - public HashSet getNormalizedSelfNumbers() { - final HashSet numbers = new HashSet<>(); - numbers.add(getCanonicalForSelf(true/*allowOverride*/)); - return numbers; - } - } - - /** - * The PhoneUtils class for L_MR1 - */ - public static class PhoneUtilsLMR1 extends PhoneUtils implements LMr1 { - private final SubscriptionManager mSubscriptionManager; - - public PhoneUtilsLMR1(final int subId) { - super(subId); - mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext()); - } - - @Override - public String getSimCountry() { - final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); - if (subInfo != null) { - final String country = subInfo.getCountryIso(); - if (TextUtils.isEmpty(country)) { - return null; - } - return country.toUpperCase(); - } - return null; - } - - @Override - public int getSimSlotCount() { - return mSubscriptionManager.getActiveSubscriptionInfoCountMax(); - } - - @Override - public String getCarrierName() { - final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); - if (subInfo != null) { - final CharSequence displayName = subInfo.getDisplayName(); - if (!TextUtils.isEmpty(displayName)) { - return displayName.toString(); - } - final CharSequence carrierName = subInfo.getCarrierName(); - if (carrierName != null) { - return carrierName.toString(); - } - } - return null; - } - - @Override - public boolean hasSim() { - return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0; - } - - @Override - public boolean isRoaming() { - return mSubscriptionManager.isNetworkRoaming(mSubId); - } - - @Override - public int[] getMccMnc() { - int mcc = 0; - int mnc = 0; - final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); - if (subInfo != null) { - mcc = subInfo.getMcc(); - mnc = subInfo.getMnc(); - } - return new int[]{mcc, mnc}; - } - - @Override - public String getSimOperatorNumeric() { - // For L_MR1 we return the canonicalized (xxxxxx) string - return getMccMncString(getMccMnc()); - } - - @Override - public String getSelfRawNumber(final boolean allowOverride) { - if (allowOverride) { - final String userDefinedNumber = getNumberFromPrefs(mContext, mSubId); - if (!TextUtils.isEmpty(userDefinedNumber)) { - return userDefinedNumber; - } - } - - final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); - if (subInfo != null) { - String phoneNumber = subInfo.getNumber(); - if (TextUtils.isEmpty(phoneNumber) && LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - LogUtil.d(TAG, "SubscriptionInfo phone number for self is empty!"); - } - return phoneNumber; - } - LogUtil.w(TAG, "PhoneUtils.getSelfRawNumber: subInfo is null for " + mSubId); - throw new IllegalStateException("No active subscription"); - } - - @Override - public SubscriptionInfo getActiveSubscriptionInfo() { - try { - final SubscriptionInfo subInfo = - mSubscriptionManager.getActiveSubscriptionInfo(mSubId); - if (subInfo == null) { - if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { - // This is possible if the sub id is no longer available. - LogUtil.d(TAG, "PhoneUtils.getActiveSubscriptionInfo(): empty sub info for " - + mSubId); - } - } - return subInfo; - } catch (Exception e) { - LogUtil.e(TAG, "PhoneUtils.getActiveSubscriptionInfo: system exception for " - + mSubId, e); - } - return null; - } - - @Override - public List getActiveSubscriptionInfoList() { - final List subscriptionInfos = - mSubscriptionManager.getActiveSubscriptionInfoList(); - if (subscriptionInfos != null) { - return subscriptionInfos; - } - return EMPTY_SUBSCRIPTION_LIST; - } - - @Override - public int getEffectiveSubId(int subId) { - if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) { - return getDefaultSmsSubscriptionId(); - } - return subId; - } - - @Override - public void registerOnSubscriptionsChangedListener( - SubscriptionManager.OnSubscriptionsChangedListener listener) { - mSubscriptionManager.addOnSubscriptionsChangedListener(listener); - } - - @Override - public SmsManager getSmsManager() { - return SmsManager.getSmsManagerForSubscriptionId(mSubId); - } - - @Override - public int getDefaultSmsSubscriptionId() { - final int systemDefaultSubId = SmsManager.getDefaultSmsSubscriptionId(); - if (systemDefaultSubId < 0) { - // Always use -1 for any negative subId from system - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - return systemDefaultSubId; - } - - @Override - public boolean getHasPreferredSmsSim() { - return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID; - } - - @Override - public int getActiveSubscriptionCount() { - return mSubscriptionManager.getActiveSubscriptionInfoCount(); - } - - @Override - public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) { - return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName, - ParticipantData.DEFAULT_SELF_SUB_ID)); - } - - private int getEffectiveIncomingSubIdFromSystem(int subId) { - if (subId < 0) { - if (mSubscriptionManager.getActiveSubscriptionInfoCount() > 1) { - // For multi-SIM device, we can not decide which SIM to use if system - // does not know either. So just make it the invalid sub id. - return ParticipantData.DEFAULT_SELF_SUB_ID; - } - // For single-SIM device, it must come from the only SIM we have - return getDefaultSmsSubscriptionId(); - } - return subId; - } - - @Override - public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) { - return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex)); - } - - @Override - public boolean isDataRoamingEnabled() { - final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); - if (subInfo == null) { - // There is nothing we can do if system give us empty sub info - LogUtil.e(TAG, "PhoneUtils.isDataRoamingEnabled: system return empty sub info for " - + mSubId); - return false; - } - return subInfo.getDataRoaming() != SubscriptionManager.DATA_ROAMING_DISABLE; - } - - @Override - public boolean isMobileDataEnabled() { - boolean mobileDataEnabled = false; - try { - final Class cmClass = mTelephonyManager.getClass(); - final Method method = cmClass.getDeclaredMethod("getDataEnabled", Integer.TYPE); - method.setAccessible(true); // Make the method callable - // get the setting for "mobile data" - mobileDataEnabled = (Boolean) method.invoke( - mTelephonyManager, Integer.valueOf(mSubId)); - } catch (final Exception e) { - LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e); - } - return mobileDataEnabled; - - } - - @Override - public HashSet getNormalizedSelfNumbers() { - final HashSet numbers = new HashSet<>(); - for (SubscriptionInfo info : getActiveSubscriptionInfoList()) { - numbers.add(PhoneUtils.get(info.getSubscriptionId()).getCanonicalForSelf( - true/*allowOverride*/)); - } - return numbers; - } - } - - /** - * A convenient get() method that uses the default SIM. Use this when SIM is - * not relevant, e.g. isDefaultSmsApp - * - * @return an instance of PhoneUtils for default SIM - */ - public static PhoneUtils getDefault() { - return Factory.get().getPhoneUtils(ParticipantData.DEFAULT_SELF_SUB_ID); - } - - /** - * Get an instance of PhoneUtils associated with a specific SIM, which is also platform - * specific. - * - * @param subId The SIM's subscription ID - * @return the instance - */ - public static PhoneUtils get(int subId) { - return Factory.get().getPhoneUtils(subId); - } - - public LMr1 toLMr1() { - if (OsUtil.isAtLeastL_MR1()) { - return (LMr1) this; - } else { - Assert.fail("PhoneUtils.toLMr1(): invalid OS version"); - return null; - } - } - - /** - * Check if this device supports SMS - * - * @return true if SMS is supported, false otherwise - */ - public boolean isSmsCapable() { - return mTelephonyManager.isSmsCapable(); - } - - /** - * Check if this device supports voice calling - * - * @return true if voice calling is supported, false otherwise - */ - public boolean isVoiceCapable() { - return mTelephonyManager.isVoiceCapable(); - } - - /** - * Get the ISO country code from system locale setting - * - * @return the ISO country code from system locale - */ - private static String getLocaleCountry() { - final String country = Locale.getDefault().getCountry(); - if (TextUtils.isEmpty(country)) { - return null; - } - return country.toUpperCase(); - } - - /** - * Get ISO country code from the SIM, if not available, fall back to locale - * - * @return SIM or locale ISO country code - */ - public String getSimOrDefaultLocaleCountry() { - String country = getSimCountry(); - if (country == null) { - country = getLocaleCountry(); - } - return country; - } - - // Get or set the cache of canonicalized phone numbers for a specific country - private static ArrayMap getOrAddCountryMapInCacheLocked(String country) { - if (country == null) { - country = ""; - } - ArrayMap countryMap = sCanonicalPhoneNumberCache.get(country); - if (countryMap == null) { - countryMap = new ArrayMap<>(); - sCanonicalPhoneNumberCache.put(country, countryMap); - } - return countryMap; - } - - // Get canonicalized phone number from cache - private static String getCanonicalFromCache(final String phoneText, String country) { - synchronized (sCanonicalPhoneNumberCache) { - final ArrayMap countryMap = getOrAddCountryMapInCacheLocked(country); - return countryMap.get(phoneText); - } - } - - // Put canonicalized phone number into cache - private static void putCanonicalToCache(final String phoneText, String country, - final String canonical) { - synchronized (sCanonicalPhoneNumberCache) { - final ArrayMap countryMap = getOrAddCountryMapInCacheLocked(country); - countryMap.put(phoneText, canonical); - } - } - - /** - * Utility method to parse user input number into standard E164 number. - * - * @param phoneText Phone number text as input by user. - * @param country ISO country code based on which to parse the number. - * @return E164 phone number. Returns null in case parsing failed. - */ - private static String getValidE164Number(final String phoneText, final String country) { - final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - try { - final PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country); - if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) { - return phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164); - } - } catch (final NumberParseException e) { - LogUtil.e(TAG, "PhoneUtils.getValidE164Number(): Not able to parse phone number " - + LogUtil.sanitizePII(phoneText) + " for country " + country); - } - return null; - } - - /** - * Canonicalize phone number using system locale country - * - * @param phoneText The phone number to canonicalize - * @return the canonicalized number - */ - public String getCanonicalBySystemLocale(final String phoneText) { - return getCanonicalByCountry(phoneText, getLocaleCountry()); - } - - /** - * Canonicalize phone number using SIM's country, may fall back to system locale country - * if SIM country can not be obtained - * - * @param phoneText The phone number to canonicalize - * @return the canonicalized number - */ - public String getCanonicalBySimLocale(final String phoneText) { - return getCanonicalByCountry(phoneText, getSimOrDefaultLocaleCountry()); - } - - /** - * Canonicalize phone number using a country code. - * This uses an internal cache per country to speed up. - * - * @param phoneText The phone number to canonicalize - * @param country The ISO country code to use - * @return the canonicalized number, or the original number if can't be parsed - */ - private String getCanonicalByCountry(final String phoneText, final String country) { - Assert.notNull(phoneText); - - String canonicalNumber = getCanonicalFromCache(phoneText, country); - if (canonicalNumber != null) { - return canonicalNumber; - } - canonicalNumber = getValidE164Number(phoneText, country); - if (canonicalNumber == null) { - // If we can't normalize this number, we just use the display string number. - // This is possible for short codes and other non-localizable numbers. - canonicalNumber = phoneText; - } - putCanonicalToCache(phoneText, country, canonicalNumber); - return canonicalNumber; - } - - /** - * Canonicalize the self (per SIM) phone number - * - * @param allowOverride whether to use the override number in app settings - * @return the canonicalized self phone number - */ - public String getCanonicalForSelf(final boolean allowOverride) { - String selfNumber = null; - try { - selfNumber = getSelfRawNumber(allowOverride); - } catch (IllegalStateException e) { - // continue; - } - if (selfNumber == null) { - return ""; - } - return getCanonicalBySimLocale(selfNumber); - } - - /** - * Get the SIM's phone number in NATIONAL format with only digits, used in sending - * as LINE1NOCOUNTRYCODE macro in mms_config - * - * @return all digits national format number of the SIM - */ - public String getSimNumberNoCountryCode() { - String selfNumber = null; - try { - selfNumber = getSelfRawNumber(false/*allowOverride*/); - } catch (IllegalStateException e) { - // continue - } - if (selfNumber == null) { - selfNumber = ""; - } - final String country = getSimCountry(); - final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - try { - final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country); - if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) { - return phoneNumberUtil - .format(phoneNumber, PhoneNumberFormat.NATIONAL) - .replaceAll("\\D", ""); - } - } catch (final NumberParseException e) { - LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number " - + LogUtil.sanitizePII(selfNumber) + " for country " + country); - } - return selfNumber; - - } - - /** - * Format a phone number for displaying, using system locale country. - * If the country code matches between the system locale and the input phone number, - * it will be formatted into NATIONAL format, otherwise, the INTERNATIONAL format - * - * @param phoneText The original phone text - * @return formatted number - */ - public String formatForDisplay(final String phoneText) { - // Only format a valid number which length >=6 - if (TextUtils.isEmpty(phoneText) || - phoneText.replaceAll("\\D", "").length() < MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT) { - return phoneText; - } - final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - final String systemCountry = getLocaleCountry(); - final int systemCountryCode = phoneNumberUtil.getCountryCodeForRegion(systemCountry); - try { - final PhoneNumber parsedNumber = phoneNumberUtil.parse(phoneText, systemCountry); - final PhoneNumberFormat phoneNumberFormat = - (systemCountryCode > 0 && parsedNumber.getCountryCode() == systemCountryCode) ? - PhoneNumberFormat.NATIONAL : PhoneNumberFormat.INTERNATIONAL; - return phoneNumberUtil.format(parsedNumber, phoneNumberFormat); - } catch (NumberParseException e) { - LogUtil.e(TAG, "PhoneUtils.formatForDisplay: invalid phone number " - + LogUtil.sanitizePII(phoneText) + " with country " + systemCountry); - return phoneText; - } - } - - /** - * Is Messaging the default SMS app? - * - On KLP+ this checks the system setting. - * - On JB (and below) this always returns true, since the setting was added in KLP. - */ - public boolean isDefaultSmsApp() { - if (OsUtil.isAtLeastKLP()) { - final String configuredApplication = Telephony.Sms.getDefaultSmsPackage(mContext); - return mContext.getPackageName().equals(configuredApplication); - } - return true; - } - - /** - * Get default SMS app package name - * - * @return the package name of default SMS app - */ - public String getDefaultSmsApp() { - if (OsUtil.isAtLeastKLP()) { - return Telephony.Sms.getDefaultSmsPackage(mContext); - } - return null; - } - - /** - * Determines if SMS is currently enabled on this device. - * - Device must support SMS - * - On KLP+ we must be set as the default SMS app - */ - public boolean isSmsEnabled() { - return isSmsCapable() && isDefaultSmsApp(); - } - - /** - * Returns the name of the default SMS app, or the empty string if there is - * an error or there is no default app (e.g. JB and below). - */ - public String getDefaultSmsAppLabel() { - if (OsUtil.isAtLeastKLP()) { - final String packageName = Telephony.Sms.getDefaultSmsPackage(mContext); - final PackageManager pm = mContext.getPackageManager(); - try { - final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); - return pm.getApplicationLabel(appInfo).toString(); - } catch (NameNotFoundException e) { - // Fall through and return empty string - } - } - return ""; - } - - /** - * Gets the state of Airplane Mode. - * - * @return true if enabled. - */ - @SuppressWarnings("deprecation") - public boolean isAirplaneModeOn() { - if (OsUtil.isAtLeastJB_MR1()) { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) != 0; - } else { - return Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) != 0; - } - } - - public static String getMccMncString(int[] mccmnc) { - if (mccmnc == null || mccmnc.length != 2) { - return "000000"; - } - return String.format("%03d%03d", mccmnc[0], mccmnc[1]); - } - - public static String canonicalizeMccMnc(final String mcc, final String mnc) { - try { - return String.format("%03d%03d", Integer.parseInt(mcc), Integer.parseInt(mnc)); - } catch (final NumberFormatException e) { - // Return invalid as is - LogUtil.w(TAG, "canonicalizeMccMnc: invalid mccmnc:" + mcc + " ," + mnc); - } - return mcc + mnc; - } - - /** - * Returns whether the given destination is valid for sending SMS/MMS message. - */ - public static boolean isValidSmsMmsDestination(final String destination) { - return PhoneNumberUtils.isWellFormedSmsAddress(destination) || - MmsSmsUtils.isEmailAddress(destination); - } - - public interface SubscriptionRunnable { - void runForSubscription(int subId); - } - - /** - * A convenience method for iterating through all active subscriptions - * - * @param runnable a {@link SubscriptionRunnable} for performing work on each subscription. - */ - public static void forEachActiveSubscription(final SubscriptionRunnable runnable) { - if (OsUtil.isAtLeastL_MR1()) { - final List subscriptionList = - getDefault().toLMr1().getActiveSubscriptionInfoList(); - for (final SubscriptionInfo subscriptionInfo : subscriptionList) { - runnable.runForSubscription(subscriptionInfo.getSubscriptionId()); - } - } else { - runnable.runForSubscription(ParticipantData.DEFAULT_SELF_SUB_ID); - } - } - - private static String getNumberFromPrefs(final Context context, final int subId) { - final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId); - final String mmsPhoneNumberPrefKey = - context.getString(R.string.mms_phone_number_pref_key); - final String userDefinedNumber = prefs.getString(mmsPhoneNumberPrefKey, null); - if (!TextUtils.isEmpty(userDefinedNumber)) { - return userDefinedNumber; - } - return null; - } -} diff --git a/src/com/android/messaging/util/RingtoneUtil.java b/src/com/android/messaging/util/RingtoneUtil.java deleted file mode 100644 index a7facfb..0000000 --- a/src/com/android/messaging/util/RingtoneUtil.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.net.Uri; -import android.provider.Settings; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.R; - -public class RingtoneUtil { - /** - * Return a ringtone Uri for the string representation passed in. Use the app - * and system defaults as fallbacks - * @param ringtoneString is the ringtone to resolve - * @return the Uri of the ringtone or the fallback ringtone - */ - public static Uri getNotificationRingtoneUri(String ringtoneString) { - if (ringtoneString == null) { - // No override specified, fall back to system-wide setting. - final BuglePrefs prefs = BuglePrefs.getApplicationPrefs(); - final Context context = Factory.get().getApplicationContext(); - final String prefKey = context.getString(R.string.notification_sound_pref_key); - ringtoneString = prefs.getString(prefKey, null); - } - - if (!TextUtils.isEmpty(ringtoneString)) { - // We have set a value, even if it is the default Uri at some point - return Uri.parse(ringtoneString); - } else if (ringtoneString == null) { - // We have no setting specified (== null), so we default to the system default - return Settings.System.DEFAULT_NOTIFICATION_URI; - } else { - // An empty string (== "") here is the result of selecting "None" as the ringtone - return null; - } - } -} diff --git a/src/com/android/messaging/util/SafeAsyncTask.java b/src/com/android/messaging/util/SafeAsyncTask.java deleted file mode 100644 index 1cce6e9..0000000 --- a/src/com/android/messaging/util/SafeAsyncTask.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Debug; -import android.os.SystemClock; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert.RunsOnAnyThread; - -/** - * Wrapper class which provides explicit API for: - *
    - *
  1. Threading policy choice - Users of this class should use the explicit API instead of - * {@link #execute} which uses different threading policy on different OS versions. - *
  2. Enforce creation on main thread as required by AsyncTask - *
  3. Enforce that the background task does not take longer than expected. - *
- */ -public abstract class SafeAsyncTask - extends AsyncTask { - private static final long DEFAULT_MAX_EXECUTION_TIME_MILLIS = 10 * 1000; // 10 seconds - - /** This is strongly discouraged as it can block other AsyncTasks indefinitely. */ - public static final long UNBOUNDED_TIME = Long.MAX_VALUE; - - private static final String WAKELOCK_ID = "bugle_safe_async_task_wakelock"; - protected static final int WAKELOCK_OP = 1000; - private static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); - - private final long mMaxExecutionTimeMillis; - private final boolean mCancelExecutionOnTimeout; - private boolean mThreadPoolRequested; - - public SafeAsyncTask() { - this(DEFAULT_MAX_EXECUTION_TIME_MILLIS, false); - } - - public SafeAsyncTask(final long maxTimeMillis) { - this(maxTimeMillis, false); - } - - /** - * @param maxTimeMillis maximum expected time for the background operation. This is just - * a diagnostic tool to catch unexpectedly long operations. If an operation does take - * longer than expected, it is fine to increase this argument. If the value is larger - * than a minute, you should consider using a dedicated thread so as not to interfere - * with other AsyncTasks. - * - *

Use {@link #UNBOUNDED_TIME} if you do not know the maximum expected time. This - * is strongly discouraged as it can block other AsyncTasks indefinitely. - * - * @param cancelExecutionOnTimeout whether to attempt to cancel the task execution on timeout. - * If this is set, at execution timeout we will call cancel(), so doInBackgroundTimed() - * should periodically check if the task is to be cancelled and finish promptly if - * possible, and handle the cancel event in onCancelled(). Also, at the end of execution - * we will not crash the execution if it went over limit since we explicitly canceled it. - */ - public SafeAsyncTask(final long maxTimeMillis, final boolean cancelExecutionOnTimeout) { - Assert.isMainThread(); // AsyncTask has to be created on the main thread - mMaxExecutionTimeMillis = maxTimeMillis; - mCancelExecutionOnTimeout = cancelExecutionOnTimeout; - } - - public final SafeAsyncTask executeOnThreadPool( - final Params... params) { - Assert.isMainThread(); // AsyncTask requires this - mThreadPoolRequested = true; - executeOnExecutor(THREAD_POOL_EXECUTOR, params); - return this; - } - - protected abstract Result doInBackgroundTimed(final Params... params); - - @Override - protected final Result doInBackground(final Params... params) { - // This enforces that executeOnThreadPool was called, not execute. Ideally, we would - // make execute throw an exception, but since it is final, we cannot override it. - Assert.isTrue(mThreadPoolRequested); - - if (mCancelExecutionOnTimeout) { - ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() { - @Override - public void run() { - if (getStatus() == Status.RUNNING) { - // Cancel the task if it's still running. - LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s timed out and is canceled", - this)); - cancel(true /* mayInterruptIfRunning */); - } - } - }, mMaxExecutionTimeMillis); - } - - final long startTime = SystemClock.elapsedRealtime(); - try { - return doInBackgroundTimed(params); - } finally { - final long executionTime = SystemClock.elapsedRealtime() - startTime; - if (executionTime > mMaxExecutionTimeMillis) { - LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s took %dms", this, executionTime)); - // Don't crash if debugger is attached or if we are asked to cancel on timeout. - if (!Debug.isDebuggerConnected() && !mCancelExecutionOnTimeout) { - Assert.fail(this + " took too long"); - } - } - } - - } - - @Override - protected void onPostExecute(final Result result) { - // No need to use AsyncTask at all if there is no onPostExecute - Assert.fail("Use SafeAsyncTask.executeOnThreadPool"); - } - - /** - * This provides a way for people to run async tasks but without onPostExecute. - * This can be called on any thread. - * - * Run code in a thread using AsyncTask's thread pool. - * - * To enable wakelock during the execution, see {@link #executeOnThreadPool(Runnable, boolean)} - * - * @param runnable The Runnable to execute asynchronously - */ - @RunsOnAnyThread - public static void executeOnThreadPool(final Runnable runnable) { - executeOnThreadPool(runnable, false); - } - - /** - * This provides a way for people to run async tasks but without onPostExecute. - * This can be called on any thread. - * - * Run code in a thread using AsyncTask's thread pool. - * - * @param runnable The Runnable to execute asynchronously - * @param withWakeLock when set, a wake lock will be held for the duration of the runnable - * execution - */ - public static void executeOnThreadPool(final Runnable runnable, final boolean withWakeLock) { - if (withWakeLock) { - final Intent intent = new Intent(); - sWakeLock.acquire(Factory.get().getApplicationContext(), intent, WAKELOCK_OP); - THREAD_POOL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - try { - runnable.run(); - } finally { - sWakeLock.release(intent, WAKELOCK_OP); - } - } - }); - } else { - THREAD_POOL_EXECUTOR.execute(runnable); - } - } -} diff --git a/src/com/android/messaging/util/SwitchCompatUtils.java b/src/com/android/messaging/util/SwitchCompatUtils.java deleted file mode 100644 index b5d1ed5..0000000 --- a/src/com/android/messaging/util/SwitchCompatUtils.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.v7.graphics.drawable.DrawableWrapper; -import android.support.v7.widget.SwitchCompat; -import android.util.TypedValue; - -/* Most methods in this file are copied from - * v7/appcompat/src/android/support/v7/internal/widget/TintManager.java. It would be better if - * we could have just extended the TintManager but this is a final class that we do not have - * access to. */ - -/** - * Util methods for the SwitchCompat widget - */ -public class SwitchCompatUtils { - /** - * Given a color and a SwitchCompat view, updates the SwitchCompat to appear with the appropiate - * color when enabled and checked - */ - public static void updateSwitchCompatColor(SwitchCompat switchCompat, final int color) { - final Context context = switchCompat.getContext(); - final TypedValue typedValue = new TypedValue(); - - switchCompat.setThumbDrawable(getColorTintedDrawable(switchCompat.getThumbDrawable(), - getSwitchThumbColorStateList(context, color, typedValue), - PorterDuff.Mode.MULTIPLY)); - - switchCompat.setTrackDrawable(getColorTintedDrawable(switchCompat.getTrackDrawable(), - getSwitchTrackColorStateList(context, color, typedValue), PorterDuff.Mode.SRC_IN)); - } - - private static Drawable getColorTintedDrawable(Drawable oldDrawable, - final ColorStateList colorStateList, final PorterDuff.Mode mode) { - final int[] thumbState = oldDrawable.isStateful() ? oldDrawable.getState() : null; - if (oldDrawable instanceof DrawableWrapper) { - oldDrawable = ((DrawableWrapper) oldDrawable).getWrappedDrawable(); - } - final Drawable newDrawable = new TintDrawableWrapper(oldDrawable, colorStateList, mode); - if (thumbState != null) { - newDrawable.setState(thumbState); - } - return newDrawable; - } - - private static ColorStateList getSwitchThumbColorStateList(final Context context, - final int color, final TypedValue typedValue) { - final int[][] states = new int[3][]; - final int[] colors = new int[3]; - int i = 0; - // Disabled state - states[i] = new int[] { -android.R.attr.state_enabled }; - colors[i] = getColor(Color.parseColor("#ffbdbdbd"), 1f); - i++; - states[i] = new int[] { android.R.attr.state_checked }; - colors[i] = color; - i++; - // Default enabled state - states[i] = new int[0]; - colors[i] = getThemeAttrColor(context, typedValue, - android.support.v7.appcompat.R.attr.colorSwitchThumbNormal); - i++; - return new ColorStateList(states, colors); - } - - private static ColorStateList getSwitchTrackColorStateList(final Context context, - final int color, final TypedValue typedValue) { - final int[][] states = new int[3][]; - final int[] colors = new int[3]; - int i = 0; - // Disabled state - states[i] = new int[] { -android.R.attr.state_enabled }; - colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.1f); - i++; - states[i] = new int[] { android.R.attr.state_checked }; - colors[i] = getColor(color, 0.3f); - i++; - // Default enabled state - states[i] = new int[0]; - colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.3f); - i++; - return new ColorStateList(states, colors); - } - - private static int getThemeAttrColor(final Context context, final TypedValue typedValue, - final int attr) { - if (context.getTheme().resolveAttribute(attr, typedValue, true)) { - if (typedValue.type >= TypedValue.TYPE_FIRST_INT - && typedValue.type <= TypedValue.TYPE_LAST_INT) { - return typedValue.data; - } else if (typedValue.type == TypedValue.TYPE_STRING) { - return context.getResources().getColor(typedValue.resourceId); - } - } - return 0; - } - - private static int getThemeAttrColor(final Context context, final TypedValue typedValue, - final int attr, final float alpha) { - final int color = getThemeAttrColor(context, typedValue, attr); - return getColor(color, alpha); - } - - private static int getColor(int color, float alpha) { - final int originalAlpha = Color.alpha(color); - // Return the color, multiplying the original alpha by the disabled value - return (color & 0x00ffffff) | (Math.round(originalAlpha * alpha) << 24); - } -} diff --git a/src/com/android/messaging/util/TextUtil.java b/src/com/android/messaging/util/TextUtil.java deleted file mode 100644 index b240396..0000000 --- a/src/com/android/messaging/util/TextUtil.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.support.annotation.Nullable; - -public class TextUtil { - /** - * Returns true if the string is empty, null or only whitespace. - */ - public static boolean isAllWhitespace(@Nullable String string) { - if (string == null || string.isEmpty()) { - return true; - } - - for (int i = 0; i < string.length(); ++i) { - if (!Character.isWhitespace(string.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * Taken from PhoneNumberUtils, where it is only available in API 21+ Replaces all unicode - * (e.g. Arabic, Persian) digits with their decimal digit equivalents. - * - * @param number the number to perform the replacement on. - * @return the replaced number. - */ - public static String replaceUnicodeDigits(String number) { - StringBuilder normalizedDigits = new StringBuilder(number.length()); - for (char c : number.toCharArray()) { - int digit = Character.digit(c, 10); - if (digit != -1) { - normalizedDigits.append(digit); - } else { - normalizedDigits.append(c); - } - } - return normalizedDigits.toString(); - } - - /** - * Appends text to the stringBuilder. - * If stringBuilder already has content, separator is prepended to create a separator between - * entries. - * @param stringBuilder The stringBuilder to add to - * @param text The text to append - * @param separator The separator to add if there is already text, typically "," or "\n" - */ - public static void appendWithSeparator(final StringBuilder stringBuilder, final String text, - final String separator) { - if (stringBuilder.length() > 0) { - stringBuilder.append(separator); - } - stringBuilder.append(text); - } -} diff --git a/src/com/android/messaging/util/ThreadUtil.java b/src/com/android/messaging/util/ThreadUtil.java deleted file mode 100644 index 3b935d8..0000000 --- a/src/com/android/messaging/util/ThreadUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.os.Handler; -import android.os.Looper; - -public class ThreadUtil { - private static final Handler sHandler = new Handler(Looper.getMainLooper()); - - public static Handler getMainThreadHandler() { - return sHandler; - } -} diff --git a/src/com/android/messaging/util/TintDrawableWrapper.java b/src/com/android/messaging/util/TintDrawableWrapper.java deleted file mode 100644 index e6ea4bd..0000000 --- a/src/com/android/messaging/util/TintDrawableWrapper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.v7.graphics.drawable.DrawableWrapper; - -/* - * This is directly copied from v7/appcompat/src/android/support/v7/internal/widget/TintManager.java - */ - -/** - * A {@link DrawableWrapper} which updates it's color filter using a {@link ColorStateList}. - */ -class TintDrawableWrapper extends DrawableWrapper { - private final ColorStateList mTintStateList; - private final PorterDuff.Mode mTintMode; - private int mCurrentColor; - public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList) { - this(drawable, tintStateList, PorterDuff.Mode.SRC_IN); - } - public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList, - PorterDuff.Mode tintMode) { - super(drawable); - mTintStateList = tintStateList; - mTintMode = tintMode; - } - @Override - public boolean isStateful() { - return (mTintStateList != null && mTintStateList.isStateful()) || super.isStateful(); - } - @Override - public boolean setState(int[] stateSet) { - boolean handled = super.setState(stateSet); - handled = updateTint(stateSet) || handled; - return handled; - } - private boolean updateTint(int[] state) { - if (mTintStateList != null) { - final int color = mTintStateList.getColorForState(state, mCurrentColor); - if (color != mCurrentColor) { - if (color != Color.TRANSPARENT) { - setColorFilter(color, mTintMode); - } else { - clearColorFilter(); - } - mCurrentColor = color; - return true; - } - } - return false; - } -} diff --git a/src/com/android/messaging/util/Trace.java b/src/com/android/messaging/util/Trace.java deleted file mode 100644 index da1e87c..0000000 --- a/src/com/android/messaging/util/Trace.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - - -import android.annotation.TargetApi; -import android.os.Build; - -/** - * Helper class for systrace (see http://developer.android.com/tools/help/systrace.html).

- * To enable, set log.tag.Bugle_Trace (defined by {@link #TAG} to VERBOSE before - * the process starts.

- * Note that this will run only on JBMR2 or later; on earlier platforms or if the log - * tag isn't set, calls to {@link #beginSection(String)} or {@link #endSection()} are no-ops.

- * Internally, calls dispatch to either a class that actually does work or a class that doesn't. - * This avoids Dalvik complaining when it loads the class on earlier platforms that the - * opcodes aren't available, and, according to the Dalvik team, using vtable dispatching for - * something like this should be faster than if (OsUtil.isAtLeast...()) on each call. - */ -public final class Trace { - private static final String TAG = "Bugle_Trace"; - private abstract static class AbstractTrace { - abstract void beginSection(String sectionName); - abstract void endSection(); - } - - private static final AbstractTrace sTrace; - - // Static initializer to pick the correct trace class to handle tracing. - static { - // Use android.util.Log instead of LogUtil here to avoid pulling in Gservices - // too early in app startup. - if (OsUtil.isAtLeastJB_MR2() && - android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) { - sTrace = new TraceJBMR2(); - } else { - sTrace = new TraceShim(); - } - } - - /** - * Writes a trace message to indicate that a given section of code has begun. This call must - * be followed by a corresponding call to {@link #endSection()} on the same thread. - * - *

At this time the vertical bar character '|', newline character '\n', and - * null character '\0' are used internally by the tracing mechanism. If sectionName contains - * these characters they will be replaced with a space character in the trace. - * - * @param sectionName The name of the code section to appear in the trace. This may be at - * most 127 Unicode code units long. - */ - public static void beginSection(String sectionName) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "beginSection() " + sectionName); - } - sTrace.beginSection(sectionName); - } - - /** - * Writes a trace message to indicate that a given section of code has ended. This call must - * be preceeded by a corresponding call to {@link #beginSection(String)}. Calling this method - * will mark the end of the most recently begun section of code, so care must be taken to - * ensure that beginSection / endSection pairs are properly nested and called from the same - * thread. - */ - public static void endSection() { - sTrace.endSection(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "endSection()"); - } - } - - /** - * Internal class that we use if we really did enable tracing. - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - private static final class TraceJBMR2 extends AbstractTrace { - @Override - void beginSection(String sectionName) { - android.os.Trace.beginSection(sectionName); - } - - @Override - void endSection() { - android.os.Trace.endSection(); - } - } - - /** - * Dummy class that we use if we aren't really tracing. - */ - private static final class TraceShim extends AbstractTrace { - @Override - void beginSection(String sectionName) { - } - - @Override - void endSection() { - } - } -} diff --git a/src/com/android/messaging/util/Typefaces.java b/src/com/android/messaging/util/Typefaces.java deleted file mode 100644 index eb8562c..0000000 --- a/src/com/android/messaging/util/Typefaces.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.graphics.Typeface; - -/** - * Provides access to typefaces used by code. Specially important for typefaces coming from assets, - * which appear (from platform code inspection) to not be cached. - * Note: Considered making this a singleton provided by factory/appcontext, but seemed too simple, - * not worth stubbing. - */ -public class Typefaces { - private static Typeface sRobotoBold; - private static Typeface sRobotoNormal; - - public static Typeface getRobotoBold() { - Assert.isMainThread(); - if (sRobotoBold == null) { - sRobotoBold = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD); - } - return sRobotoBold; - } - - public static Typeface getRobotoNormal() { - Assert.isMainThread(); - if (sRobotoNormal == null) { - sRobotoNormal = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); - } - return sRobotoNormal; - } -} diff --git a/src/com/android/messaging/util/UiUtils.java b/src/com/android/messaging/util/UiUtils.java deleted file mode 100644 index 84fe353..0000000 --- a/src/com/android/messaging/util/UiUtils.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.app.Activity; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; -import android.text.Html; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.URLSpan; -import android.view.Gravity; -import android.view.Surface; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.Interpolator; -import android.view.animation.ScaleAnimation; -import android.widget.RemoteViews; -import android.widget.Toast; - -import com.android.messaging.Factory; -import com.android.messaging.R; -import com.android.messaging.ui.SnackBar; -import com.android.messaging.ui.SnackBar.Placement; -import com.android.messaging.ui.conversationlist.ConversationListActivity; -import com.android.messaging.ui.SnackBarInteraction; -import com.android.messaging.ui.SnackBarManager; -import com.android.messaging.ui.UIIntents; - -import java.lang.reflect.Field; -import java.util.List; - -public class UiUtils { - /** MediaPicker transition duration in ms */ - public static final int MEDIAPICKER_TRANSITION_DURATION = - getApplicationContext().getResources().getInteger( - R.integer.mediapicker_transition_duration); - /** Short transition duration in ms */ - public static final int ASYNCIMAGE_TRANSITION_DURATION = - getApplicationContext().getResources().getInteger( - R.integer.asyncimage_transition_duration); - /** Compose transition duration in ms */ - public static final int COMPOSE_TRANSITION_DURATION = - getApplicationContext().getResources().getInteger( - R.integer.compose_transition_duration); - /** Generic duration for revealing/hiding a view */ - public static final int REVEAL_ANIMATION_DURATION = - getApplicationContext().getResources().getInteger( - R.integer.reveal_view_animation_duration); - - public static final Interpolator DEFAULT_INTERPOLATOR = new CubicBezierInterpolator( - 0.4f, 0.0f, 0.2f, 1.0f); - - public static final Interpolator EASE_IN_INTERPOLATOR = new CubicBezierInterpolator( - 0.4f, 0.0f, 0.8f, 0.5f); - - public static final Interpolator EASE_OUT_INTERPOLATOR = new CubicBezierInterpolator( - 0.0f, 0.0f, 0.2f, 1f); - - /** Show a simple toast at the bottom */ - public static void showToastAtBottom(final int messageId) { - UiUtils.showToastAtBottom(getApplicationContext().getString(messageId)); - } - - /** Show a simple toast at the bottom */ - public static void showToastAtBottom(final String message) { - final Toast toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG); - toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0); - toast.show(); - } - - /** Show a simple toast at the default position */ - public static void showToast(final int messageId) { - final Toast toast = Toast.makeText(getApplicationContext(), - getApplicationContext().getString(messageId), Toast.LENGTH_LONG); - toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0); - toast.show(); - } - - /** Show a simple toast at the default position */ - public static void showToast(final int pluralsMessageId, final int count) { - final Toast toast = Toast.makeText(getApplicationContext(), - getApplicationContext().getResources().getQuantityString(pluralsMessageId, count), - Toast.LENGTH_LONG); - toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0); - toast.show(); - } - - public static void showSnackBar(final Context context, @NonNull final View parentView, - final String message, @Nullable final Runnable runnable, final int runnableLabel, - @Nullable final List interactions) { - Assert.notNull(context); - SnackBar.Action action = null; - switch (runnableLabel) { - case SnackBar.Action.SNACK_BAR_UNDO: - action = SnackBar.Action.createUndoAction(runnable); - break; - case SnackBar.Action.SNACK_BAR_RETRY: - action = SnackBar.Action.createRetryAction(runnable); - break; - default : - break; - } - - showSnackBarWithCustomAction(context, parentView, message, action, interactions, - null /* placement */); - } - - public static void showSnackBarWithCustomAction(final Context context, - @NonNull final View parentView, - @NonNull final String message, - @NonNull final SnackBar.Action action, - @Nullable final List interactions, - @Nullable final Placement placement) { - Assert.notNull(context); - Assert.isTrue(!TextUtils.isEmpty(message)); - Assert.notNull(action); - SnackBarManager.get() - .newBuilder(parentView) - .setText(message) - .setAction(action) - .withInteractions(interactions) - .withPlacement(placement) - .show(); - } - - /** - * Run the given runnable once after the next layout pass of the view. - */ - public static void doOnceAfterLayoutChange(final View view, final Runnable runnable) { - final OnLayoutChangeListener listener = new OnLayoutChangeListener() { - @Override - public void onLayoutChange(final View v, final int left, final int top, final int right, - final int bottom, final int oldLeft, final int oldTop, final int oldRight, - final int oldBottom) { - // Call the runnable outside the layout pass because very few actions are allowed in - // the layout pass - ThreadUtil.getMainThreadHandler().post(runnable); - view.removeOnLayoutChangeListener(this); - } - }; - view.addOnLayoutChangeListener(listener); - } - - public static boolean isLandscapeMode() { - return Factory.get().getApplicationContext().getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } - - private static Context getApplicationContext() { - return Factory.get().getApplicationContext(); - } - - public static CharSequence commaEllipsize( - final String text, - final TextPaint paint, - final int width, - final String oneMore, - final String more) { - CharSequence ellipsized = TextUtils.commaEllipsize( - text, - paint, - width, - oneMore, - more); - if (TextUtils.isEmpty(ellipsized)) { - ellipsized = text; - } - return ellipsized; - } - - /** - * Reveals/Hides a view with a scale animation from view center. - * @param view the view to animate - * @param desiredVisibility desired visibility (e.g. View.GONE) for the animated view. - * @param onFinishRunnable an optional runnable called at the end of the animation - */ - public static void revealOrHideViewWithAnimation(final View view, final int desiredVisibility, - @Nullable final Runnable onFinishRunnable) { - final boolean needAnimation = view.getVisibility() != desiredVisibility; - if (needAnimation) { - final float fromScale = desiredVisibility == View.VISIBLE ? 0F : 1F; - final float toScale = desiredVisibility == View.VISIBLE ? 1F : 0F; - final ScaleAnimation showHideAnimation = - new ScaleAnimation(fromScale, toScale, fromScale, toScale, - ScaleAnimation.RELATIVE_TO_SELF, 0.5f, - ScaleAnimation.RELATIVE_TO_SELF, 0.5f); - showHideAnimation.setDuration(REVEAL_ANIMATION_DURATION); - showHideAnimation.setInterpolator(DEFAULT_INTERPOLATOR); - showHideAnimation.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(final Animation animation) { - } - - @Override - public void onAnimationRepeat(final Animation animation) { - } - - @Override - public void onAnimationEnd(final Animation animation) { - if (onFinishRunnable != null) { - // Rather than running this immediately, we post it to happen next so that - // the animation will be completed so that the view can be detached from - // it's window. Otherwise, we may leak memory. - ThreadUtil.getMainThreadHandler().post(onFinishRunnable); - } - } - }); - view.clearAnimation(); - view.startAnimation(showHideAnimation); - // We are playing a view Animation; unlike view property animations, we can commit the - // visibility immediately instead of waiting for animation end. - view.setVisibility(desiredVisibility); - } else if (onFinishRunnable != null) { - // Make sure onFinishRunnable is always executed. - ThreadUtil.getMainThreadHandler().post(onFinishRunnable); - } - } - - public static Rect getMeasuredBoundsOnScreen(final View view) { - final int[] location = new int[2]; - view.getLocationOnScreen(location); - return new Rect(location[0], location[1], - location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight()); - } - - public static void setStatusBarColor(final Activity activity, final int color) { - if (OsUtil.isAtLeastL()) { - // To achieve the appearance of an 80% opacity blend against a black background, - // each color channel is reduced in value by 20%. - final int blendedRed = (int) Math.floor(0.8 * Color.red(color)); - final int blendedGreen = (int) Math.floor(0.8 * Color.green(color)); - final int blendedBlue = (int) Math.floor(0.8 * Color.blue(color)); - - activity.getWindow().setStatusBarColor( - Color.rgb(blendedRed, blendedGreen, blendedBlue)); - } - } - - public static void lockOrientation(final Activity activity) { - final int orientation = activity.getResources().getConfiguration().orientation; - final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - - // rotation tracks the rotation of the device from its natural orientation - // orientation tracks whether the screen is landscape or portrait. - // It is possible to have a rotation of 0 (device in its natural orientation) in portrait - // (phone), or in landscape (tablet), so we have to check both values to determine what to - // pass to setRequestedOrientation. - if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } - } else if (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) { - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); - } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); - } - } - } - - public static void unlockOrientation(final Activity activity) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); - } - - public static int getPaddingStart(final View view) { - return OsUtil.isAtLeastJB_MR1() ? view.getPaddingStart() : view.getPaddingLeft(); - } - - public static int getPaddingEnd(final View view) { - return OsUtil.isAtLeastJB_MR1() ? view.getPaddingEnd() : view.getPaddingRight(); - } - - public static boolean isRtlMode() { - return OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources() - .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - } - - /** - * Check if the activity needs to be redirected to permission check - * @return true if {@link Activity#finish()} was called because redirection was performed - */ - public static boolean redirectToPermissionCheckIfNeeded(final Activity activity) { - if (!OsUtil.hasRequiredPermissions()) { - UIIntents.get().launchPermissionCheckActivity(activity); - } else { - // No redirect performed - return false; - } - - // Redirect performed - activity.finish(); - return true; - } - - /** - * Called to check if all conditions are nominal and a "go" for some action, such as deleting - * a message, that requires this app to be the default app. This is also a precondition - * required for sending a draft. - * @return true if all conditions are nominal and we're ready to send a message - */ - public static boolean isReadyForAction() { - final PhoneUtils phoneUtils = PhoneUtils.getDefault(); - - // Have all the conditions been met: - // Supports SMS? - // Has a preferred sim? - // Is the default sms app? - return phoneUtils.isSmsCapable() && - phoneUtils.getHasPreferredSmsSim() && - phoneUtils.isDefaultSmsApp(); - } - - /* - * Removes all html markup from the text and replaces links with the the text and a text version - * of the href. - * @param htmlText HTML markup text - * @return Sanitized string with link hrefs inlined - */ - public static String stripHtml(final String htmlText) { - final StringBuilder result = new StringBuilder(); - final Spanned markup = Html.fromHtml(htmlText); - final String strippedText = markup.toString(); - - final URLSpan[] links = markup.getSpans(0, markup.length() - 1, URLSpan.class); - int currentIndex = 0; - for (final URLSpan link : links) { - final int spanStart = markup.getSpanStart(link); - final int spanEnd = markup.getSpanEnd(link); - if (spanStart > currentIndex) { - result.append(strippedText, currentIndex, spanStart); - } - final String displayText = strippedText.substring(spanStart, spanEnd); - final String linkText = link.getURL(); - result.append(getApplicationContext().getString(R.string.link_display_format, - displayText, linkText)); - currentIndex = spanEnd; - } - if (strippedText.length() > currentIndex) { - result.append(strippedText, currentIndex, strippedText.length()); - } - return result.toString(); - } - - public static void setActionBarShadowVisibility(final ActionBarActivity activity, final boolean visible) { - final ActionBar actionBar = activity.getSupportActionBar(); - actionBar.setElevation(visible ? - activity.getResources().getDimensionPixelSize(R.dimen.action_bar_elevation) : - 0); - final View actionBarView = activity.getWindow().getDecorView().findViewById( - android.support.v7.appcompat.R.id.decor_content_parent); - if (actionBarView != null) { - // AppCompatActionBar has one drawable Field, which is the shadow for the action bar - // set the alpha on that drawable manually - final Field[] fields = actionBarView.getClass().getDeclaredFields(); - try { - for (final Field field : fields) { - if (field.getType().equals(Drawable.class)) { - field.setAccessible(true); - final Drawable shadowDrawable = (Drawable) field.get(actionBarView); - if (shadowDrawable != null) { - shadowDrawable.setAlpha(visible ? 255 : 0); - actionBarView.invalidate(); - return; - } - } - } - } catch (final IllegalAccessException ex) { - // Not expected, we should avoid this via field.setAccessible(true) above - LogUtil.e(LogUtil.BUGLE_TAG, "Error setting shadow visibility", ex); - } - } - } - - /** - * Get the activity that's hosting the view, typically casting view.getContext() as an Activity - * is sufficient, but sometimes the context is a context wrapper, in which case we need to case - * the base context - */ - public static Activity getActivity(final View view) { - if (view == null) { - return null; - } - return getActivity(view.getContext()); - } - - /** - * Get the activity for the supplied context, typically casting context as an Activity - * is sufficient, but sometimes the context is a context wrapper, in which case we need to case - * the base context - */ - public static Activity getActivity(final Context context) { - if (context == null) { - return null; - } - if (context instanceof Activity) { - return (Activity) context; - } - if (context instanceof ContextWrapper) { - return getActivity(((ContextWrapper) context).getBaseContext()); - } - - // We've hit a non-activity context such as an app-context - return null; - } - - public static RemoteViews getWidgetMissingPermissionView(final Context context) { - return new RemoteViews(context.getPackageName(), R.layout.widget_missing_permission); - } -} diff --git a/src/com/android/messaging/util/UriUtil.java b/src/com/android/messaging/util/UriUtil.java deleted file mode 100644 index 4bbc80d..0000000 --- a/src/com/android/messaging/util/UriUtil.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.provider.MediaStore; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import com.android.messaging.Factory; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.util.Assert.DoesNotRunOnMainThread; -import com.google.common.io.ByteStreams; -import com.google.common.io.Files; -import com.google.common.io.Resources; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.Arrays; -import java.util.HashSet; - -public class UriUtil { - private static final String SCHEME_SMS = "sms"; - private static final String SCHEME_SMSTO = "smsto"; - private static final String SCHEME_MMS = "mms"; - private static final String SCHEME_MMSTO = "smsto"; - public static final HashSet SMS_MMS_SCHEMES = new HashSet( - Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO)); - - public static final String SCHEME_BUGLE = "bugle"; - public static final HashSet SUPPORTED_SCHEME = new HashSet( - Arrays.asList(ContentResolver.SCHEME_ANDROID_RESOURCE, - ContentResolver.SCHEME_CONTENT, - ContentResolver.SCHEME_FILE, - SCHEME_BUGLE)); - - public static final String SCHEME_TEL = "tel:"; - - /** - * Get a Uri representation of the file path of a resource file. - */ - public static Uri getUriForResourceFile(final String path) { - return TextUtils.isEmpty(path) ? null : Uri.fromFile(new File(path)); - } - - /** - * Extract the path from a file:// Uri, or null if the uri is of other scheme. - */ - public static String getFilePathFromUri(final Uri uri) { - if (!isFileUri(uri)) { - return null; - } - return uri.getPath(); - } - - /** - * Returns whether the given Uri is local or remote. - */ - public static boolean isLocalResourceUri(final Uri uri) { - final String scheme = uri.getScheme(); - return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE) || - TextUtils.equals(scheme, ContentResolver.SCHEME_CONTENT) || - TextUtils.equals(scheme, ContentResolver.SCHEME_FILE); - } - - /** - * Returns whether the given Uri is part of Bugle's app package - */ - public static boolean isBugleAppResource(final Uri uri) { - final String scheme = uri.getScheme(); - return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE); - } - - public static boolean isFileUri(final Uri uri) { - return uri != null && TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE); - } - - /** - * Constructs an android.resource:// uri for the given resource id. - */ - public static Uri getUriForResourceId(final Context context, final int resId) { - return new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(context.getPackageName()) - .appendPath(String.valueOf(resId)) - .build(); - } - - /** - * Returns whether the given Uri string is local. - */ - public static boolean isLocalUri(@NonNull final Uri uri) { - Assert.notNull(uri); - return SUPPORTED_SCHEME.contains(uri.getScheme()); - } - - private static final String MEDIA_STORE_URI_KLP = "com.android.providers.media.documents"; - - /** - * Check if a URI is from the MediaStore - */ - public static boolean isMediaStoreUri(final Uri uri) { - final String uriAuthority = uri.getAuthority(); - return TextUtils.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme()) - && (TextUtils.equals(MediaStore.AUTHORITY, uriAuthority) || - // KK changed the media store authority name - TextUtils.equals(MEDIA_STORE_URI_KLP, uriAuthority)); - } - - /** - * Gets the size in bytes for the content uri. Currently we only support content in the - * scratch space. - */ - @DoesNotRunOnMainThread - public static long getContentSize(final Uri uri) { - Assert.isNotMainThread(); - if (isLocalResourceUri(uri)) { - ParcelFileDescriptor pfd = null; - try { - pfd = Factory.get().getApplicationContext() - .getContentResolver().openFileDescriptor(uri, "r"); - return Math.max(pfd.getStatSize(), 0); - } catch (final FileNotFoundException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error getting content size", e); - } finally { - if (pfd != null) { - try { - pfd.close(); - } catch (final IOException e) { - // Do nothing. - } - } - } - } else { - Assert.fail("Unsupported uri type!"); - } - return 0; - } - - /** @return duration in milliseconds or 0 if not able to determine */ - public static int getMediaDurationMs(final Uri uri) { - final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper(); - try { - retriever.setDataSource(uri); - return retriever.extractInteger(MediaMetadataRetriever.METADATA_KEY_DURATION, 0); - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Unable extract duration from media file: " + uri, e); - return 0; - } finally { - retriever.release(); - } - } - - /** - * Persist a piece of content from the given input stream, byte by byte to the scratch - * directory. - * @return the output Uri if the operation succeeded, or null if failed. - */ - @DoesNotRunOnMainThread - public static Uri persistContentToScratchSpace(final InputStream inputStream) { - final Context context = Factory.get().getApplicationContext(); - final Uri scratchSpaceUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null); - return copyContent(context, inputStream, scratchSpaceUri); - } - - /** - * Persist a piece of content from the given sourceUri, byte by byte to the scratch - * directory. - * @return the output Uri if the operation succeeded, or null if failed. - */ - @DoesNotRunOnMainThread - public static Uri persistContentToScratchSpace(final Uri sourceUri) { - InputStream inputStream = null; - final Context context = Factory.get().getApplicationContext(); - try { - if (UriUtil.isLocalResourceUri(sourceUri)) { - inputStream = context.getContentResolver().openInputStream(sourceUri); - } else { - // The content is remote. Download it. - final URL url = new URL(sourceUri.toString()); - final URLConnection ucon = url.openConnection(); - inputStream = new BufferedInputStream(ucon.getInputStream()); - } - return persistContentToScratchSpace(inputStream); - } catch (final Exception ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex); - return null; - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e); - } - } - } - } - - /** - * Persist a piece of content from the given input stream, byte by byte to the specified - * directory. - * @return the output Uri if the operation succeeded, or null if failed. - */ - @DoesNotRunOnMainThread - public static Uri persistContent( - final InputStream inputStream, final File outputDir, final String contentType) { - if (!outputDir.exists() && !outputDir.mkdirs()) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error creating " + outputDir.getAbsolutePath()); - return null; - } - - final Context context = Factory.get().getApplicationContext(); - try { - final Uri targetUri = Uri.fromFile(FileUtil.getNewFile(outputDir, contentType)); - return copyContent(context, inputStream, targetUri); - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error creating file in " + outputDir.getAbsolutePath()); - return null; - } - } - - /** - * Persist a piece of content from the given sourceUri, byte by byte to the - * specified output directory. - * @return the output Uri if the operation succeeded, or null if failed. - */ - @DoesNotRunOnMainThread - public static Uri persistContent( - final Uri sourceUri, final File outputDir, final String contentType) { - InputStream inputStream = null; - final Context context = Factory.get().getApplicationContext(); - try { - if (UriUtil.isLocalResourceUri(sourceUri)) { - inputStream = context.getContentResolver().openInputStream(sourceUri); - } else { - // The content is remote. Download it. - final URL url = new URL(sourceUri.toString()); - final URLConnection ucon = url.openConnection(); - inputStream = new BufferedInputStream(ucon.getInputStream()); - } - return persistContent(inputStream, outputDir, contentType); - } catch (final Exception ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex); - return null; - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e); - } - } - } - } - - /** @return uri of target file, or null on error */ - @DoesNotRunOnMainThread - private static Uri copyContent( - final Context context, final InputStream inputStream, final Uri targetUri) { - Assert.isNotMainThread(); - OutputStream outputStream = null; - try { - outputStream = context.getContentResolver().openOutputStream(targetUri); - ByteStreams.copy(inputStream, outputStream); - } catch (final Exception ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error while copying content ", ex); - return null; - } finally { - if (outputStream != null) { - try { - outputStream.flush(); - } catch (final IOException e) { - LogUtil.e(LogUtil.BUGLE_TAG, "error trying to flush the outputStream", e); - return null; - } finally { - try { - outputStream.close(); - } catch (final IOException e) { - // Do nothing. - } - } - } - } - return targetUri; - } - - public static boolean isSmsMmsUri(final Uri uri) { - return uri != null && SMS_MMS_SCHEMES.contains(uri.getScheme()); - } - - /** - * Extract recipient destinations from Uri of form - * SCHEME:destionation[,destination]?otherstuff - * where SCHEME is one of the supported sms/mms schemes. - * - * @param uri sms/mms uri - * @return recipient destinations or null - */ - public static String[] parseRecipientsFromSmsMmsUri(final Uri uri) { - if (!isSmsMmsUri(uri)) { - return null; - } - final String[] parts = uri.getSchemeSpecificPart().split("\\?"); - if (TextUtils.isEmpty(parts[0])) { - return null; - } - // replaceUnicodeDigits will replace digits typed in other languages (i.e. Egyptian) with - // the usual ascii equivalents. - return TextUtil.replaceUnicodeDigits(parts[0]).replace(';', ',').split(","); - } - - /** - * Return the length of the file to which contentUri refers - * - * @param contentUri URI for the file of which we want the length - * @return Length of the file or AssetFileDescriptor.UNKNOWN_LENGTH - */ - public static long getUriContentLength(final Uri contentUri) { - final Context context = Factory.get().getApplicationContext(); - AssetFileDescriptor afd = null; - try { - afd = context.getContentResolver().openAssetFileDescriptor(contentUri, "r"); - return afd.getLength(); - } catch (final FileNotFoundException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Failed to query length of " + contentUri); - } finally { - if (afd != null) { - try { - afd.close(); - } catch (final IOException e) { - LogUtil.w(LogUtil.BUGLE_TAG, "Failed to close afd for " + contentUri); - } - } - } - return AssetFileDescriptor.UNKNOWN_LENGTH; - } - - /** - * Download data from the given url to the given local file. - * @return true if the download was successful. - * - * TODO: Add retry/exponential backoff logic. - */ - @DoesNotRunOnMainThread - public static boolean downloadDataFromUrl(final String urlString, final File localFile) { - Assert.isNotMainThread(); - LogUtil.i(LogUtil.BUGLE_TAG, "Downloading from " + urlString + " to " + localFile); - try { - Files.createParentDirs(localFile); - final URL inUrl = new URL(urlString); - ByteStreams.copy(Resources.newInputStreamSupplier(inUrl), - Files.newOutputStreamSupplier(localFile)); - return true; - } catch (final IOException ex) { - LogUtil.e(LogUtil.BUGLE_TAG, "Error downloading from " + urlString, ex); - return false; - } - } - - /** @return string representation of URI or null if URI was null */ - public static String stringFromUri(final Uri uri) { - return uri == null ? null : uri.toString(); - } - - /** @return URI created from string or null if string was null or empty */ - public static Uri uriFromString(final String uriString) { - return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString); - } -} diff --git a/src/com/android/messaging/util/VersionUtil.java b/src/com/android/messaging/util/VersionUtil.java deleted file mode 100644 index b87aa55..0000000 --- a/src/com/android/messaging/util/VersionUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; - -import java.util.Locale; - -public final class VersionUtil { - private static final Object sLock = new Object(); - private static VersionUtil sInstance; - private final String mSimpleVersionName; - private final int mVersionCode; - - public static VersionUtil getInstance(final Context context) { - synchronized (sLock) { - if (sInstance == null) { - sInstance = new VersionUtil(context); - } - } - return sInstance; - } - - private VersionUtil(final Context context) { - int versionCode; - try { - PackageInfo pi = context.getPackageManager().getPackageInfo( - context.getPackageName(), 0); - versionCode = pi.versionCode; - } catch (final NameNotFoundException exception) { - Assert.fail("couldn't get package info " + exception); - versionCode = -1; - } - mVersionCode = versionCode; - final int majorBuildNumber = versionCode / 1000; - // Use US locale to format version number so that other language characters don't - // show up in version string. - mSimpleVersionName = String.format(Locale.US, "%d.%d.%03d", - majorBuildNumber / 10000, - (majorBuildNumber / 1000) % 10, - majorBuildNumber % 1000); - } - - public int getVersionCode() { - return mVersionCode; - } - - public String getSimpleName() { - return mSimpleVersionName; - } -} diff --git a/src/com/android/messaging/util/WakeLockHelper.java b/src/com/android/messaging/util/WakeLockHelper.java deleted file mode 100644 index c9a9152..0000000 --- a/src/com/android/messaging/util/WakeLockHelper.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.Context; -import android.content.Intent; -import android.os.Debug; -import android.os.PowerManager; -import android.os.Process; - -import com.google.common.annotations.VisibleForTesting; - -/** - * Helper class used to manage wakelock state - */ -public class WakeLockHelper { - private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; - private static final boolean VERBOSE = false; - - @VisibleForTesting - public static final String EXTRA_CALLING_PID = "pid"; - - private final Object mLock = new Object(); - private final String mWakeLockId; - private final int mMyPid; - - private PowerManager.WakeLock mWakeLock; - - public WakeLockHelper(final String wakeLockId) { - mWakeLockId = wakeLockId; - mMyPid = Process.myPid(); - } - - /** - * Acquire the wakelock - */ - public void acquire(final Context context, final Intent intent, final int opcode) { - synchronized (mLock) { - if (mWakeLock == null) { - if (VERBOSE) { - LogUtil.v(TAG, "initializing wakelock"); - } - final PowerManager pm = (PowerManager) - context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mWakeLockId); - } - } - if (VERBOSE) { - LogUtil.v(TAG, "acquiring " + mWakeLockId + " for opcode " + opcode); - } - mWakeLock.acquire(); - intent.putExtra(EXTRA_CALLING_PID, mMyPid); - } - - /** - * Check if wakelock held by this process - */ - public boolean isHeld(final Intent intent) { - final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1)); - return (respectWakeLock && mWakeLock.isHeld()); - } - - /** - * Ensure that wakelock is held by this process - */ - public boolean ensure(final Intent intent, final int opcode) { - final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1)); - if (VERBOSE) { - LogUtil.v(TAG, "WakeLockHelper.ensure Intent " + intent + " " - + intent.getAction() + " opcode: " + opcode - + " respectWakeLock " + respectWakeLock); - } - - if (respectWakeLock) { - final boolean isHeld = (respectWakeLock && isHeld(intent)); - if (!isHeld) { - LogUtil.e(TAG, "WakeLockHelper.ensure called " + intent + " " + intent.getAction() - + " opcode: " + opcode + " sWakeLock: " + mWakeLock + " isHeld: " - + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld())); - if (!Debug.isDebuggerConnected()) { - Assert.fail("WakeLock dropped prior to service starting"); - } - } - return true; - } - return false; - } - - /** - * Release wakelock (if it is held by this process) - */ - public void release(final Intent intent, final int opcode) { - final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1)); - if (respectWakeLock) { - try { - mWakeLock.release(); - } catch (final RuntimeException ex) { - LogUtil.e(TAG, "KeepAliveService.onHandleIntent exit crash " + intent + " " - + intent.getAction() + " opcode: " + opcode + " sWakeLock: " + mWakeLock - + " isHeld: " + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld())); - if (!Debug.isDebuggerConnected()) { - Assert.fail("WakeLock no longer held at end of handler"); - } - } - } - } -} diff --git a/src/com/android/messaging/util/YouTubeUtil.java b/src/com/android/messaging/util/YouTubeUtil.java deleted file mode 100644 index 203a666..0000000 --- a/src/com/android/messaging/util/YouTubeUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.net.Uri; -import android.text.TextUtils; - -public class YouTubeUtil { - private static final String YOUTUBE_HOST_1 = "www.youtube.com"; - private static final String YOUTUBE_HOST_2 = "youtube.com"; - private static final String YOUTUBE_HOST_3 = "m.youtube.com"; - private static final String YOUTUBE_HOST_4 = "youtube.googleapis.com"; - private static final String YOUTUBE_HOST_5 = "youtu.be"; - - private static final String YOUTUBE_PATH_1 = "/watch"; - private static final String YOUTUBE_PATH_2 = "/embed/"; - private static final String YOUTUBE_PATH_3 = "/v/"; - private static final String YOUTUBE_PATH_4 = "/apiplayer"; - - public static final String YOUTUBE_STATIC_THUMBNAIL_PREFIX = "https://img.youtube.com/vi/"; - public static final String YOUTUBE_STATIC_THUMBNAIL_END = "/hqdefault.jpg"; - - public static String getYoutubePreviewImageLink(String urlString) { - // Types of youtube urls: - // 1.) http://www.youtube.com/watch?v=VIDEOID - // 2.) http://www.youtube.com/embed/VIDEOID - // 3.) http://www.youtube.com/v/VIDEOID - // 3a.) https://youtube.googleapis.com/v/VIDEOID - // 4.) http://www.youtube.com/apiplayer?video_id=VIDEO_ID - // 5.) http://youtu.be/VIDEOID - if (!urlString.startsWith("http")) { - // Apparently the url is not an RFC 2396 compliant uri without the port - urlString = "http://" + urlString; - } - final Uri uri = Uri.parse(urlString); - final String host = uri.getHost(); - if (YOUTUBE_HOST_1.equalsIgnoreCase(host) - || YOUTUBE_HOST_2.equalsIgnoreCase(host) - || YOUTUBE_HOST_3.equalsIgnoreCase(host) - || YOUTUBE_HOST_4.equalsIgnoreCase(host) - || YOUTUBE_HOST_5.equalsIgnoreCase(host)) { - final String videoId = getYouTubeVideoId(uri); - if (!TextUtils.isEmpty(videoId)) { - return YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId + YOUTUBE_STATIC_THUMBNAIL_END; - } - return null; - } - return null; - } - - private static String getYouTubeVideoId(Uri uri) { - final String urlPath = uri.getPath(); - - if (TextUtils.isEmpty(urlPath)) { - // There is no path so no need to continue. - return null; - } - // Case 1 - if (urlPath.startsWith(YOUTUBE_PATH_1)) { - return uri.getQueryParameter("v"); - } - // Case 2 - if (urlPath.startsWith(YOUTUBE_PATH_2)) { - return getVideoIdFromPath(YOUTUBE_PATH_2, urlPath); - } - // Case 3 - if (urlPath.startsWith(YOUTUBE_PATH_3)) { - return getVideoIdFromPath(YOUTUBE_PATH_3, urlPath); - } - // Case 4 - if (urlPath.startsWith(YOUTUBE_PATH_4)) { - return uri.getQueryParameter("video_id"); - } - // Case 5 - if (YOUTUBE_HOST_5.equalsIgnoreCase(uri.getHost())) { - return getVideoIdFromPath("/", urlPath); - } - return null; - } - - private static String getVideoIdFromPath(String prefixSubstring, String urlPath) { - return urlPath.substring(prefixSubstring.length()); - } - -} diff --git a/src/com/android/messaging/util/exif/ByteBufferInputStream.java b/src/com/android/messaging/util/exif/ByteBufferInputStream.java deleted file mode 100644 index 9db92ef..0000000 --- a/src/com/android/messaging/util/exif/ByteBufferInputStream.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import java.io.InputStream; -import java.nio.ByteBuffer; - -class ByteBufferInputStream extends InputStream { - - private final ByteBuffer mBuf; - - public ByteBufferInputStream(ByteBuffer buf) { - mBuf = buf; - } - - @Override - public int read() { - if (!mBuf.hasRemaining()) { - return -1; - } - return mBuf.get() & 0xFF; - } - - @Override - public int read(byte[] bytes, int off, int len) { - if (!mBuf.hasRemaining()) { - return -1; - } - - len = Math.min(len, mBuf.remaining()); - mBuf.get(bytes, off, len); - return len; - } -} diff --git a/src/com/android/messaging/util/exif/CountedDataInputStream.java b/src/com/android/messaging/util/exif/CountedDataInputStream.java deleted file mode 100644 index ce766d9..0000000 --- a/src/com/android/messaging/util/exif/CountedDataInputStream.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.Charset; - -class CountedDataInputStream extends FilterInputStream { - - private int mCount = 0; - - // allocate a byte buffer for a long value; - private final byte mByteArray[] = new byte[8]; - private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray); - - protected CountedDataInputStream(InputStream in) { - super(in); - } - - public int getReadByteCount() { - return mCount; - } - - @Override - public int read(byte[] b) throws IOException { - int r = in.read(b); - mCount += (r >= 0) ? r : 0; - return r; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int r = in.read(b, off, len); - mCount += (r >= 0) ? r : 0; - return r; - } - - @Override - public int read() throws IOException { - int r = in.read(); - mCount += (r >= 0) ? 1 : 0; - return r; - } - - @Override - public long skip(long length) throws IOException { - long skip = in.skip(length); - mCount += skip; - return skip; - } - - public void skipOrThrow(long length) throws IOException { - if (skip(length) != length) { - throw new EOFException(); - } - } - - public void skipTo(long target) throws IOException { - long cur = mCount; - long diff = target - cur; - assert(diff >= 0); - skipOrThrow(diff); - } - - public void readOrThrow(byte[] b, int off, int len) throws IOException { - int r = read(b, off, len); - if (r != len) { - throw new EOFException(); - } - } - - public void readOrThrow(byte[] b) throws IOException { - readOrThrow(b, 0, b.length); - } - - public void setByteOrder(ByteOrder order) { - mByteBuffer.order(order); - } - - public ByteOrder getByteOrder() { - return mByteBuffer.order(); - } - - public short readShort() throws IOException { - readOrThrow(mByteArray, 0 , 2); - mByteBuffer.rewind(); - return mByteBuffer.getShort(); - } - - public int readUnsignedShort() throws IOException { - return readShort() & 0xffff; - } - - public int readInt() throws IOException { - readOrThrow(mByteArray, 0 , 4); - mByteBuffer.rewind(); - return mByteBuffer.getInt(); - } - - public long readUnsignedInt() throws IOException { - return readInt() & 0xffffffffL; - } - - public long readLong() throws IOException { - readOrThrow(mByteArray, 0 , 8); - mByteBuffer.rewind(); - return mByteBuffer.getLong(); - } - - public String readString(int n) throws IOException { - byte buf[] = new byte[n]; - readOrThrow(buf); - return new String(buf, "UTF8"); - } - - public String readString(int n, Charset charset) throws IOException { - byte buf[] = new byte[n]; - readOrThrow(buf); - return new String(buf, charset); - } -} diff --git a/src/com/android/messaging/util/exif/ExifData.java b/src/com/android/messaging/util/exif/ExifData.java deleted file mode 100644 index 77ba4e9..0000000 --- a/src/com/android/messaging/util/exif/ExifData.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.util.Log; -import com.android.messaging.util.LogUtil; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * This class stores the EXIF header in IFDs according to the JPEG - * specification. It is the result produced by {@link ExifReader}. - * - * @see ExifReader - * @see IfdData - */ -class ExifData { - private static final String TAG = LogUtil.BUGLE_TAG; - private static final byte[] USER_COMMENT_ASCII = { - 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 - }; - private static final byte[] USER_COMMENT_JIS = { - 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - private static final byte[] USER_COMMENT_UNICODE = { - 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 - }; - - private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; - private byte[] mThumbnail; - private final ArrayList mStripBytes = new ArrayList(); - private final ByteOrder mByteOrder; - - ExifData(ByteOrder order) { - mByteOrder = order; - } - - /** - * Gets the compressed thumbnail. Returns null if there is no compressed - * thumbnail. - * - * @see #hasCompressedThumbnail() - */ - protected byte[] getCompressedThumbnail() { - return mThumbnail; - } - - /** - * Sets the compressed thumbnail. - */ - protected void setCompressedThumbnail(byte[] thumbnail) { - mThumbnail = thumbnail; - } - - /** - * Returns true it this header contains a compressed thumbnail. - */ - protected boolean hasCompressedThumbnail() { - return mThumbnail != null; - } - - /** - * Adds an uncompressed strip. - */ - protected void setStripBytes(int index, byte[] strip) { - if (index < mStripBytes.size()) { - mStripBytes.set(index, strip); - } else { - for (int i = mStripBytes.size(); i < index; i++) { - mStripBytes.add(null); - } - mStripBytes.add(strip); - } - } - - /** - * Gets the strip count. - */ - protected int getStripCount() { - return mStripBytes.size(); - } - - /** - * Gets the strip at the specified index. - * - * @exceptions #IndexOutOfBoundException - */ - protected byte[] getStrip(int index) { - return mStripBytes.get(index); - } - - /** - * Returns true if this header contains uncompressed strip. - */ - protected boolean hasUncompressedStrip() { - return mStripBytes.size() != 0; - } - - /** - * Gets the byte order. - */ - protected ByteOrder getByteOrder() { - return mByteOrder; - } - - /** - * Returns the {@link IfdData} object corresponding to a given IFD if it - * exists or null. - */ - protected IfdData getIfdData(int ifdId) { - if (ExifTag.isValidIfd(ifdId)) { - return mIfdDatas[ifdId]; - } - return null; - } - - /** - * Adds IFD data. If IFD data of the same type already exists, it will be - * replaced by the new data. - */ - protected void addIfdData(IfdData data) { - mIfdDatas[data.getId()] = data; - } - - /** - * Returns the {@link IfdData} object corresponding to a given IFD or - * generates one if none exist. - */ - protected IfdData getOrCreateIfdData(int ifdId) { - IfdData ifdData = mIfdDatas[ifdId]; - if (ifdData == null) { - ifdData = new IfdData(ifdId); - mIfdDatas[ifdId] = ifdData; - } - return ifdData; - } - - /** - * Returns the tag with a given TID in the given IFD if the tag exists. - * Otherwise returns null. - */ - protected ExifTag getTag(short tag, int ifd) { - IfdData ifdData = mIfdDatas[ifd]; - return (ifdData == null) ? null : ifdData.getTag(tag); - } - - /** - * Adds the given ExifTag to its default IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - protected ExifTag addTag(ExifTag tag) { - if (tag != null) { - int ifd = tag.getIfd(); - return addTag(tag, ifd); - } - return null; - } - - /** - * Adds the given ExifTag to the given IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - protected ExifTag addTag(ExifTag tag, int ifdId) { - if (tag != null && ExifTag.isValidIfd(ifdId)) { - IfdData ifdData = getOrCreateIfdData(ifdId); - return ifdData.setTag(tag); - } - return null; - } - - protected void clearThumbnailAndStrips() { - mThumbnail = null; - mStripBytes.clear(); - } - - /** - * Removes the thumbnail and its related tags. IFD1 will be removed. - */ - protected void removeThumbnailData() { - clearThumbnailAndStrips(); - mIfdDatas[IfdId.TYPE_IFD_1] = null; - } - - /** - * Removes the tag with a given TID and IFD. - */ - protected void removeTag(short tagId, int ifdId) { - IfdData ifdData = mIfdDatas[ifdId]; - if (ifdData == null) { - return; - } - ifdData.removeTag(tagId); - } - - /** - * Decodes the user comment tag into string as specified in the EXIF - * standard. Returns null if decoding failed. - */ - protected String getUserComment() { - IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; - if (ifdData == null) { - return null; - } - ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)); - if (tag == null) { - return null; - } - if (tag.getComponentCount() < 8) { - return null; - } - - byte[] buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - - byte[] code = new byte[8]; - System.arraycopy(buf, 0, code, 0, 8); - - try { - if (Arrays.equals(code, USER_COMMENT_ASCII)) { - return new String(buf, 8, buf.length - 8, "US-ASCII"); - } else if (Arrays.equals(code, USER_COMMENT_JIS)) { - return new String(buf, 8, buf.length - 8, "EUC-JP"); - } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) { - return new String(buf, 8, buf.length - 8, "UTF-16"); - } else { - return null; - } - } catch (UnsupportedEncodingException e) { - Log.w(TAG, "Failed to decode the user comment"); - return null; - } - } - - /** - * Returns a list of all {@link ExifTag}s in the ExifData or null if there - * are none. - */ - protected List getAllTags() { - ArrayList ret = new ArrayList(); - for (IfdData d : mIfdDatas) { - if (d != null) { - ExifTag[] tags = d.getAllTags(); - if (tags != null) { - for (ExifTag t : tags) { - ret.add(t); - } - } - } - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - /** - * Returns a list of all {@link ExifTag}s in a given IFD or null if there - * are none. - */ - protected List getAllTagsForIfd(int ifd) { - IfdData d = mIfdDatas[ifd]; - if (d == null) { - return null; - } - ExifTag[] tags = d.getAllTags(); - if (tags == null) { - return null; - } - ArrayList ret = new ArrayList(tags.length); - for (ExifTag t : tags) { - ret.add(t); - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - /** - * Returns a list of all {@link ExifTag}s with a given TID or null if there - * are none. - */ - protected List getAllTagsForTagId(short tag) { - ArrayList ret = new ArrayList(); - for (IfdData d : mIfdDatas) { - if (d != null) { - ExifTag t = d.getTag(tag); - if (t != null) { - ret.add(t); - } - } - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (obj instanceof ExifData) { - ExifData data = (ExifData) obj; - if (data.mByteOrder != mByteOrder || - data.mStripBytes.size() != mStripBytes.size() || - !Arrays.equals(data.mThumbnail, mThumbnail)) { - return false; - } - for (int i = 0; i < mStripBytes.size(); i++) { - if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { - return false; - } - } - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - IfdData ifd1 = data.getIfdData(i); - IfdData ifd2 = getIfdData(i); - if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { - return false; - } - } - return true; - } - return false; - } - -} diff --git a/src/com/android/messaging/util/exif/ExifInterface.java b/src/com/android/messaging/util/exif/ExifInterface.java deleted file mode 100644 index b556748..0000000 --- a/src/com/android/messaging/util/exif/ExifInterface.java +++ /dev/null @@ -1,2448 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.SparseIntArray; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel.MapMode; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.TimeZone; - -/** - * This class provides methods and constants for reading and writing jpeg file - * metadata. It contains a collection of ExifTags, and a collection of - * definitions for creating valid ExifTags. The collection of ExifTags can be - * updated by: reading new ones from a file, deleting or adding existing ones, - * or building new ExifTags from a tag definition. These ExifTags can be written - * to a valid jpeg image as exif metadata. - *

- * Each ExifTag has a tag ID (TID) and is stored in a specific image file - * directory (IFD) as specified by the exif standard. A tag definition can be - * looked up with a constant that is a combination of TID and IFD. This - * definition has information about the type, number of components, and valid - * IFDs for a tag. - * - * @see ExifTag - */ -public class ExifInterface { - public static final int TAG_NULL = -1; - public static final int IFD_NULL = -1; - public static final int DEFINITION_NULL = 0; - - /** - * Tag constants for Jeita EXIF 2.2 - */ - - // IFD 0 - public static final int TAG_IMAGE_WIDTH = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0100); - public static final int TAG_IMAGE_LENGTH = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height - public static final int TAG_BITS_PER_SAMPLE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0102); - public static final int TAG_COMPRESSION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0103); - public static final int TAG_PHOTOMETRIC_INTERPRETATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0106); - public static final int TAG_IMAGE_DESCRIPTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x010E); - public static final int TAG_MAKE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x010F); - public static final int TAG_MODEL = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0110); - public static final int TAG_STRIP_OFFSETS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0111); - public static final int TAG_ORIENTATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0112); - public static final int TAG_SAMPLES_PER_PIXEL = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0115); - public static final int TAG_ROWS_PER_STRIP = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0116); - public static final int TAG_STRIP_BYTE_COUNTS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0117); - public static final int TAG_X_RESOLUTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011A); - public static final int TAG_Y_RESOLUTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011B); - public static final int TAG_PLANAR_CONFIGURATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011C); - public static final int TAG_RESOLUTION_UNIT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0128); - public static final int TAG_TRANSFER_FUNCTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x012D); - public static final int TAG_SOFTWARE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0131); - public static final int TAG_DATE_TIME = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0132); - public static final int TAG_ARTIST = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013B); - public static final int TAG_WHITE_POINT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013E); - public static final int TAG_PRIMARY_CHROMATICITIES = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013F); - public static final int TAG_Y_CB_CR_COEFFICIENTS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0211); - public static final int TAG_Y_CB_CR_SUB_SAMPLING = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0212); - public static final int TAG_Y_CB_CR_POSITIONING = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0213); - public static final int TAG_REFERENCE_BLACK_WHITE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0214); - public static final int TAG_COPYRIGHT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8298); - public static final int TAG_EXIF_IFD = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8769); - public static final int TAG_GPS_IFD = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8825); - // IFD 1 - public static final int TAG_JPEG_INTERCHANGE_FORMAT = - defineTag(IfdId.TYPE_IFD_1, (short) 0x0201); - public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = - defineTag(IfdId.TYPE_IFD_1, (short) 0x0202); - // IFD Exif Tags - public static final int TAG_EXPOSURE_TIME = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A); - public static final int TAG_F_NUMBER = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D); - public static final int TAG_EXPOSURE_PROGRAM = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822); - public static final int TAG_SPECTRAL_SENSITIVITY = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824); - public static final int TAG_ISO_SPEED_RATINGS = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827); - public static final int TAG_OECF = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828); - public static final int TAG_EXIF_VERSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000); - public static final int TAG_DATE_TIME_ORIGINAL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003); - public static final int TAG_DATE_TIME_DIGITIZED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004); - public static final int TAG_COMPONENTS_CONFIGURATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101); - public static final int TAG_COMPRESSED_BITS_PER_PIXEL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102); - public static final int TAG_SHUTTER_SPEED_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201); - public static final int TAG_APERTURE_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202); - public static final int TAG_BRIGHTNESS_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203); - public static final int TAG_EXPOSURE_BIAS_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204); - public static final int TAG_MAX_APERTURE_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205); - public static final int TAG_SUBJECT_DISTANCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206); - public static final int TAG_METERING_MODE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207); - public static final int TAG_LIGHT_SOURCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208); - public static final int TAG_FLASH = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209); - public static final int TAG_FOCAL_LENGTH = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A); - public static final int TAG_SUBJECT_AREA = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214); - public static final int TAG_MAKER_NOTE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C); - public static final int TAG_USER_COMMENT = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286); - public static final int TAG_SUB_SEC_TIME = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290); - public static final int TAG_SUB_SEC_TIME_ORIGINAL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291); - public static final int TAG_SUB_SEC_TIME_DIGITIZED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292); - public static final int TAG_FLASHPIX_VERSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000); - public static final int TAG_COLOR_SPACE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001); - public static final int TAG_PIXEL_X_DIMENSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002); - public static final int TAG_PIXEL_Y_DIMENSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003); - public static final int TAG_RELATED_SOUND_FILE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004); - public static final int TAG_INTEROPERABILITY_IFD = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005); - public static final int TAG_FLASH_ENERGY = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B); - public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C); - public static final int TAG_FOCAL_PLANE_X_RESOLUTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E); - public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F); - public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210); - public static final int TAG_SUBJECT_LOCATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214); - public static final int TAG_EXPOSURE_INDEX = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215); - public static final int TAG_SENSING_METHOD = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217); - public static final int TAG_FILE_SOURCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300); - public static final int TAG_SCENE_TYPE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301); - public static final int TAG_CFA_PATTERN = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302); - public static final int TAG_CUSTOM_RENDERED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401); - public static final int TAG_EXPOSURE_MODE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402); - public static final int TAG_WHITE_BALANCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403); - public static final int TAG_DIGITAL_ZOOM_RATIO = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404); - public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405); - public static final int TAG_SCENE_CAPTURE_TYPE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406); - public static final int TAG_GAIN_CONTROL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407); - public static final int TAG_CONTRAST = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408); - public static final int TAG_SATURATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409); - public static final int TAG_SHARPNESS = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A); - public static final int TAG_DEVICE_SETTING_DESCRIPTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B); - public static final int TAG_SUBJECT_DISTANCE_RANGE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C); - public static final int TAG_IMAGE_UNIQUE_ID = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420); - // IFD GPS tags - public static final int TAG_GPS_VERSION_ID = - defineTag(IfdId.TYPE_IFD_GPS, (short) 0); - public static final int TAG_GPS_LATITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 1); - public static final int TAG_GPS_LATITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 2); - public static final int TAG_GPS_LONGITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 3); - public static final int TAG_GPS_LONGITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 4); - public static final int TAG_GPS_ALTITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 5); - public static final int TAG_GPS_ALTITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 6); - public static final int TAG_GPS_TIME_STAMP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 7); - public static final int TAG_GPS_SATTELLITES = - defineTag(IfdId.TYPE_IFD_GPS, (short) 8); - public static final int TAG_GPS_STATUS = - defineTag(IfdId.TYPE_IFD_GPS, (short) 9); - public static final int TAG_GPS_MEASURE_MODE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 10); - public static final int TAG_GPS_DOP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 11); - public static final int TAG_GPS_SPEED_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 12); - public static final int TAG_GPS_SPEED = - defineTag(IfdId.TYPE_IFD_GPS, (short) 13); - public static final int TAG_GPS_TRACK_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 14); - public static final int TAG_GPS_TRACK = - defineTag(IfdId.TYPE_IFD_GPS, (short) 15); - public static final int TAG_GPS_IMG_DIRECTION_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 16); - public static final int TAG_GPS_IMG_DIRECTION = - defineTag(IfdId.TYPE_IFD_GPS, (short) 17); - public static final int TAG_GPS_MAP_DATUM = - defineTag(IfdId.TYPE_IFD_GPS, (short) 18); - public static final int TAG_GPS_DEST_LATITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 19); - public static final int TAG_GPS_DEST_LATITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 20); - public static final int TAG_GPS_DEST_LONGITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 21); - public static final int TAG_GPS_DEST_LONGITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 22); - public static final int TAG_GPS_DEST_BEARING_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 23); - public static final int TAG_GPS_DEST_BEARING = - defineTag(IfdId.TYPE_IFD_GPS, (short) 24); - public static final int TAG_GPS_DEST_DISTANCE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 25); - public static final int TAG_GPS_DEST_DISTANCE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 26); - public static final int TAG_GPS_PROCESSING_METHOD = - defineTag(IfdId.TYPE_IFD_GPS, (short) 27); - public static final int TAG_GPS_AREA_INFORMATION = - defineTag(IfdId.TYPE_IFD_GPS, (short) 28); - public static final int TAG_GPS_DATE_STAMP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 29); - public static final int TAG_GPS_DIFFERENTIAL = - defineTag(IfdId.TYPE_IFD_GPS, (short) 30); - // IFD Interoperability tags - public static final int TAG_INTEROPERABILITY_INDEX = - defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1); - - /** - * Tags that contain offset markers. These are included in the banned - * defines. - */ - private static HashSet sOffsetTags = new HashSet(); - static { - sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)); - sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS)); - } - - /** - * Tags with definitions that cannot be overridden (banned defines). - */ - protected static HashSet sBannedDefines = new HashSet(sOffsetTags); - static { - sBannedDefines.add(getTrueTagKey(TAG_NULL)); - sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS)); - } - - /** - * Returns the constant representing a tag with a given TID and default IFD. - */ - public static int defineTag(int ifdId, short tagId) { - return (tagId & 0x0000ffff) | (ifdId << 16); - } - - /** - * Returns the TID for a tag constant. - */ - public static short getTrueTagKey(int tag) { - // Truncate - return (short) tag; - } - - /** - * Returns the default IFD for a tag constant. - */ - public static int getTrueIfd(int tag) { - return tag >>> 16; - } - - /** - * Constants for {@link TAG_ORIENTATION}. They can be interpreted as - * follows: - *

    - *
  • TOP_LEFT is the normal orientation.
  • - *
  • TOP_RIGHT is a left-right mirror.
  • - *
  • BOTTOM_LEFT is a 180 degree rotation.
  • - *
  • BOTTOM_RIGHT is a top-bottom mirror.
  • - *
  • LEFT_TOP is mirrored about the top-left<->bottom-right axis.
  • - *
  • RIGHT_TOP is a 90 degree clockwise rotation.
  • - *
  • LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
  • - *
  • RIGHT_BOTTOM is a 270 degree clockwise rotation.
  • - *
- */ - public static interface Orientation { - public static final short TOP_LEFT = 1; - public static final short TOP_RIGHT = 2; - public static final short BOTTOM_LEFT = 3; - public static final short BOTTOM_RIGHT = 4; - public static final short LEFT_TOP = 5; - public static final short RIGHT_TOP = 6; - public static final short LEFT_BOTTOM = 7; - public static final short RIGHT_BOTTOM = 8; - } - - /** - * Constants for {@link TAG_Y_CB_CR_POSITIONING} - */ - public static interface YCbCrPositioning { - public static final short CENTERED = 1; - public static final short CO_SITED = 2; - } - - /** - * Constants for {@link TAG_COMPRESSION} - */ - public static interface Compression { - public static final short UNCOMPRESSION = 1; - public static final short JPEG = 6; - } - - /** - * Constants for {@link TAG_RESOLUTION_UNIT} - */ - public static interface ResolutionUnit { - public static final short INCHES = 2; - public static final short CENTIMETERS = 3; - } - - /** - * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION} - */ - public static interface PhotometricInterpretation { - public static final short RGB = 2; - public static final short YCBCR = 6; - } - - /** - * Constants for {@link TAG_PLANAR_CONFIGURATION} - */ - public static interface PlanarConfiguration { - public static final short CHUNKY = 1; - public static final short PLANAR = 2; - } - - /** - * Constants for {@link TAG_EXPOSURE_PROGRAM} - */ - public static interface ExposureProgram { - public static final short NOT_DEFINED = 0; - public static final short MANUAL = 1; - public static final short NORMAL_PROGRAM = 2; - public static final short APERTURE_PRIORITY = 3; - public static final short SHUTTER_PRIORITY = 4; - public static final short CREATIVE_PROGRAM = 5; - public static final short ACTION_PROGRAM = 6; - public static final short PROTRAIT_MODE = 7; - public static final short LANDSCAPE_MODE = 8; - } - - /** - * Constants for {@link TAG_METERING_MODE} - */ - public static interface MeteringMode { - public static final short UNKNOWN = 0; - public static final short AVERAGE = 1; - public static final short CENTER_WEIGHTED_AVERAGE = 2; - public static final short SPOT = 3; - public static final short MULTISPOT = 4; - public static final short PATTERN = 5; - public static final short PARTAIL = 6; - public static final short OTHER = 255; - } - - /** - * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2 - * standard, we can treat this constant as bitwise flag. - *

- * e.g. - *

- * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | - * MODE_AUTO_MODE - */ - public static interface Flash { - // LSB - public static final short DID_NOT_FIRED = 0; - public static final short FIRED = 1; - // 1st~2nd bits - public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1; - // 3rd~4th bits - public static final short MODE_UNKNOWN = 0 << 3; - public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3; - public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3; - public static final short MODE_AUTO_MODE = 3 << 3; - // 5th bit - public static final short FUNCTION_PRESENT = 0 << 5; - public static final short FUNCTION_NO_FUNCTION = 1 << 5; - // 6th bit - public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6; - public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6; - } - - /** - * Constants for {@link TAG_COLOR_SPACE} - */ - public static interface ColorSpace { - public static final short SRGB = 1; - public static final short UNCALIBRATED = (short) 0xFFFF; - } - - /** - * Constants for {@link TAG_EXPOSURE_MODE} - */ - public static interface ExposureMode { - public static final short AUTO_EXPOSURE = 0; - public static final short MANUAL_EXPOSURE = 1; - public static final short AUTO_BRACKET = 2; - } - - /** - * Constants for {@link TAG_WHITE_BALANCE} - */ - public static interface WhiteBalance { - public static final short AUTO = 0; - public static final short MANUAL = 1; - } - - /** - * Constants for {@link TAG_SCENE_CAPTURE_TYPE} - */ - public static interface SceneCapture { - public static final short STANDARD = 0; - public static final short LANDSCAPE = 1; - public static final short PROTRAIT = 2; - public static final short NIGHT_SCENE = 3; - } - - /** - * Constants for {@link TAG_COMPONENTS_CONFIGURATION} - */ - public static interface ComponentsConfiguration { - public static final short NOT_EXIST = 0; - public static final short Y = 1; - public static final short CB = 2; - public static final short CR = 3; - public static final short R = 4; - public static final short G = 5; - public static final short B = 6; - } - - /** - * Constants for {@link TAG_LIGHT_SOURCE} - */ - public static interface LightSource { - public static final short UNKNOWN = 0; - public static final short DAYLIGHT = 1; - public static final short FLUORESCENT = 2; - public static final short TUNGSTEN = 3; - public static final short FLASH = 4; - public static final short FINE_WEATHER = 9; - public static final short CLOUDY_WEATHER = 10; - public static final short SHADE = 11; - public static final short DAYLIGHT_FLUORESCENT = 12; - public static final short DAY_WHITE_FLUORESCENT = 13; - public static final short COOL_WHITE_FLUORESCENT = 14; - public static final short WHITE_FLUORESCENT = 15; - public static final short STANDARD_LIGHT_A = 17; - public static final short STANDARD_LIGHT_B = 18; - public static final short STANDARD_LIGHT_C = 19; - public static final short D55 = 20; - public static final short D65 = 21; - public static final short D75 = 22; - public static final short D50 = 23; - public static final short ISO_STUDIO_TUNGSTEN = 24; - public static final short OTHER = 255; - } - - /** - * Constants for {@link TAG_SENSING_METHOD} - */ - public static interface SensingMethod { - public static final short NOT_DEFINED = 1; - public static final short ONE_CHIP_COLOR = 2; - public static final short TWO_CHIP_COLOR = 3; - public static final short THREE_CHIP_COLOR = 4; - public static final short COLOR_SEQUENTIAL_AREA = 5; - public static final short TRILINEAR = 7; - public static final short COLOR_SEQUENTIAL_LINEAR = 8; - } - - /** - * Constants for {@link TAG_FILE_SOURCE} - */ - public static interface FileSource { - public static final short DSC = 3; - } - - /** - * Constants for {@link TAG_SCENE_TYPE} - */ - public static interface SceneType { - public static final short DIRECT_PHOTOGRAPHED = 1; - } - - /** - * Constants for {@link TAG_GAIN_CONTROL} - */ - public static interface GainControl { - public static final short NONE = 0; - public static final short LOW_UP = 1; - public static final short HIGH_UP = 2; - public static final short LOW_DOWN = 3; - public static final short HIGH_DOWN = 4; - } - - /** - * Constants for {@link TAG_CONTRAST} - */ - public static interface Contrast { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link TAG_SATURATION} - */ - public static interface Saturation { - public static final short NORMAL = 0; - public static final short LOW = 1; - public static final short HIGH = 2; - } - - /** - * Constants for {@link TAG_SHARPNESS} - */ - public static interface Sharpness { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link TAG_SUBJECT_DISTANCE} - */ - public static interface SubjectDistance { - public static final short UNKNOWN = 0; - public static final short MACRO = 1; - public static final short CLOSE_VIEW = 2; - public static final short DISTANT_VIEW = 3; - } - - /** - * Constants for {@link TAG_GPS_LATITUDE_REF}, - * {@link TAG_GPS_DEST_LATITUDE_REF} - */ - public static interface GpsLatitudeRef { - public static final String NORTH = "N"; - public static final String SOUTH = "S"; - } - - /** - * Constants for {@link TAG_GPS_LONGITUDE_REF}, - * {@link TAG_GPS_DEST_LONGITUDE_REF} - */ - public static interface GpsLongitudeRef { - public static final String EAST = "E"; - public static final String WEST = "W"; - } - - /** - * Constants for {@link TAG_GPS_ALTITUDE_REF} - */ - public static interface GpsAltitudeRef { - public static final short SEA_LEVEL = 0; - public static final short SEA_LEVEL_NEGATIVE = 1; - } - - /** - * Constants for {@link TAG_GPS_STATUS} - */ - public static interface GpsStatus { - public static final String IN_PROGRESS = "A"; - public static final String INTEROPERABILITY = "V"; - } - - /** - * Constants for {@link TAG_GPS_MEASURE_MODE} - */ - public static interface GpsMeasureMode { - public static final String MODE_2_DIMENSIONAL = "2"; - public static final String MODE_3_DIMENSIONAL = "3"; - } - - /** - * Constants for {@link TAG_GPS_SPEED_REF}, - * {@link TAG_GPS_DEST_DISTANCE_REF} - */ - public static interface GpsSpeedRef { - public static final String KILOMETERS = "K"; - public static final String MILES = "M"; - public static final String KNOTS = "N"; - } - - /** - * Constants for {@link TAG_GPS_TRACK_REF}, - * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF} - */ - public static interface GpsTrackRef { - public static final String TRUE_DIRECTION = "T"; - public static final String MAGNETIC_DIRECTION = "M"; - } - - /** - * Constants for {@link TAG_GPS_DIFFERENTIAL} - */ - public static interface GpsDifferential { - public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0; - public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1; - } - - private static final String NULL_ARGUMENT_STRING = "Argument is null"; - private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER); - public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN; - - public ExifInterface() { - mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - /** - * Reads the exif tags from a byte array, clearing this ExifInterface - * object's existing exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @throws java.io.IOException - */ - public void readExif(byte[] jpeg) throws IOException { - readExif(new ByteArrayInputStream(jpeg)); - } - - /** - * Reads the exif tags from an InputStream, clearing this ExifInterface - * object's existing exif tags. - * - * @param inStream an InputStream containing a jpeg compressed image. - * @throws java.io.IOException - */ - public void readExif(InputStream inStream) throws IOException { - if (inStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - ExifData d = null; - try { - d = new ExifReader(this).read(inStream); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : " + e); - } - mData = d; - } - - /** - * Reads the exif tags from a file, clearing this ExifInterface object's - * existing exif tags. - * - * @param inFileName a string representing the filepath to jpeg file. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public void readExif(String inFileName) throws FileNotFoundException, IOException { - if (inFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(inFileName)); - readExif(is); - } catch (IOException e) { - closeSilently(is); - throw e; - } - is.close(); - } - - /** - * Sets the exif tags, clearing this ExifInterface object's existing exif - * tags. - * - * @param tags a collection of exif tags to set. - */ - public void setExif(Collection tags) { - clearExif(); - setTags(tags); - } - - /** - * Clears this ExifInterface object's existing exif tags. - */ - public void clearExif() { - mData = new ExifData(DEFAULT_BYTE_ORDER); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg image, - * removing prior exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @param exifOutStream an OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws java.io.IOException - */ - public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException { - if (jpeg == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - s.write(jpeg, 0, jpeg.length); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg compressed - * bitmap, removing prior exif tags. - * - * @param bmap a bitmap to compress and write exif into. - * @param exifOutStream the OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws java.io.IOException - */ - public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException { - if (bmap == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg stream, - * removing prior exif tags. - * - * @param jpegStream an InputStream containing a jpeg compressed image. - * @param exifOutStream an OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws java.io.IOException - */ - public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException { - if (jpegStream == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - doExifStreamIO(jpegStream, s); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg image, - * removing prior exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException, - IOException { - if (jpeg == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - s.write(jpeg, 0, jpeg.length); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg compressed - * bitmap, removing prior exif tags. - * - * @param bmap a bitmap to compress and write exif into. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException, - IOException { - if (bmap == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg stream, - * removing prior exif tags. - * - * @param jpegStream an InputStream containing a jpeg compressed image. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public void writeExif(InputStream jpegStream, String exifOutFileName) - throws FileNotFoundException, IOException { - if (jpegStream == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - doExifStreamIO(jpegStream, s); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg file, removing - * prior exif tags. - * - * @param jpegFileName a String containing the filepath for a jpeg file. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public void writeExif(String jpegFileName, String exifOutFileName) - throws FileNotFoundException, IOException { - if (jpegFileName == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - InputStream is = null; - try { - is = new FileInputStream(jpegFileName); - writeExif(is, exifOutFileName); - } catch (IOException e) { - closeSilently(is); - throw e; - } - is.close(); - } - - /** - * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this - * ExifInterface object will be added to a jpeg image written to this - * stream, removing prior exif tags. Other methods of this ExifInterface - * object should not be called until the returned OutputStream has been - * closed. - * - * @param outStream an OutputStream to wrap. - * @return an OutputStream that wraps the outStream parameter, and adds exif - * metadata. A jpeg image should be written to this stream. - */ - public OutputStream getExifWriterStream(OutputStream outStream) { - if (outStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - ExifOutputStream eos = new ExifOutputStream(outStream, this); - eos.setExifData(mData); - return eos; - } - - /** - * Returns an OutputStream object that writes to a file. Exif tags in this - * ExifInterface object will be added to a jpeg image written to this - * stream, removing prior exif tags. Other methods of this ExifInterface - * object should not be called until the returned OutputStream has been - * closed. - * - * @param exifOutFileName an String containing a filepath for a jpeg file. - * @return an OutputStream that writes to the exifOutFileName file, and adds - * exif metadata. A jpeg image should be written to this stream. - * @throws java.io.FileNotFoundException - */ - public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException { - if (exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream out = null; - try { - out = new FileOutputStream(exifOutFileName); - } catch (FileNotFoundException e) { - closeSilently(out); - throw e; - } - return getExifWriterStream(out); - } - - /** - * Attempts to do an in-place rewrite the exif metadata in a file for the - * given tags. If tags do not exist or do not have the same size as the - * existing exif tags, this method will fail. - * - * @param filename a String containing a filepath for a jpeg file with exif - * tags to rewrite. - * @param tags tags that will be written into the jpeg file over existing - * tags if possible. - * @return true if success, false if could not overwrite. If false, no - * changes are made to the file. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - */ - public boolean rewriteExif(String filename, Collection tags) - throws FileNotFoundException, IOException { - RandomAccessFile file = null; - InputStream is = null; - boolean ret; - try { - File temp = new File(filename); - is = new BufferedInputStream(new FileInputStream(temp)); - - // Parse beginning of APP1 in exif to find size of exif header. - ExifParser parser = null; - try { - parser = ExifParser.parse(is, this); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : ", e); - } - long exifSize = parser.getOffsetToExifEndFromSOF(); - - // Free up resources - is.close(); - is = null; - - // Open file for memory mapping. - file = new RandomAccessFile(temp, "rw"); - long fileLength = file.length(); - if (fileLength < exifSize) { - throw new IOException("Filesize changed during operation"); - } - - // Map only exif header into memory. - ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize); - - // Attempt to overwrite tag values without changing lengths (avoids - // file copy). - ret = rewriteExif(buf, tags); - } catch (IOException e) { - closeSilently(file); - throw e; - } finally { - closeSilently(is); - } - file.close(); - return ret; - } - - /** - * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for - * the given tags. If tags do not exist or do not have the same size as the - * existing exif tags, this method will fail. - * - * @param buf a ByteBuffer containing a jpeg file with existing exif tags to - * rewrite. - * @param tags tags that will be written into the jpeg ByteBuffer over - * existing tags if possible. - * @return true if success, false if could not overwrite. If false, no - * changes are made to the ByteBuffer. - * @throws java.io.IOException - */ - public boolean rewriteExif(ByteBuffer buf, Collection tags) throws IOException { - ExifModifier mod = null; - try { - mod = new ExifModifier(buf, this); - for (ExifTag t : tags) { - mod.modifyTag(t); - } - return mod.commit(); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : " + e); - } - } - - /** - * Attempts to do an in-place rewrite of the exif metadata. If this fails, - * fall back to overwriting file. This preserves tags that are not being - * rewritten. - * - * @param filename a String containing a filepath for a jpeg file. - * @param tags tags that will be written into the jpeg file over existing - * tags if possible. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - * @see #rewriteExif - */ - public void forceRewriteExif(String filename, Collection tags) - throws FileNotFoundException, - IOException { - // Attempt in-place write - if (!rewriteExif(filename, tags)) { - // Fall back to doing a copy - ExifData tempData = mData; - mData = new ExifData(DEFAULT_BYTE_ORDER); - FileInputStream is = null; - ByteArrayOutputStream bytes = null; - try { - is = new FileInputStream(filename); - bytes = new ByteArrayOutputStream(); - doExifStreamIO(is, bytes); - byte[] imageBytes = bytes.toByteArray(); - readExif(imageBytes); - setTags(tags); - writeExif(imageBytes, filename); - } catch (IOException e) { - closeSilently(is); - throw e; - } finally { - is.close(); - // Prevent clobbering of mData - mData = tempData; - } - } - } - - /** - * Attempts to do an in-place rewrite of the exif metadata using the tags in - * this ExifInterface object. If this fails, fall back to overwriting file. - * This preserves tags that are not being rewritten. - * - * @param filename a String containing a filepath for a jpeg file. - * @throws java.io.FileNotFoundException - * @throws java.io.IOException - * @see #rewriteExif - */ - public void forceRewriteExif(String filename) throws FileNotFoundException, IOException { - forceRewriteExif(filename, getAllTags()); - } - - /** - * Get the exif tags in this ExifInterface object or null if none exist. - * - * @return a List of {@link ExifTag}s. - */ - public List getAllTags() { - return mData.getAllTags(); - } - - /** - * Returns a list of ExifTags that share a TID (which can be obtained by - * calling {@link #getTrueTagKey} on a defined tag constant) or null if none - * exist. - * - * @param tagId a TID as defined in the exif standard (or with - * {@link #defineTag}). - * @return a List of {@link ExifTag}s. - */ - public List getTagsForTagId(short tagId) { - return mData.getAllTagsForTagId(tagId); - } - - /** - * Returns a list of ExifTags that share an IFD (which can be obtained by - * calling {@link #getTrueIFD} on a defined tag constant) or null if none - * exist. - * - * @param ifdId an IFD as defined in the exif standard (or with - * {@link #defineTag}). - * @return a List of {@link ExifTag}s. - */ - public List getTagsForIfdId(int ifdId) { - return mData.getAllTagsForIfd(ifdId); - } - - /** - * Gets an ExifTag for an IFD other than the tag's default. - * - * @see #getTag - */ - public ExifTag getTag(int tagId, int ifdId) { - if (!ExifTag.isValidIfd(ifdId)) { - return null; - } - return mData.getTag(getTrueTagKey(tagId), ifdId); - } - - /** - * Returns the ExifTag in that tag's default IFD for a defined tag constant - * or null if none exists. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return an {@link ExifTag} or null if none exists. - */ - public ExifTag getTag(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTag(tagId, ifdId); - } - - /** - * Gets a tag value for an IFD other than the tag's default. - * - * @see #getTagValue - */ - public Object getTagValue(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - return (t == null) ? null : t.getValue(); - } - - /** - * Returns the value of the ExifTag in that tag's default IFD for a defined - * tag constant or null if none exists or the value could not be cast into - * the return type. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the value of the ExifTag or null if none exists. - */ - public Object getTagValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagValue(tagId, ifdId); - } - - /* - * Getter methods that are similar to getTagValue. Null is returned if the - * tag value cannot be cast into the return type. - */ - - /** - * @see #getTagValue - */ - public String getTagStringValue(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsString(); - } - - /** - * @see #getTagValue - */ - public String getTagStringValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagStringValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Long getTagLongValue(int tagId, int ifdId) { - long[] l = getTagLongValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return new Long(l[0]); - } - - /** - * @see #getTagValue - */ - public Long getTagLongValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagLongValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Integer getTagIntValue(int tagId, int ifdId) { - int[] l = getTagIntValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return new Integer(l[0]); - } - - /** - * @see #getTagValue - */ - public Integer getTagIntValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagIntValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Byte getTagByteValue(int tagId, int ifdId) { - byte[] l = getTagByteValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return new Byte(l[0]); - } - - /** - * @see #getTagValue - */ - public Byte getTagByteValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagByteValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Rational getTagRationalValue(int tagId, int ifdId) { - Rational[] l = getTagRationalValues(tagId, ifdId); - if (l == null || l.length == 0) { - return null; - } - return new Rational(l[0]); - } - - /** - * @see #getTagValue - */ - public Rational getTagRationalValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagRationalValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public long[] getTagLongValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsLongs(); - } - - /** - * @see #getTagValue - */ - public long[] getTagLongValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagLongValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public int[] getTagIntValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsInts(); - } - - /** - * @see #getTagValue - */ - public int[] getTagIntValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagIntValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public byte[] getTagByteValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsBytes(); - } - - /** - * @see #getTagValue - */ - public byte[] getTagByteValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagByteValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Rational[] getTagRationalValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsRationals(); - } - - /** - * @see #getTagValue - */ - public Rational[] getTagRationalValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagRationalValues(tagId, ifdId); - } - - /** - * Checks whether a tag has a defined number of elements. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return true if the tag has a defined number of elements. - */ - public boolean isTagCountDefined(int tagId) { - int info = getTagInfo().get(tagId); - // No value in info can be zero, as all tags have a non-zero type - if (info == 0) { - return false; - } - return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED; - } - - /** - * Gets the defined number of elements for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the - * tag or the number of elements is not defined. - */ - public int getDefinedTagCount(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return ExifTag.SIZE_UNDEFINED; - } - return getComponentCountFromInfo(info); - } - - /** - * Gets the number of elements for an ExifTag in a given IFD. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD containing the ExifTag to check. - * @return the number of elements in the ExifTag, if the tag's size is - * undefined this will return the actual number of elements that is - * in the ExifTag's value. - */ - public int getActualTagCount(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return 0; - } - return t.getComponentCount(); - } - - /** - * Gets the default IFD for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the default IFD for a tag definition or {@link #IFD_NULL} if no - * definition exists. - */ - public int getDefinedTagDefaultIfd(int tagId) { - int info = getTagInfo().get(tagId); - if (info == DEFINITION_NULL) { - return IFD_NULL; - } - return getTrueIfd(tagId); - } - - /** - * Gets the defined type for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the type. - * @see ExifTag#getDataType() - */ - public short getDefinedTagType(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return -1; - } - return getTypeFromInfo(info); - } - - /** - * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD}, - * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT}, - * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD} - *

- * Note: defining tags with these TID's is disallowed. - * - * @param tag a tag's TID (can be obtained from a defined tag constant with - * {@link #getTrueTagKey}). - * @return true if the TID is that of an offset tag. - */ - protected static boolean isOffsetTag(short tag) { - return sOffsetTags.contains(tag); - } - - /** - * Creates a tag for a defined tag constant in a given IFD if that IFD is - * allowed for the tag. This method will fail anytime the appropriate - * {@link ExifTag#setValue} for this tag's datatype would fail. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD that the tag should be in. - * @param val the value of the tag to set. - * @return an ExifTag object or null if one could not be constructed. - * @see #buildTag - */ - public ExifTag buildTag(int tagId, int ifdId, Object val) { - int info = getTagInfo().get(tagId); - if (info == 0 || val == null) { - return null; - } - short type = getTypeFromInfo(info); - int definedCount = getComponentCountFromInfo(info); - boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); - if (!ExifInterface.isIfdAllowed(info, ifdId)) { - return null; - } - ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); - if (!t.setValue(val)) { - return null; - } - return t; - } - - /** - * Creates a tag for a defined tag constant in the tag's default IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param val the tag's value. - * @return an ExifTag object. - */ - public ExifTag buildTag(int tagId, Object val) { - int ifdId = getTrueIfd(tagId); - return buildTag(tagId, ifdId, val); - } - - protected ExifTag buildUninitializedTag(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return null; - } - short type = getTypeFromInfo(info); - int definedCount = getComponentCountFromInfo(info); - boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); - int ifdId = getTrueIfd(tagId); - ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); - return t; - } - - /** - * Sets the value of an ExifTag if it exists in the given IFD. The value - * must be the correct type and length for that ExifTag. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD that the ExifTag is in. - * @param val the value to set. - * @return true if success, false if the ExifTag doesn't exist or the value - * is the wrong type/length. - * @see #setTagValue - */ - public boolean setTagValue(int tagId, int ifdId, Object val) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return false; - } - return t.setValue(val); - } - - /** - * Sets the value of an ExifTag if it exists it's default IFD. The value - * must be the correct type and length for that ExifTag. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param val the value to set. - * @return true if success, false if the ExifTag doesn't exist or the value - * is the wrong type/length. - */ - public boolean setTagValue(int tagId, Object val) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return setTagValue(tagId, ifdId, val); - } - - /** - * Puts an ExifTag into this ExifInterface object's tags, removing a - * previous ExifTag with the same TID and IFD. The IFD it is put into will - * be the one the tag was created with in {@link #buildTag}. - * - * @param tag an ExifTag to put into this ExifInterface's tags. - * @return the previous ExifTag with the same TID and IFD or null if none - * exists. - */ - public ExifTag setTag(ExifTag tag) { - return mData.addTag(tag); - } - - /** - * Puts a collection of ExifTags into this ExifInterface objects's tags. Any - * previous ExifTags with the same TID and IFDs will be removed. - * - * @param tags a Collection of ExifTags. - * @see #setTag - */ - public void setTags(Collection tags) { - for (ExifTag t : tags) { - setTag(t); - } - } - - /** - * Removes the ExifTag for a tag constant from the given IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD of the ExifTag to remove. - */ - public void deleteTag(int tagId, int ifdId) { - mData.removeTag(getTrueTagKey(tagId), ifdId); - } - - /** - * Removes the ExifTag for a tag constant from that tag's default IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - */ - public void deleteTag(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - deleteTag(tagId, ifdId); - } - - /** - * Creates a new tag definition in this ExifInterface object for a given TID - * and default IFD. Creating a definition with the same TID and default IFD - * as a previous definition will override it. - * - * @param tagId the TID for the tag. - * @param defaultIfd the default IFD for the tag. - * @param tagType the type of the tag (see {@link ExifTag#getDataType()}). - * @param defaultComponentCount the number of elements of this tag's type in - * the tags value. - * @param allowedIfds the IFD's this tag is allowed to be put in. - * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or - * {@link #TAG_NULL} if the definition could not be made. - */ - public int setTagDefinition(short tagId, int defaultIfd, short tagType, - short defaultComponentCount, int[] allowedIfds) { - if (sBannedDefines.contains(tagId)) { - return TAG_NULL; - } - if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) { - int tagDef = defineTag(defaultIfd, tagId); - if (tagDef == TAG_NULL) { - return TAG_NULL; - } - int[] otherDefs = getTagDefinitionsForTagId(tagId); - SparseIntArray infos = getTagInfo(); - // Make sure defaultIfd is in allowedIfds - boolean defaultCheck = false; - for (int i : allowedIfds) { - if (defaultIfd == i) { - defaultCheck = true; - } - if (!ExifTag.isValidIfd(i)) { - return TAG_NULL; - } - } - if (!defaultCheck) { - return TAG_NULL; - } - - int ifdFlags = getFlagsFromAllowedIfds(allowedIfds); - // Make sure no identical tags can exist in allowedIfds - if (otherDefs != null) { - for (int def : otherDefs) { - int tagInfo = infos.get(def); - int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo); - if ((ifdFlags & allowedFlags) != 0) { - return TAG_NULL; - } - } - } - getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount); - return tagDef; - } - return TAG_NULL; - } - - protected int getTagDefinition(short tagId, int defaultIfd) { - return getTagInfo().get(defineTag(defaultIfd, tagId)); - } - - protected int[] getTagDefinitionsForTagId(short tagId) { - int[] ifds = IfdData.getIfds(); - int[] defs = new int[ifds.length]; - int counter = 0; - SparseIntArray infos = getTagInfo(); - for (int i : ifds) { - int def = defineTag(i, tagId); - if (infos.get(def) != DEFINITION_NULL) { - defs[counter++] = def; - } - } - if (counter == 0) { - return null; - } - - return Arrays.copyOfRange(defs, 0, counter); - } - - protected int getTagDefinitionForTag(ExifTag tag) { - short type = tag.getDataType(); - int count = tag.getComponentCount(); - int ifd = tag.getIfd(); - return getTagDefinitionForTag(tag.getTagId(), type, count, ifd); - } - - protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) { - int[] defs = getTagDefinitionsForTagId(tagId); - if (defs == null) { - return TAG_NULL; - } - SparseIntArray infos = getTagInfo(); - int ret = TAG_NULL; - for (int i : defs) { - int info = infos.get(i); - short defType = getTypeFromInfo(info); - int defCount = getComponentCountFromInfo(info); - int[] defIfds = getAllowedIfdsFromInfo(info); - boolean validIfd = false; - for (int j : defIfds) { - if (j == ifd) { - validIfd = true; - break; - } - } - if (validIfd && type == defType - && (count == defCount || defCount == ExifTag.SIZE_UNDEFINED)) { - ret = i; - break; - } - } - return ret; - } - - /** - * Removes a tag definition for given defined tag constant. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - */ - public void removeTagDefinition(int tagId) { - getTagInfo().delete(tagId); - } - - /** - * Resets tag definitions to the default ones. - */ - public void resetTagDefinitions() { - mTagInfo = null; - } - - /** - * Returns the thumbnail from IFD1 as a bitmap, or null if none exists. - * - * @return the thumbnail as a bitmap. - */ - public Bitmap getThumbnailBitmap() { - if (mData.hasCompressedThumbnail()) { - byte[] thumb = mData.getCompressedThumbnail(); - return BitmapFactory.decodeByteArray(thumb, 0, thumb.length); - } else if (mData.hasUncompressedStrip()) { - // TODO: implement uncompressed - } - return null; - } - - /** - * Returns the thumbnail from IFD1 as a byte array, or null if none exists. - * The bytes may either be an uncompressed strip as specified in the exif - * standard or a jpeg compressed image. - * - * @return the thumbnail as a byte array. - */ - public byte[] getThumbnailBytes() { - if (mData.hasCompressedThumbnail()) { - return mData.getCompressedThumbnail(); - } else if (mData.hasUncompressedStrip()) { - // TODO: implement this - } - return null; - } - - /** - * Returns the thumbnail if it is jpeg compressed, or null if none exists. - * - * @return the thumbnail as a byte array. - */ - public byte[] getThumbnail() { - return mData.getCompressedThumbnail(); - } - - /** - * Check if thumbnail is compressed. - * - * @return true if the thumbnail is compressed. - */ - public boolean isThumbnailCompressed() { - return mData.hasCompressedThumbnail(); - } - - /** - * Check if thumbnail exists. - * - * @return true if a compressed thumbnail exists. - */ - public boolean hasThumbnail() { - // TODO: add back in uncompressed strip - return mData.hasCompressedThumbnail(); - } - - // TODO: uncompressed thumbnail setters - - /** - * Sets the thumbnail to be a jpeg compressed image. Clears any prior - * thumbnail. - * - * @param thumb a byte array containing a jpeg compressed image. - * @return true if the thumbnail was set. - */ - public boolean setCompressedThumbnail(byte[] thumb) { - mData.clearThumbnailAndStrips(); - mData.setCompressedThumbnail(thumb); - return true; - } - - /** - * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior - * thumbnail. - * - * @param thumb a bitmap to compress to a jpeg thumbnail. - * @return true if the thumbnail was set. - */ - public boolean setCompressedThumbnail(Bitmap thumb) { - ByteArrayOutputStream thumbnail = new ByteArrayOutputStream(); - if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) { - return false; - } - return setCompressedThumbnail(thumbnail.toByteArray()); - } - - /** - * Clears the compressed thumbnail if it exists. - */ - public void removeCompressedThumbnail() { - mData.setCompressedThumbnail(null); - } - - // Convenience methods: - - /** - * Decodes the user comment tag into string as specified in the EXIF - * standard. Returns null if decoding failed. - */ - public String getUserComment() { - return mData.getUserComment(); - } - - /** - * Returns the Orientation ExifTag value for a given number of degrees. - * - * @param degrees the amount an image is rotated in degrees. - */ - public static short getOrientationValueForRotation(int degrees) { - degrees %= 360; - if (degrees < 0) { - degrees += 360; - } - if (degrees < 90) { - return Orientation.TOP_LEFT; // 0 degrees - } else if (degrees < 180) { - return Orientation.RIGHT_TOP; // 90 degrees cw - } else if (degrees < 270) { - return Orientation.BOTTOM_LEFT; // 180 degrees - } else { - return Orientation.RIGHT_BOTTOM; // 270 degrees cw - } - } - - /** - * Returns the rotation degrees corresponding to an ExifTag Orientation - * value. - * - * @param orientation the ExifTag Orientation value. - */ - public static int getRotationForOrientationValue(short orientation) { - switch (orientation) { - case Orientation.TOP_LEFT: - return 0; - case Orientation.RIGHT_TOP: - return 90; - case Orientation.BOTTOM_LEFT: - return 180; - case Orientation.RIGHT_BOTTOM: - return 270; - default: - return 0; - } - } - - public static OrientationParams getOrientationParams(int orientation) { - OrientationParams params = new OrientationParams(); - switch (orientation) { - case Orientation.TOP_RIGHT: // Flip horizontal - params.scaleX = -1; - break; - case Orientation.BOTTOM_RIGHT: // Flip vertical - params.scaleY = -1; - break; - case Orientation.BOTTOM_LEFT: // Rotate 180 - params.rotation = 180; - break; - case Orientation.RIGHT_BOTTOM: // Rotate 270 - params.rotation = 270; - params.invertDimensions = true; - break; - case Orientation.RIGHT_TOP: // Rotate 90 - params.rotation = 90; - params.invertDimensions = true; - break; - case Orientation.LEFT_TOP: // Transpose - params.rotation = 90; - params.scaleX = -1; - params.invertDimensions = true; - break; - case Orientation.LEFT_BOTTOM: // Transverse - params.rotation = 270; - params.scaleX = -1; - params.invertDimensions = true; - break; - } - return params; - } - - public static class OrientationParams { - public int rotation = 0; - public int scaleX = 1; - public int scaleY = 1; - public boolean invertDimensions = false; - } - - /** - * Gets the double representation of the GPS latitude or longitude - * coordinate. - * - * @param coordinate an array of 3 Rationals representing the degrees, - * minutes, and seconds of the GPS location as defined in the - * exif specification. - * @param reference a GPS reference reperesented by a String containing "N", - * "S", "E", or "W". - * @return the GPS coordinate represented as degrees + minutes/60 + - * seconds/3600 - */ - public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) { - try { - double degrees = coordinate[0].toDouble(); - double minutes = coordinate[1].toDouble(); - double seconds = coordinate[2].toDouble(); - double result = degrees + minutes / 60.0 + seconds / 3600.0; - if ((reference.equals("S") || reference.equals("W"))) { - return -result; - } - return result; - } catch (ArrayIndexOutOfBoundsException e) { - throw new IllegalArgumentException(); - } - } - - /** - * Gets the GPS latitude and longitude as a pair of doubles from this - * ExifInterface object's tags, or null if the necessary tags do not exist. - * - * @return an array of 2 doubles containing the latitude, and longitude - * respectively. - * @see #convertLatOrLongToDouble - */ - public double[] getLatLongAsDoubles() { - Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE); - String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF); - Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE); - String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF); - if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null - || latitude.length < 3 || longitude.length < 3) { - return null; - } - double[] latLon = new double[2]; - latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef); - latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef); - return latLon; - } - - private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; - private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"; - private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR); - private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR); - private final Calendar mGPSTimeStampCalendar = Calendar - .getInstance(TimeZone.getTimeZone("UTC")); - - /** - * Creates, formats, and sets the DateTimeStamp tag for one of: - * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED}, - * {@link #TAG_DATE_TIME_ORIGINAL}. - * - * @param tagId one of the DateTimeStamp tags. - * @param timestamp a timestamp to format. - * @param timezone a TimeZone object. - * @return true if success, false if the tag could not be set. - */ - public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) { - if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED - || tagId == TAG_DATE_TIME_ORIGINAL) { - mDateTimeStampFormat.setTimeZone(timezone); - ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp)); - if (t == null) { - return false; - } - setTag(t); - } else { - return false; - } - return true; - } - - /** - * Creates and sets all to the GPS tags for a give latitude and longitude. - * - * @param latitude a GPS latitude coordinate. - * @param longitude a GPS longitude coordinate. - * @return true if success, false if they could not be created or set. - */ - public boolean addGpsTags(double latitude, double longitude) { - ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude)); - ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude)); - ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF, - latitude >= 0 ? GpsLatitudeRef.NORTH - : GpsLatitudeRef.SOUTH); - ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF, - longitude >= 0 ? GpsLongitudeRef.EAST - : GpsLongitudeRef.WEST); - if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) { - return false; - } - setTag(latTag); - setTag(longTag); - setTag(latRefTag); - setTag(longRefTag); - return true; - } - - /** - * Creates and sets the GPS timestamp tag. - * - * @param timestamp a GPS timestamp. - * @return true if success, false if could not be created or set. - */ - public boolean addGpsDateTimeStampTag(long timestamp) { - ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp)); - if (t == null) { - return false; - } - setTag(t); - mGPSTimeStampCalendar.setTimeInMillis(timestamp); - t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] { - new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1) - }); - if (t == null) { - return false; - } - setTag(t); - return true; - } - - private static Rational[] toExifLatLong(double value) { - // convert to the format dd/1 mm/1 ssss/100 - value = Math.abs(value); - int degrees = (int) value; - value = (value - degrees) * 60; - int minutes = (int) value; - value = (value - minutes) * 6000; - int seconds = (int) value; - return new Rational[] { - new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100) - }; - } - - private void doExifStreamIO(InputStream is, OutputStream os) throws IOException { - byte[] buf = new byte[1024]; - int ret = is.read(buf, 0, 1024); - while (ret != -1) { - os.write(buf, 0, ret); - ret = is.read(buf, 0, 1024); - } - } - - protected static void closeSilently(Closeable c) { - if (c != null) { - try { - c.close(); - } catch (Throwable e) { - // ignored - } - } - } - - private SparseIntArray mTagInfo = null; - - protected SparseIntArray getTagInfo() { - if (mTagInfo == null) { - mTagInfo = new SparseIntArray(); - initTagInfo(); - } - return mTagInfo; - } - - private void initTagInfo() { - /** - * We put tag information in a 4-bytes integer. The first byte a bitmask - * representing the allowed IFDs of the tag, the second byte is the data - * type, and the last two byte are a short value indicating the default - * component count of this tag. - */ - // IFD0 tags - int[] ifdAllowedIfds = { - IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1 - }; - int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_MAKE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3); - mTagInfo.put(ExifInterface.TAG_COMPRESSION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 - | 1); - mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_X_RESOLUTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256); - mTagInfo.put(ExifInterface.TAG_WHITE_POINT, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2); - mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); - mTagInfo.put(ExifInterface.TAG_DATE_TIME, - ifdFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_MAKE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_MODEL, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SOFTWARE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ARTIST, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_COPYRIGHT, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_EXIF_IFD, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_IFD, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // IFD1 tags - int[] ifd1AllowedIfds = { - IfdId.TYPE_IFD_1 - }; - int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, - ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // Exif tags - int[] exifAllowedIfds = { - IfdId.TYPE_IFD_EXIF - }; - int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_EXIF_VERSION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_COLOR_SPACE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION, - exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, - exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_MAKER_NOTE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_USER_COMMENT, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE, - exifFlags | ExifTag.TYPE_ASCII << 16 | 13); - mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL, - exifFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED, - exifFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID, - exifFlags | ExifTag.TYPE_ASCII << 16 | 33); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_F_NUMBER, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_OECF, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_METERING_MODE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FLASH, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SENSING_METHOD, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FILE_SOURCE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SCENE_TYPE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); - mTagInfo.put(ExifInterface.TAG_CFA_PATTERN, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_CONTRAST, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SATURATION, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SHARPNESS, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags - | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // GPS tag - int[] gpsAllowedIfds = { - IfdId.TYPE_IFD_GPS - }; - int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID, - gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4); - mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE, - gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE, - gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF, - gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES, - gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_STATUS, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DOP, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_SPEED, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_TRACK, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM, - gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD, - gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION, - gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 11); - mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL, - gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11); - // Interoperability tag - int[] interopAllowedIfds = { - IfdId.TYPE_IFD_INTEROPERABILITY - }; - int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24; - mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16 - | ExifTag.SIZE_UNDEFINED); - } - - protected static int getAllowedIfdFlagsFromInfo(int info) { - return info >>> 24; - } - - protected static int[] getAllowedIfdsFromInfo(int info) { - int ifdFlags = getAllowedIfdFlagsFromInfo(info); - int[] ifds = IfdData.getIfds(); - ArrayList l = new ArrayList(); - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - int flag = (ifdFlags >> i) & 1; - if (flag == 1) { - l.add(ifds[i]); - } - } - if (l.size() <= 0) { - return null; - } - int[] ret = new int[l.size()]; - int j = 0; - for (int i : l) { - ret[j++] = i; - } - return ret; - } - - protected static boolean isIfdAllowed(int info, int ifd) { - int[] ifds = IfdData.getIfds(); - int ifdFlags = getAllowedIfdFlagsFromInfo(info); - for (int i = 0; i < ifds.length; i++) { - if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) { - return true; - } - } - return false; - } - - protected static int getFlagsFromAllowedIfds(int[] allowedIfds) { - if (allowedIfds == null || allowedIfds.length == 0) { - return 0; - } - int flags = 0; - int[] ifds = IfdData.getIfds(); - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - for (int j : allowedIfds) { - if (ifds[i] == j) { - flags |= 1 << i; - break; - } - } - } - return flags; - } - - protected static short getTypeFromInfo(int info) { - return (short) ((info >> 16) & 0x0ff); - } - - protected static int getComponentCountFromInfo(int info) { - return info & 0x0ffff; - } - -} diff --git a/src/com/android/messaging/util/exif/ExifInvalidFormatException.java b/src/com/android/messaging/util/exif/ExifInvalidFormatException.java deleted file mode 100644 index a38f8a3..0000000 --- a/src/com/android/messaging/util/exif/ExifInvalidFormatException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -public class ExifInvalidFormatException extends Exception { - public ExifInvalidFormatException(String meg) { - super(meg); - } -} diff --git a/src/com/android/messaging/util/exif/ExifModifier.java b/src/com/android/messaging/util/exif/ExifModifier.java deleted file mode 100644 index 274022c..0000000 --- a/src/com/android/messaging/util/exif/ExifModifier.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.util.Log; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; - -class ExifModifier { - public static final String TAG = LogUtil.BUGLE_TAG; - public static final boolean DEBUG = false; - private final ByteBuffer mByteBuffer; - private final ExifData mTagToModified; - private final List mTagOffsets = new ArrayList(); - private final ExifInterface mInterface; - private int mOffsetBase; - - private static class TagOffset { - final int mOffset; - final ExifTag mTag; - - TagOffset(ExifTag tag, int offset) { - mTag = tag; - mOffset = offset; - } - } - - protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException, - ExifInvalidFormatException { - mByteBuffer = byteBuffer; - mOffsetBase = byteBuffer.position(); - mInterface = iRef; - InputStream is = null; - try { - is = new ByteBufferInputStream(byteBuffer); - // Do not require any IFD; - ExifParser parser = ExifParser.parse(is, mInterface); - mTagToModified = new ExifData(parser.getByteOrder()); - mOffsetBase += parser.getTiffStartPosition(); - mByteBuffer.position(0); - } finally { - ExifInterface.closeSilently(is); - } - } - - protected ByteOrder getByteOrder() { - return mTagToModified.getByteOrder(); - } - - protected boolean commit() throws IOException, ExifInvalidFormatException { - InputStream is = null; - try { - is = new ByteBufferInputStream(mByteBuffer); - int flag = 0; - IfdData[] ifdDatas = new IfdData[] { - mTagToModified.getIfdData(IfdId.TYPE_IFD_0), - mTagToModified.getIfdData(IfdId.TYPE_IFD_1), - mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF), - mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY), - mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS) - }; - - if (ifdDatas[IfdId.TYPE_IFD_0] != null) { - flag |= ExifParser.OPTION_IFD_0; - } - if (ifdDatas[IfdId.TYPE_IFD_1] != null) { - flag |= ExifParser.OPTION_IFD_1; - } - if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) { - flag |= ExifParser.OPTION_IFD_EXIF; - } - if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) { - flag |= ExifParser.OPTION_IFD_GPS; - } - if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) { - flag |= ExifParser.OPTION_IFD_INTEROPERABILITY; - } - - ExifParser parser = ExifParser.parse(is, flag, mInterface); - int event = parser.next(); - IfdData currIfd = null; - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - currIfd = ifdDatas[parser.getCurrentIfd()]; - if (currIfd == null) { - parser.skipRemainingTagsInCurrentIfd(); - } - break; - case ExifParser.EVENT_NEW_TAG: - ExifTag oldTag = parser.getTag(); - ExifTag newTag = currIfd.getTag(oldTag.getTagId()); - if (newTag != null) { - if (newTag.getComponentCount() != oldTag.getComponentCount() - || newTag.getDataType() != oldTag.getDataType()) { - return false; - } else { - mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset())); - currIfd.removeTag(oldTag.getTagId()); - if (currIfd.getTagCount() == 0) { - parser.skipRemainingTagsInCurrentIfd(); - } - } - } - break; - } - event = parser.next(); - } - for (IfdData ifd : ifdDatas) { - if (ifd != null && ifd.getTagCount() > 0) { - return false; - } - } - modify(); - } finally { - ExifInterface.closeSilently(is); - } - return true; - } - - private void modify() { - mByteBuffer.order(getByteOrder()); - for (TagOffset tagOffset : mTagOffsets) { - writeTagValue(tagOffset.mTag, tagOffset.mOffset); - } - } - - private void writeTagValue(ExifTag tag, int offset) { - if (DEBUG) { - Log.v(TAG, "modifying tag to: \n" + tag.toString()); - Log.v(TAG, "at offset: " + offset); - } - mByteBuffer.position(offset + mOffsetBase); - switch (tag.getDataType()) { - case ExifTag.TYPE_ASCII: - byte buf[] = tag.getStringByte(); - if (buf.length == tag.getComponentCount()) { - buf[buf.length - 1] = 0; - mByteBuffer.put(buf); - } else { - mByteBuffer.put(buf); - mByteBuffer.put((byte) 0); - } - break; - case ExifTag.TYPE_LONG: - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - mByteBuffer.putInt((int) tag.getValueAt(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - Rational v = tag.getRational(i); - mByteBuffer.putInt((int) v.getNumerator()); - mByteBuffer.putInt((int) v.getDenominator()); - } - break; - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - mByteBuffer.put(buf); - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - mByteBuffer.putShort((short) tag.getValueAt(i)); - } - break; - } - } - - public void modifyTag(ExifTag tag) { - mTagToModified.addTag(tag); - } -} diff --git a/src/com/android/messaging/util/exif/ExifOutputStream.java b/src/com/android/messaging/util/exif/ExifOutputStream.java deleted file mode 100644 index 2016da4..0000000 --- a/src/com/android/messaging/util/exif/ExifOutputStream.java +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.util.Log; -import com.android.messaging.util.LogUtil; - -import java.io.BufferedOutputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; - -/** - * This class provides a way to replace the Exif header of a JPEG image. - *

- * Below is an example of writing EXIF data into a file - * - *

- * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
- *     OutputStream os = null;
- *     try {
- *         os = new FileOutputStream(path);
- *         ExifOutputStream eos = new ExifOutputStream(os);
- *         // Set the exif header
- *         eos.setExifData(exif);
- *         // Write the original jpeg out, the header will be add into the file.
- *         eos.write(jpeg);
- *     } catch (FileNotFoundException e) {
- *         e.printStackTrace();
- *     } catch (IOException e) {
- *         e.printStackTrace();
- *     } finally {
- *         if (os != null) {
- *             try {
- *                 os.close();
- *             } catch (IOException e) {
- *                 e.printStackTrace();
- *             }
- *         }
- *     }
- * }
- * 
- */ -class ExifOutputStream extends FilterOutputStream { - private static final String TAG = LogUtil.BUGLE_TAG; - private static final boolean DEBUG = false; - private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb - - private static final int STATE_SOI = 0; - private static final int STATE_FRAME_HEADER = 1; - private static final int STATE_JPEG_DATA = 2; - - private static final int EXIF_HEADER = 0x45786966; - private static final short TIFF_HEADER = 0x002A; - private static final short TIFF_BIG_ENDIAN = 0x4d4d; - private static final short TIFF_LITTLE_ENDIAN = 0x4949; - private static final short TAG_SIZE = 12; - private static final short TIFF_HEADER_SIZE = 8; - private static final int MAX_EXIF_SIZE = 65535; - - private ExifData mExifData; - private int mState = STATE_SOI; - private int mByteToSkip; - private int mByteToCopy; - private final byte[] mSingleByteArray = new byte[1]; - private final ByteBuffer mBuffer = ByteBuffer.allocate(4); - private final ExifInterface mInterface; - - protected ExifOutputStream(OutputStream ou, ExifInterface iRef) { - super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE)); - mInterface = iRef; - } - - /** - * Sets the ExifData to be written into the JPEG file. Should be called - * before writing image data. - */ - protected void setExifData(ExifData exifData) { - mExifData = exifData; - } - - /** - * Gets the Exif header to be written into the JPEF file. - */ - protected ExifData getExifData() { - return mExifData; - } - - private int requestByteToBuffer(int requestByteCount, byte[] buffer - , int offset, int length) { - int byteNeeded = requestByteCount - mBuffer.position(); - int byteToRead = length > byteNeeded ? byteNeeded : length; - mBuffer.put(buffer, offset, byteToRead); - return byteToRead; - } - - /** - * Writes the image out. The input data should be a valid JPEG format. After - * writing, it's Exif header will be replaced by the given header. - */ - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) - && length > 0) { - if (mByteToSkip > 0) { - int byteToProcess = length > mByteToSkip ? mByteToSkip : length; - length -= byteToProcess; - mByteToSkip -= byteToProcess; - offset += byteToProcess; - } - if (mByteToCopy > 0) { - int byteToProcess = length > mByteToCopy ? mByteToCopy : length; - out.write(buffer, offset, byteToProcess); - length -= byteToProcess; - mByteToCopy -= byteToProcess; - offset += byteToProcess; - } - if (length == 0) { - return; - } - switch (mState) { - case STATE_SOI: - int byteRead = requestByteToBuffer(2, buffer, offset, length); - offset += byteRead; - length -= byteRead; - if (mBuffer.position() < 2) { - return; - } - mBuffer.rewind(); - if (mBuffer.getShort() != JpegHeader.SOI) { - throw new IOException("Not a valid jpeg image, cannot write exif"); - } - out.write(mBuffer.array(), 0, 2); - mState = STATE_FRAME_HEADER; - mBuffer.rewind(); - writeExifData(); - break; - case STATE_FRAME_HEADER: - // We ignore the APP1 segment and copy all other segments - // until SOF tag. - byteRead = requestByteToBuffer(4, buffer, offset, length); - offset += byteRead; - length -= byteRead; - // Check if this image data doesn't contain SOF. - if (mBuffer.position() == 2) { - short tag = mBuffer.getShort(); - if (tag == JpegHeader.EOI) { - out.write(mBuffer.array(), 0, 2); - mBuffer.rewind(); - } - } - if (mBuffer.position() < 4) { - return; - } - mBuffer.rewind(); - short marker = mBuffer.getShort(); - if (marker == JpegHeader.APP1) { - mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2; - mState = STATE_JPEG_DATA; - } else if (!JpegHeader.isSofMarker(marker)) { - out.write(mBuffer.array(), 0, 4); - mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; - } else { - out.write(mBuffer.array(), 0, 4); - mState = STATE_JPEG_DATA; - } - mBuffer.rewind(); - } - } - if (length > 0) { - out.write(buffer, offset, length); - } - } - - /** - * Writes the one bytes out. The input data should be a valid JPEG format. - * After writing, it's Exif header will be replaced by the given header. - */ - @Override - public void write(int oneByte) throws IOException { - mSingleByteArray[0] = (byte) (0xff & oneByte); - write(mSingleByteArray); - } - - /** - * Equivalent to calling write(buffer, 0, buffer.length). - */ - @Override - public void write(byte[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - private void writeExifData() throws IOException { - if (mExifData == null) { - return; - } - if (DEBUG) { - Log.v(TAG, "Writing exif data..."); - } - ArrayList nullTags = stripNullValueTags(mExifData); - createRequiredIfdAndTag(); - int exifSize = calculateAllOffset(); - if (exifSize + 8 > MAX_EXIF_SIZE) { - throw new IOException("Exif header is too large (>64Kb)"); - } - OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out); - dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); - dataOutputStream.writeShort(JpegHeader.APP1); - dataOutputStream.writeShort((short) (exifSize + 8)); - dataOutputStream.writeInt(EXIF_HEADER); - dataOutputStream.writeShort((short) 0x0000); - if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) { - dataOutputStream.writeShort(TIFF_BIG_ENDIAN); - } else { - dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN); - } - dataOutputStream.setByteOrder(mExifData.getByteOrder()); - dataOutputStream.writeShort(TIFF_HEADER); - dataOutputStream.writeInt(8); - writeAllTags(dataOutputStream); - writeThumbnail(dataOutputStream); - for (ExifTag t : nullTags) { - mExifData.addTag(t); - } - } - - private ArrayList stripNullValueTags(ExifData data) { - ArrayList nullTags = new ArrayList(); - if (data.getAllTags() == null) { - return nullTags; - } - for (ExifTag t : data.getAllTags()) { - if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) { - data.removeTag(t.getTagId(), t.getIfd()); - nullTags.add(t); - } - } - return nullTags; - } - - private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException { - if (mExifData.hasCompressedThumbnail()) { - dataOutputStream.write(mExifData.getCompressedThumbnail()); - } else if (mExifData.hasUncompressedStrip()) { - for (int i = 0; i < mExifData.getStripCount(); i++) { - dataOutputStream.write(mExifData.getStrip(i)); - } - } - } - - private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException { - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream); - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream); - IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interoperabilityIfd != null) { - writeIfd(interoperabilityIfd, dataOutputStream); - } - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - writeIfd(gpsIfd, dataOutputStream); - } - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - if (ifd1 != null) { - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream); - } - } - - private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream) - throws IOException { - ExifTag[] tags = ifd.getAllTags(); - dataOutputStream.writeShort((short) tags.length); - for (ExifTag tag : tags) { - dataOutputStream.writeShort(tag.getTagId()); - dataOutputStream.writeShort(tag.getDataType()); - dataOutputStream.writeInt(tag.getComponentCount()); - if (DEBUG) { - Log.v(TAG, "\n" + tag.toString()); - } - if (tag.getDataSize() > 4) { - dataOutputStream.writeInt(tag.getOffset()); - } else { - ExifOutputStream.writeTagValue(tag, dataOutputStream); - for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) { - dataOutputStream.write(0); - } - } - } - dataOutputStream.writeInt(ifd.getOffsetToNextIfd()); - for (ExifTag tag : tags) { - if (tag.getDataSize() > 4) { - ExifOutputStream.writeTagValue(tag, dataOutputStream); - } - } - } - - private int calculateOffsetOfIfd(IfdData ifd, int offset) { - offset += 2 + ifd.getTagCount() * TAG_SIZE + 4; - ExifTag[] tags = ifd.getAllTags(); - for (ExifTag tag : tags) { - if (tag.getDataSize() > 4) { - tag.setOffset(offset); - offset += tag.getDataSize(); - } - } - return offset; - } - - private void createRequiredIfdAndTag() throws IOException { - // IFD0 is required for all file - IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); - if (ifd0 == null) { - ifd0 = new IfdData(IfdId.TYPE_IFD_0); - mExifData.addIfdData(ifd0); - } - ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD); - if (exifOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_EXIF_IFD); - } - ifd0.setTag(exifOffsetTag); - - // Exif IFD is required for all files. - IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); - if (exifIfd == null) { - exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF); - mExifData.addIfdData(exifIfd); - } - - // GPS IFD - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD); - if (gpsOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_GPS_IFD); - } - ifd0.setTag(gpsOffsetTag); - } - - // Interoperability IFD - IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interIfd != null) { - ExifTag interOffsetTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD); - if (interOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_INTEROPERABILITY_IFD); - } - exifIfd.setTag(interOffsetTag); - } - - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - - // thumbnail - if (mExifData.hasCompressedThumbnail()) { - - if (ifd1 == null) { - ifd1 = new IfdData(IfdId.TYPE_IFD_1); - mExifData.addIfdData(ifd1); - } - - ExifTag offsetTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - if (offsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - } - - ifd1.setTag(offsetTag); - ExifTag lengthTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - if (lengthTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - } - - lengthTag.setValue(mExifData.getCompressedThumbnail().length); - ifd1.setTag(lengthTag); - - // Get rid of tags for uncompressed if they exist. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); - } else if (mExifData.hasUncompressedStrip()) { - if (ifd1 == null) { - ifd1 = new IfdData(IfdId.TYPE_IFD_1); - mExifData.addIfdData(ifd1); - } - int stripCount = mExifData.getStripCount(); - ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS); - if (offsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_STRIP_OFFSETS); - } - ExifTag lengthTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS); - if (lengthTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_STRIP_BYTE_COUNTS); - } - long[] lengths = new long[stripCount]; - for (int i = 0; i < mExifData.getStripCount(); i++) { - lengths[i] = mExifData.getStrip(i).length; - } - lengthTag.setValue(lengths); - ifd1.setTag(offsetTag); - ifd1.setTag(lengthTag); - // Get rid of tags for compressed if they exist. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); - ifd1.removeTag(ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - } else if (ifd1 != null) { - // Get rid of offset and length tags if there is no thumbnail. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); - ifd1.removeTag(ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - } - } - - private int calculateAllOffset() { - int offset = TIFF_HEADER_SIZE; - IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); - offset = calculateOffsetOfIfd(ifd0, offset); - ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset); - - IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); - offset = calculateOffsetOfIfd(exifIfd, offset); - - IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interIfd != null) { - exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)) - .setValue(offset); - offset = calculateOffsetOfIfd(interIfd, offset); - } - - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset); - offset = calculateOffsetOfIfd(gpsIfd, offset); - } - - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - if (ifd1 != null) { - ifd0.setOffsetToNextIfd(offset); - offset = calculateOffsetOfIfd(ifd1, offset); - } - - // thumbnail - if (mExifData.hasCompressedThumbnail()) { - ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) - .setValue(offset); - offset += mExifData.getCompressedThumbnail().length; - } else if (mExifData.hasUncompressedStrip()) { - int stripCount = mExifData.getStripCount(); - long[] offsets = new long[stripCount]; - for (int i = 0; i < mExifData.getStripCount(); i++) { - offsets[i] = offset; - offset += mExifData.getStrip(i).length; - } - ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue( - offsets); - } - return offset; - } - - static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream) - throws IOException { - switch (tag.getDataType()) { - case ExifTag.TYPE_ASCII: - byte buf[] = tag.getStringByte(); - if (buf.length == tag.getComponentCount()) { - buf[buf.length - 1] = 0; - dataOutputStream.write(buf); - } else { - dataOutputStream.write(buf); - dataOutputStream.write(0); - } - break; - case ExifTag.TYPE_LONG: - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt((int) tag.getValueAt(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeRational(tag.getRational(i)); - } - break; - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - dataOutputStream.write(buf); - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeShort((short) tag.getValueAt(i)); - } - break; - } - } -} diff --git a/src/com/android/messaging/util/exif/ExifParser.java b/src/com/android/messaging/util/exif/ExifParser.java deleted file mode 100644 index 4b6cf68..0000000 --- a/src/com/android/messaging/util/exif/ExifParser.java +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.util.Log; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteOrder; -import java.nio.charset.Charset; -import java.util.Map.Entry; -import java.util.TreeMap; - -/** - * This class provides a low-level EXIF parsing API. Given a JPEG format - * InputStream, the caller can request which IFD's to read via - * {@link #parse(java.io.InputStream, int)} with given options. - *

- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the - * parser. - * - *

- * void parse() {
- *     ExifParser parser = ExifParser.parse(mImageInputStream,
- *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
- *     int event = parser.next();
- *     while (event != ExifParser.EVENT_END) {
- *         switch (event) {
- *             case ExifParser.EVENT_START_OF_IFD:
- *                 break;
- *             case ExifParser.EVENT_NEW_TAG:
- *                 ExifTag tag = parser.getTag();
- *                 if (!tag.hasValue()) {
- *                     parser.registerForTagValue(tag);
- *                 } else {
- *                     processTag(tag);
- *                 }
- *                 break;
- *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
- *                 tag = parser.getTag();
- *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
- *                     processTag(tag);
- *                 }
- *                 break;
- *         }
- *         event = parser.next();
- *     }
- * }
- *
- * void processTag(ExifTag tag) {
- *     // process the tag as you like.
- * }
- * 
- */ -public class ExifParser { - private static final boolean LOGV = false; - private static final String TAG = LogUtil.BUGLE_TAG; - /** - * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to - * know which IFD we are in. - */ - public static final int EVENT_START_OF_IFD = 0; - /** - * When the parser reaches a new tag. Call {@link #getTag()}to get the - * corresponding tag. - */ - public static final int EVENT_NEW_TAG = 1; - /** - * When the parser reaches the value area of tag that is registered by - * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()} - * to get the corresponding tag. - */ - public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2; - - /** - * When the parser reaches the compressed image area. - */ - public static final int EVENT_COMPRESSED_IMAGE = 3; - /** - * When the parser reaches the uncompressed image strip. Call - * {@link #getStripIndex()} to get the index of the strip. - * - * @see #getStripIndex() - * @see #getStripCount() - */ - public static final int EVENT_UNCOMPRESSED_STRIP = 4; - /** - * When there is nothing more to parse. - */ - public static final int EVENT_END = 5; - - /** - * Option bit to request to parse IFD0. - */ - public static final int OPTION_IFD_0 = 1 << 0; - /** - * Option bit to request to parse IFD1. - */ - public static final int OPTION_IFD_1 = 1 << 1; - /** - * Option bit to request to parse Exif-IFD. - */ - public static final int OPTION_IFD_EXIF = 1 << 2; - /** - * Option bit to request to parse GPS-IFD. - */ - public static final int OPTION_IFD_GPS = 1 << 3; - /** - * Option bit to request to parse Interoperability-IFD. - */ - public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4; - /** - * Option bit to request to parse thumbnail. - */ - public static final int OPTION_THUMBNAIL = 1 << 5; - - protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif" - protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1 - - // TIFF header - protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II" - protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" - protected static final short TIFF_HEADER_TAIL = 0x002A; - - protected static final int TAG_SIZE = 12; - protected static final int OFFSET_SIZE = 2; - - private static final Charset US_ASCII = Charset.forName("US-ASCII"); - - protected static final int DEFAULT_IFD0_OFFSET = 8; - - private final CountedDataInputStream mTiffStream; - private final int mOptions; - private int mIfdStartOffset = 0; - private int mNumOfTagInIfd = 0; - private int mIfdType; - private ExifTag mTag; - private ImageEvent mImageEvent; - private int mStripCount; - private ExifTag mStripSizeTag; - private ExifTag mJpegSizeTag; - private boolean mNeedToParseOffsetsInCurrentIfd; - private boolean mContainExifData = false; - private int mApp1End; - private int mOffsetToApp1EndFromSOF = 0; - private byte[] mDataAboveIfd0; - private int mIfd0Position; - private int mTiffStartPosition; - private final ExifInterface mInterface; - - private static final short TAG_EXIF_IFD = ExifInterface - .getTrueTagKey(ExifInterface.TAG_EXIF_IFD); - private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD); - private static final short TAG_INTEROPERABILITY_IFD = ExifInterface - .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD); - private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - private static final short TAG_STRIP_OFFSETS = ExifInterface - .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS); - private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface - .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS); - - private final TreeMap mCorrespondingEvent = new TreeMap(); - - private boolean isIfdRequested(int ifdType) { - switch (ifdType) { - case IfdId.TYPE_IFD_0: - return (mOptions & OPTION_IFD_0) != 0; - case IfdId.TYPE_IFD_1: - return (mOptions & OPTION_IFD_1) != 0; - case IfdId.TYPE_IFD_EXIF: - return (mOptions & OPTION_IFD_EXIF) != 0; - case IfdId.TYPE_IFD_GPS: - return (mOptions & OPTION_IFD_GPS) != 0; - case IfdId.TYPE_IFD_INTEROPERABILITY: - return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0; - } - return false; - } - - private boolean isThumbnailRequested() { - return (mOptions & OPTION_THUMBNAIL) != 0; - } - - private ExifParser(InputStream inputStream, int options, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - if (inputStream == null) { - throw new IOException("Null argument inputStream to ExifParser"); - } - if (LOGV) { - Log.v(TAG, "Reading exif..."); - } - mInterface = iRef; - mContainExifData = seekTiffData(inputStream); - mTiffStream = new CountedDataInputStream(inputStream); - mOptions = options; - if (!mContainExifData) { - return; - } - - parseTiffHeader(); - long offset = mTiffStream.readUnsignedInt(); - if (offset > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException("Invalid offset " + offset); - } - mIfd0Position = (int) offset; - mIfdType = IfdId.TYPE_IFD_0; - if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { - registerIfd(IfdId.TYPE_IFD_0, offset); - if (offset != DEFAULT_IFD0_OFFSET) { - mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; - read(mDataAboveIfd0); - } - } - } - - /** - * Parses the the given InputStream with the given options - * - * @exception java.io.IOException - * @exception ExifInvalidFormatException - */ - protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, options, iRef); - } - - /** - * Parses the the given InputStream with default options; that is, every IFD - * and thumbnaill will be parsed. - * - * @exception java.io.IOException - * @exception ExifInvalidFormatException - * @see #parse(java.io.InputStream, int) - */ - protected static ExifParser parse(InputStream inputStream, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1 - | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY - | OPTION_THUMBNAIL, iRef); - } - - /** - * Moves the parser forward and returns the next parsing event - * - * @exception java.io.IOException - * @exception ExifInvalidFormatException - * @see #EVENT_START_OF_IFD - * @see #EVENT_NEW_TAG - * @see #EVENT_VALUE_OF_REGISTERED_TAG - * @see #EVENT_COMPRESSED_IMAGE - * @see #EVENT_UNCOMPRESSED_STRIP - * @see #EVENT_END - */ - protected int next() throws IOException, ExifInvalidFormatException { - if (!mContainExifData) { - return EVENT_END; - } - int offset = mTiffStream.getReadByteCount(); - int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; - if (offset < endOfTags) { - mTag = readTag(); - if (mTag == null) { - return next(); - } - if (mNeedToParseOffsetsInCurrentIfd) { - checkOffsetOrImageTag(mTag); - } - return EVENT_NEW_TAG; - } else if (offset == endOfTags) { - // There is a link to ifd1 at the end of ifd0 - if (mIfdType == IfdId.TYPE_IFD_0) { - long ifdOffset = readUnsignedLong(); - if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { - if (ifdOffset != 0) { - registerIfd(IfdId.TYPE_IFD_1, ifdOffset); - } - } - } else { - int offsetSize = 4; - // Some camera models use invalid length of the offset - if (mCorrespondingEvent.size() > 0) { - offsetSize = mCorrespondingEvent.firstEntry().getKey() - - mTiffStream.getReadByteCount(); - } - if (offsetSize < 4) { - Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize); - } else { - long ifdOffset = readUnsignedLong(); - if (ifdOffset != 0) { - Log.w(TAG, "Invalid link to next IFD: " + ifdOffset); - } - } - } - } - while (mCorrespondingEvent.size() != 0) { - Entry entry = mCorrespondingEvent.pollFirstEntry(); - Object event = entry.getValue(); - try { - skipTo(entry.getKey()); - } catch (IOException e) { - Log.w(TAG, "Failed to skip to data at: " + entry.getKey() + - " for " + event.getClass().getName() + ", the file may be broken."); - continue; - } - if (event instanceof IfdEvent) { - mIfdType = ((IfdEvent) event).ifd; - mNumOfTagInIfd = mTiffStream.readUnsignedShort(); - mIfdStartOffset = entry.getKey(); - - if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) { - Log.w(TAG, "Invalid size of IFD " + mIfdType); - return EVENT_END; - } - - mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd(); - if (((IfdEvent) event).isRequested) { - return EVENT_START_OF_IFD; - } else { - skipRemainingTagsInCurrentIfd(); - } - } else if (event instanceof ImageEvent) { - mImageEvent = (ImageEvent) event; - return mImageEvent.type; - } else { - ExifTagEvent tagEvent = (ExifTagEvent) event; - mTag = tagEvent.tag; - if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) { - readFullTagValue(mTag); - checkOffsetOrImageTag(mTag); - } - if (tagEvent.isRequested) { - return EVENT_VALUE_OF_REGISTERED_TAG; - } - } - } - return EVENT_END; - } - - /** - * Skips the tags area of current IFD, if the parser is not in the tag area, - * nothing will happen. - * - * @throws java.io.IOException - * @throws ExifInvalidFormatException - */ - protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException { - int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; - int offset = mTiffStream.getReadByteCount(); - if (offset > endOfTags) { - return; - } - if (mNeedToParseOffsetsInCurrentIfd) { - while (offset < endOfTags) { - mTag = readTag(); - offset += TAG_SIZE; - if (mTag == null) { - continue; - } - checkOffsetOrImageTag(mTag); - } - } else { - skipTo(endOfTags); - } - long ifdOffset = readUnsignedLong(); - // For ifd0, there is a link to ifd1 in the end of all tags - if (mIfdType == IfdId.TYPE_IFD_0 - && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) { - if (ifdOffset > 0) { - registerIfd(IfdId.TYPE_IFD_1, ifdOffset); - } - } - } - - private boolean needToParseOffsetsInCurrentIfd() { - switch (mIfdType) { - case IfdId.TYPE_IFD_0: - return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) - || isIfdRequested(IfdId.TYPE_IFD_1); - case IfdId.TYPE_IFD_1: - return isThumbnailRequested(); - case IfdId.TYPE_IFD_EXIF: - // The offset to interoperability IFD is located in Exif IFD - return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY); - default: - return false; - } - } - - /** - * If {@link #next()} return {@link #EVENT_NEW_TAG} or - * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the - * corresponding tag. - *

- * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size - * of the value is greater than 4 bytes. One should call - * {@link ExifTag#hasValue()} to check if the tag contains value. If there - * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser - * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area - * pointed by the offset. - *

- * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the - * tag will have already been read except for tags of undefined type. For - * tags of undefined type, call one of the read methods to get the value. - * - * @see #registerForTagValue(ExifTag) - * @see #read(byte[]) - * @see #read(byte[], int, int) - * @see #readLong() - * @see #readRational() - * @see #readString(int) - * @see #readString(int, java.nio.charset.Charset) - */ - protected ExifTag getTag() { - return mTag; - } - - /** - * Gets number of tags in the current IFD area. - */ - protected int getTagCountInCurrentIfd() { - return mNumOfTagInIfd; - } - - /** - * Gets the ID of current IFD. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - * @see IfdId#TYPE_IFD_EXIF - */ - protected int getCurrentIfd() { - return mIfdType; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the index of this strip. - * - * @see #getStripCount() - */ - protected int getStripIndex() { - return mImageEvent.stripIndex; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the number of strip data. - * - * @see #getStripIndex() - */ - protected int getStripCount() { - return mStripCount; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the strip size. - */ - protected int getStripSize() { - if (mStripSizeTag == null) { - return 0; - } - return (int) mStripSizeTag.getValueAt(0); - } - - /** - * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get - * the image data size. - */ - protected int getCompressedImageSize() { - if (mJpegSizeTag == null) { - return 0; - } - return (int) mJpegSizeTag.getValueAt(0); - } - - private void skipTo(int offset) throws IOException { - mTiffStream.skipTo(offset); - while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) { - mCorrespondingEvent.pollFirstEntry(); - } - } - - /** - * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may - * not contain the value if the size of the value is greater than 4 bytes. - * When the value is not available here, call this method so that the parser - * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area - * where the value is located. - * - * @see #EVENT_VALUE_OF_REGISTERED_TAG - */ - protected void registerForTagValue(ExifTag tag) { - if (tag.getOffset() >= mTiffStream.getReadByteCount()) { - mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true)); - } - } - - private void registerIfd(int ifdType, long offset) { - // Cast unsigned int to int since the offset is always smaller - // than the size of APP1 (65536) - mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType))); - } - - private void registerCompressedImage(long offset) { - mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE)); - } - - private void registerUncompressedStrip(int stripIndex, long offset) { - mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP - , stripIndex)); - } - - private ExifTag readTag() throws IOException, ExifInvalidFormatException { - short tagId = mTiffStream.readShort(); - short dataFormat = mTiffStream.readShort(); - long numOfComp = mTiffStream.readUnsignedInt(); - if (numOfComp > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException( - "Number of component is larger then Integer.MAX_VALUE"); - } - // Some invalid image file contains invalid data type. Ignore those tags - if (!ExifTag.isValidType(dataFormat)) { - Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat)); - mTiffStream.skip(4); - return null; - } - // TODO: handle numOfComp overflow - ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType, - ((int) numOfComp) != ExifTag.SIZE_UNDEFINED); - int dataSize = tag.getDataSize(); - if (dataSize > 4) { - long offset = mTiffStream.readUnsignedInt(); - if (offset > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException( - "offset is larger then Integer.MAX_VALUE"); - } - // Some invalid images put some undefined data before IFD0. - // Read the data here. - if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) { - byte[] buf = new byte[(int) numOfComp]; - System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, - buf, 0, (int) numOfComp); - tag.setValue(buf); - } else { - tag.setOffset((int) offset); - } - } else { - boolean defCount = tag.hasDefinedCount(); - // Set defined count to 0 so we can add \0 to non-terminated strings - tag.setHasDefinedCount(false); - // Read value - readFullTagValue(tag); - tag.setHasDefinedCount(defCount); - mTiffStream.skip(4 - dataSize); - // Set the offset to the position of value. - tag.setOffset(mTiffStream.getReadByteCount() - 4); - } - return tag; - } - - /** - * Check the tag, if the tag is one of the offset tag that points to the IFD - * or image the caller is interested in, register the IFD or image. - */ - private void checkOffsetOrImageTag(ExifTag tag) { - // Some invalid formattd image contains tag with 0 size. - if (tag.getComponentCount() == 0) { - return; - } - short tid = tag.getTagId(); - int ifd = tag.getIfd(); - if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_EXIF) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); - } - } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); - } - } else if (tid == TAG_INTEROPERABILITY_IFD - && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); - } - } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT - && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) { - if (isThumbnailRequested()) { - registerCompressedImage(tag.getValueAt(0)); - } - } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH - && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) { - if (isThumbnailRequested()) { - mJpegSizeTag = tag; - } - } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) { - if (isThumbnailRequested()) { - if (tag.hasValue()) { - for (int i = 0; i < tag.getComponentCount(); i++) { - if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - registerUncompressedStrip(i, tag.getValueAt(i)); - } else { - registerUncompressedStrip(i, tag.getValueAt(i)); - } - } - } else { - mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); - } - } - } else if (tid == TAG_STRIP_BYTE_COUNTS - && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS) - && isThumbnailRequested() && tag.hasValue()) { - mStripSizeTag = tag; - } - } - - private boolean checkAllowed(int ifd, int tagId) { - int info = mInterface.getTagInfo().get(tagId); - if (info == ExifInterface.DEFINITION_NULL) { - return false; - } - return ExifInterface.isIfdAllowed(info, ifd); - } - - protected void readFullTagValue(ExifTag tag) throws IOException { - // Some invalid images contains tags with wrong size, check it here - short type = tag.getDataType(); - if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || - type == ExifTag.TYPE_UNSIGNED_BYTE) { - int size = tag.getComponentCount(); - if (mCorrespondingEvent.size() > 0) { - if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount() - + size) { - Object event = mCorrespondingEvent.firstEntry().getValue(); - if (event instanceof ImageEvent) { - // Tag value overlaps thumbnail, ignore thumbnail. - Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString()); - Entry entry = mCorrespondingEvent.pollFirstEntry(); - Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey()); - } else { - // Tag value overlaps another tag, shorten count - if (event instanceof IfdEvent) { - Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd - + " overlaps value for tag: \n" + tag.toString()); - } else if (event instanceof ExifTagEvent) { - Log.w(TAG, "Tag value for tag: \n" - + ((ExifTagEvent) event).tag.toString() - + " overlaps value for tag: \n" + tag.toString()); - } - size = mCorrespondingEvent.firstEntry().getKey() - - mTiffStream.getReadByteCount(); - Log.w(TAG, "Invalid size of tag: \n" + tag.toString() - + " setting count to: " + size); - tag.forceSetComponentCount(size); - } - } - } - } - switch (tag.getDataType()) { - case ExifTag.TYPE_UNSIGNED_BYTE: - case ExifTag.TYPE_UNDEFINED: { - byte buf[] = new byte[tag.getComponentCount()]; - read(buf); - tag.setValue(buf); - } - break; - case ExifTag.TYPE_ASCII: - tag.setValue(readString(tag.getComponentCount())); - break; - case ExifTag.TYPE_UNSIGNED_LONG: { - long value[] = new long[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedLong(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_UNSIGNED_RATIONAL: { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedRational(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_UNSIGNED_SHORT: { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedShort(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_LONG: { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readLong(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_RATIONAL: { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readRational(); - } - tag.setValue(value); - } - break; - } - if (LOGV) { - Log.v(TAG, "\n" + tag.toString()); - } - } - - private void parseTiffHeader() throws IOException, - ExifInvalidFormatException { - short byteOrder = mTiffStream.readShort(); - if (LITTLE_ENDIAN_TAG == byteOrder) { - mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); - } else if (BIG_ENDIAN_TAG == byteOrder) { - mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN); - } else { - throw new ExifInvalidFormatException("Invalid TIFF header"); - } - - if (mTiffStream.readShort() != TIFF_HEADER_TAIL) { - throw new ExifInvalidFormatException("Invalid TIFF header"); - } - } - - private boolean seekTiffData(InputStream inputStream) throws IOException, - ExifInvalidFormatException { - CountedDataInputStream dataStream = new CountedDataInputStream(inputStream); - if (dataStream.readShort() != JpegHeader.SOI) { - throw new ExifInvalidFormatException("Invalid JPEG format"); - } - - short marker = dataStream.readShort(); - while (marker != JpegHeader.EOI - && !JpegHeader.isSofMarker(marker)) { - int length = dataStream.readUnsignedShort(); - // Some invalid formatted image contains multiple APP1, - // try to find the one with Exif data. - if (marker == JpegHeader.APP1) { - int header = 0; - short headerTail = 0; - if (length >= 8) { - header = dataStream.readInt(); - headerTail = dataStream.readShort(); - length -= 6; - if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { - mTiffStartPosition = dataStream.getReadByteCount(); - mApp1End = length; - mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End; - return true; - } - } - } - if (length < 2 || (length - 2) != dataStream.skip(length - 2)) { - Log.w(TAG, "Invalid JPEG format."); - return false; - } - marker = dataStream.readShort(); - } - return false; - } - - protected int getOffsetToExifEndFromSOF() { - return mOffsetToApp1EndFromSOF; - } - - protected int getTiffStartPosition() { - return mTiffStartPosition; - } - - /** - * Reads bytes from the InputStream. - */ - protected int read(byte[] buffer, int offset, int length) throws IOException { - return mTiffStream.read(buffer, offset, length); - } - - /** - * Equivalent to read(buffer, 0, buffer.length). - */ - protected int read(byte[] buffer) throws IOException { - return mTiffStream.read(buffer); - } - - /** - * Reads a String from the InputStream with US-ASCII charset. The parser - * will read n bytes and convert it to ascii string. This is used for - * reading values of type {@link ExifTag#TYPE_ASCII}. - */ - protected String readString(int n) throws IOException { - return readString(n, US_ASCII); - } - - /** - * Reads a String from the InputStream with the given charset. The parser - * will read n bytes and convert it to string. This is used for reading - * values of type {@link ExifTag#TYPE_ASCII}. - */ - protected String readString(int n, Charset charset) throws IOException { - if (n > 0) { - return mTiffStream.readString(n, charset); - } else { - return ""; - } - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the - * InputStream. - */ - protected int readUnsignedShort() throws IOException { - return mTiffStream.readShort() & 0xffff; - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the - * InputStream. - */ - protected long readUnsignedLong() throws IOException { - return readLong() & 0xffffffffL; - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the - * InputStream. - */ - protected Rational readUnsignedRational() throws IOException { - long nomi = readUnsignedLong(); - long denomi = readUnsignedLong(); - return new Rational(nomi, denomi); - } - - /** - * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream. - */ - protected int readLong() throws IOException { - return mTiffStream.readInt(); - } - - /** - * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream. - */ - protected Rational readRational() throws IOException { - int nomi = readLong(); - int denomi = readLong(); - return new Rational(nomi, denomi); - } - - private static class ImageEvent { - int stripIndex; - int type; - - ImageEvent(int type) { - this.stripIndex = 0; - this.type = type; - } - - ImageEvent(int type, int stripIndex) { - this.type = type; - this.stripIndex = stripIndex; - } - } - - private static class IfdEvent { - int ifd; - boolean isRequested; - - IfdEvent(int ifd, boolean isInterestedIfd) { - this.ifd = ifd; - this.isRequested = isInterestedIfd; - } - } - - private static class ExifTagEvent { - ExifTag tag; - boolean isRequested; - - ExifTagEvent(ExifTag tag, boolean isRequireByUser) { - this.tag = tag; - this.isRequested = isRequireByUser; - } - } - - /** - * Gets the byte order of the current InputStream. - */ - protected ByteOrder getByteOrder() { - return mTiffStream.getByteOrder(); - } -} diff --git a/src/com/android/messaging/util/exif/ExifReader.java b/src/com/android/messaging/util/exif/ExifReader.java deleted file mode 100644 index eece48a..0000000 --- a/src/com/android/messaging/util/exif/ExifReader.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import android.util.Log; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; -import java.io.InputStream; - -/** - * This class reads the EXIF header of a JPEG file and stores it in - * {@link ExifData}. - */ -class ExifReader { - private static final String TAG = LogUtil.BUGLE_TAG; - - private final ExifInterface mInterface; - - ExifReader(ExifInterface iRef) { - mInterface = iRef; - } - - /** - * Parses the inputStream and and returns the EXIF data in an - * {@link ExifData}. - * - * @throws ExifInvalidFormatException - * @throws java.io.IOException - */ - protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException, - IOException { - ExifParser parser = ExifParser.parse(inputStream, mInterface); - ExifData exifData = new ExifData(parser.getByteOrder()); - ExifTag tag = null; - - int event = parser.next(); - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - exifData.addIfdData(new IfdData(parser.getCurrentIfd())); - break; - case ExifParser.EVENT_NEW_TAG: - tag = parser.getTag(); - if (!tag.hasValue()) { - parser.registerForTagValue(tag); - } else { - exifData.getIfdData(tag.getIfd()).setTag(tag); - } - break; - case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - tag = parser.getTag(); - if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { - parser.readFullTagValue(tag); - } - exifData.getIfdData(tag.getIfd()).setTag(tag); - break; - case ExifParser.EVENT_COMPRESSED_IMAGE: - byte buf[] = new byte[parser.getCompressedImageSize()]; - if (buf.length == parser.read(buf)) { - exifData.setCompressedThumbnail(buf); - } else { - Log.w(TAG, "Failed to read the compressed thumbnail"); - } - break; - case ExifParser.EVENT_UNCOMPRESSED_STRIP: - buf = new byte[parser.getStripSize()]; - if (buf.length == parser.read(buf)) { - exifData.setStripBytes(parser.getStripIndex(), buf); - } else { - Log.w(TAG, "Failed to read the strip bytes"); - } - break; - } - event = parser.next(); - } - return exifData; - } -} diff --git a/src/com/android/messaging/util/exif/ExifTag.java b/src/com/android/messaging/util/exif/ExifTag.java deleted file mode 100644 index da6f4ed..0000000 --- a/src/com/android/messaging/util/exif/ExifTag.java +++ /dev/null @@ -1,1008 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; - -/** - * This class stores information of an EXIF tag. For more information about - * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be - * instantiated using {@link ExifInterface#buildTag}. - * - * @see ExifInterface - */ -public class ExifTag { - /** - * The BYTE type in the EXIF standard. An 8-bit unsigned integer. - */ - public static final short TYPE_UNSIGNED_BYTE = 1; - /** - * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit - * ASCII code. The final byte is terminated with NULL. - */ - public static final short TYPE_ASCII = 2; - /** - * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer - */ - public static final short TYPE_UNSIGNED_SHORT = 3; - /** - * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer - */ - public static final short TYPE_UNSIGNED_LONG = 4; - /** - * The RATIONAL type of EXIF standard. It consists of two LONGs. The first - * one is the numerator and the second one expresses the denominator. - */ - public static final short TYPE_UNSIGNED_RATIONAL = 5; - /** - * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any - * value depending on the field definition. - */ - public static final short TYPE_UNDEFINED = 7; - /** - * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer - * (2's complement notation). - */ - public static final short TYPE_LONG = 9; - /** - * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first - * one is the numerator and the second one is the denominator. - */ - public static final short TYPE_RATIONAL = 10; - - private static Charset US_ASCII = Charset.forName("US-ASCII"); - private static final int TYPE_TO_SIZE_MAP[] = new int[11]; - private static final int UNSIGNED_SHORT_MAX = 65535; - private static final long UNSIGNED_LONG_MAX = 4294967295L; - private static final long LONG_MAX = Integer.MAX_VALUE; - private static final long LONG_MIN = Integer.MIN_VALUE; - - static { - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1; - TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8; - TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1; - TYPE_TO_SIZE_MAP[TYPE_LONG] = 4; - TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; - } - - static final int SIZE_UNDEFINED = 0; - - // Exif TagId - private final short mTagId; - // Exif Tag Type - private final short mDataType; - // If tag has defined count - private boolean mHasDefinedDefaultComponentCount; - // Actual data count in tag (should be number of elements in value array) - private int mComponentCountActual; - // The ifd that this tag should be put in - private int mIfd; - // The value (array of elements of type Tag Type) - private Object mValue; - // Value offset in exif header. - private int mOffset; - - private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); - - /** - * Returns true if the given IFD is a valid IFD. - */ - public static boolean isValidIfd(int ifdId) { - return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1 - || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY - || ifdId == IfdId.TYPE_IFD_GPS; - } - - /** - * Returns true if a given type is a valid tag type. - */ - public static boolean isValidType(short type) { - return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII || - type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || - type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || - type == TYPE_LONG || type == TYPE_RATIONAL; - } - - // Use builtTag in ExifInterface instead of constructor. - ExifTag(short tagId, short type, int componentCount, int ifd, - boolean hasDefinedComponentCount) { - mTagId = tagId; - mDataType = type; - mComponentCountActual = componentCount; - mHasDefinedDefaultComponentCount = hasDefinedComponentCount; - mIfd = ifd; - mValue = null; - } - - /** - * Gets the element size of the given data type in bytes. - * - * @see #TYPE_ASCII - * @see #TYPE_LONG - * @see #TYPE_RATIONAL - * @see #TYPE_UNDEFINED - * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_LONG - * @see #TYPE_UNSIGNED_RATIONAL - * @see #TYPE_UNSIGNED_SHORT - */ - public static int getElementSize(short type) { - return TYPE_TO_SIZE_MAP[type]; - } - - /** - * Returns the ID of the IFD this tag belongs to. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - public int getIfd() { - return mIfd; - } - - protected void setIfd(int ifdId) { - mIfd = ifdId; - } - - /** - * Gets the TID of this tag. - */ - public short getTagId() { - return mTagId; - } - - /** - * Gets the data type of this tag - * - * @see #TYPE_ASCII - * @see #TYPE_LONG - * @see #TYPE_RATIONAL - * @see #TYPE_UNDEFINED - * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_LONG - * @see #TYPE_UNSIGNED_RATIONAL - * @see #TYPE_UNSIGNED_SHORT - */ - public short getDataType() { - return mDataType; - } - - /** - * Gets the total data size in bytes of the value of this tag. - */ - public int getDataSize() { - return getComponentCount() * getElementSize(getDataType()); - } - - /** - * Gets the component count of this tag. - */ - - // TODO: fix integer overflows with this - public int getComponentCount() { - return mComponentCountActual; - } - - /** - * Sets the component count of this tag. Call this function before - * setValue() if the length of value does not match the component count. - */ - protected void forceSetComponentCount(int count) { - mComponentCountActual = count; - } - - /** - * Returns true if this ExifTag contains value; otherwise, this tag will - * contain an offset value that is determined when the tag is written. - */ - public boolean hasValue() { - return mValue != null; - } - - /** - * Sets integer values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if: - *

    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the component count in the definition - * for this tag.
  • - *
- */ - public boolean setValue(int[] value) { - if (checkBadComponentCount(value.length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG && - mDataType != TYPE_UNSIGNED_LONG) { - return false; - } - if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) { - return false; - } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) { - return false; - } - - long[] data = new long[value.length]; - for (int i = 0; i < value.length; i++) { - data[i] = value[i]; - } - mValue = data; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets integer value into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method - * will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • - *
  • The value overflows.
  • - *
  • The component count in the definition of this tag is not 1.
  • - *
- */ - public boolean setValue(int value) { - return setValue(new int[] { - value - }); - } - - /** - * Sets long values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the component count in the definition - * for this tag.
  • - *
- */ - public boolean setValue(long[] value) { - if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) { - return false; - } - if (checkOverflowForUnsignedLong(value)) { - return false; - } - mValue = value; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets long values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • - *
  • The value overflows.
  • - *
  • The component count in the definition for this tag is not 1.
  • - *
- */ - public boolean setValue(long value) { - return setValue(new long[] { - value - }); - } - - /** - * Sets a string value into this tag. This method should be used for tags of - * type {@link #TYPE_ASCII}. The string is converted to an ASCII string. - * Characters that cannot be converted are replaced with '?'. The length of - * the string must be equal to either (component count -1) or (component - * count). The final byte will be set to the string null terminator '\0', - * overwriting the last character in the string if the value.length is equal - * to the component count. This method will fail if: - *
    - *
  • The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.
  • - *
  • The length of the string is not equal to (component count -1) or - * (component count) in the definition for this tag.
  • - *
- */ - public boolean setValue(String value) { - if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) { - return false; - } - - byte[] buf = value.getBytes(US_ASCII); - byte[] finalBuf = buf; - if (buf.length > 0) { - finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays - .copyOf(buf, buf.length + 1); - } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) { - finalBuf = new byte[] { 0 }; - } - int count = finalBuf.length; - if (checkBadComponentCount(count)) { - return false; - } - mComponentCountActual = count; - mValue = finalBuf; - return true; - } - - /** - * Sets Rational values into this tag. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This - * method will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} - * or {@link #TYPE_RATIONAL}.
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the component count in the definition - * for this tag.
  • - *
- * - * @see Rational - */ - public boolean setValue(Rational[] value) { - if (checkBadComponentCount(value.length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) { - return false; - } - if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) { - return false; - } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) { - return false; - } - - mValue = value; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets a Rational value into this tag. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This - * method will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} - * or {@link #TYPE_RATIONAL}.
  • - *
  • The value overflows.
  • - *
  • The component count in the definition for this tag is not 1.
  • - *
- * - * @see Rational - */ - public boolean setValue(Rational value) { - return setValue(new Rational[] { - value - }); - } - - /** - * Sets byte values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method - * will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or - * {@link #TYPE_UNDEFINED} .
  • - *
  • The length does NOT match the component count in the definition for - * this tag.
  • - *
- */ - public boolean setValue(byte[] value, int offset, int length) { - if (checkBadComponentCount(length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) { - return false; - } - mValue = new byte[length]; - System.arraycopy(value, offset, mValue, 0, length); - mComponentCountActual = length; - return true; - } - - /** - * Equivalent to setValue(value, 0, value.length). - */ - public boolean setValue(byte[] value) { - return setValue(value, 0, value.length); - } - - /** - * Sets byte value into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method - * will fail if: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or - * {@link #TYPE_UNDEFINED} .
  • - *
  • The component count in the definition for this tag is not 1.
  • - *
- */ - public boolean setValue(byte value) { - return setValue(new byte[] { - value - }); - } - - /** - * Sets the value for this tag using an appropriate setValue method for the - * given object. This method will fail if: - *
    - *
  • The corresponding setValue method for the class of the object passed - * in would fail.
  • - *
  • There is no obvious way to cast the object passed in into an EXIF tag - * type.
  • - *
- */ - public boolean setValue(Object obj) { - if (obj == null) { - return false; - } else if (obj instanceof Short) { - return setValue(((Short) obj).shortValue() & 0x0ffff); - } else if (obj instanceof String) { - return setValue((String) obj); - } else if (obj instanceof int[]) { - return setValue((int[]) obj); - } else if (obj instanceof long[]) { - return setValue((long[]) obj); - } else if (obj instanceof Rational) { - return setValue((Rational) obj); - } else if (obj instanceof Rational[]) { - return setValue((Rational[]) obj); - } else if (obj instanceof byte[]) { - return setValue((byte[]) obj); - } else if (obj instanceof Integer) { - return setValue(((Integer) obj).intValue()); - } else if (obj instanceof Long) { - return setValue(((Long) obj).longValue()); - } else if (obj instanceof Byte) { - return setValue(((Byte) obj).byteValue()); - } else if (obj instanceof Short[]) { - // Nulls in this array are treated as zeroes. - Short[] arr = (Short[]) obj; - int[] fin = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff; - } - return setValue(fin); - } else if (obj instanceof Integer[]) { - // Nulls in this array are treated as zeroes. - Integer[] arr = (Integer[]) obj; - int[] fin = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].intValue(); - } - return setValue(fin); - } else if (obj instanceof Long[]) { - // Nulls in this array are treated as zeroes. - Long[] arr = (Long[]) obj; - long[] fin = new long[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].longValue(); - } - return setValue(fin); - } else if (obj instanceof Byte[]) { - // Nulls in this array are treated as zeroes. - Byte[] arr = (Byte[]) obj; - byte[] fin = new byte[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue(); - } - return setValue(fin); - } else { - return false; - } - } - - /** - * Sets a timestamp to this tag. The method converts the timestamp with the - * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This - * method will fail if the data type is not {@link #TYPE_ASCII} or the - * component count of this tag is not 20 or undefined. - * - * @param time the number of milliseconds since Jan. 1, 1970 GMT - * @return true on success - */ - public boolean setTimeValue(long time) { - // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe - synchronized (TIME_FORMAT) { - return setValue(TIME_FORMAT.format(new Date(time))); - } - } - - /** - * Gets the value as a String. This method should be used for tags of type - * {@link #TYPE_ASCII}. - * - * @return the value as a String, or null if the tag's value does not exist - * or cannot be converted to a String. - */ - public String getValueAsString() { - if (mValue == null) { - return null; - } else if (mValue instanceof String) { - return (String) mValue; - } else if (mValue instanceof byte[]) { - return new String((byte[]) mValue, US_ASCII); - } - return null; - } - - /** - * Gets the value as a String. This method should be used for tags of type - * {@link #TYPE_ASCII}. - * - * @param defaultValue the String to return if the tag's value does not - * exist or cannot be converted to a String. - * @return the tag's value as a String, or the defaultValue. - */ - public String getValueAsString(String defaultValue) { - String s = getValueAsString(); - if (s == null) { - return defaultValue; - } - return s; - } - - /** - * Gets the value as a byte array. This method should be used for tags of - * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - * - * @return the value as a byte array, or null if the tag's value does not - * exist or cannot be converted to a byte array. - */ - public byte[] getValueAsBytes() { - if (mValue instanceof byte[]) { - return (byte[]) mValue; - } - return null; - } - - /** - * Gets the value as a byte. If there are more than 1 bytes in this value, - * gets the first byte. This method should be used for tags of type - * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - * - * @param defaultValue the byte to return if tag's value does not exist or - * cannot be converted to a byte. - * @return the tag's value as a byte, or the defaultValue. - */ - public byte getValueAsByte(byte defaultValue) { - byte[] b = getValueAsBytes(); - if (b == null || b.length < 1) { - return defaultValue; - } - return b[0]; - } - - /** - * Gets the value as an array of Rationals. This method should be used for - * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @return the value as as an array of Rationals, or null if the tag's value - * does not exist or cannot be converted to an array of Rationals. - */ - public Rational[] getValueAsRationals() { - if (mValue instanceof Rational[]) { - return (Rational[]) mValue; - } - return null; - } - - /** - * Gets the value as a Rational. If there are more than 1 Rationals in this - * value, gets the first one. This method should be used for tags of type - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @param defaultValue the Rational to return if tag's value does not exist - * or cannot be converted to a Rational. - * @return the tag's value as a Rational, or the defaultValue. - */ - public Rational getValueAsRational(Rational defaultValue) { - Rational[] r = getValueAsRationals(); - if (r == null || r.length < 1) { - return defaultValue; - } - return r[0]; - } - - /** - * Gets the value as a Rational. If there are more than 1 Rationals in this - * value, gets the first one. This method should be used for tags of type - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @param defaultValue the numerator of the Rational to return if tag's - * value does not exist or cannot be converted to a Rational (the - * denominator will be 1). - * @return the tag's value as a Rational, or the defaultValue. - */ - public Rational getValueAsRational(long defaultValue) { - Rational defaultVal = new Rational(defaultValue, 1); - return getValueAsRational(defaultVal); - } - - /** - * Gets the value as an array of ints. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. - * - * @return the value as as an array of ints, or null if the tag's value does - * not exist or cannot be converted to an array of ints. - */ - public int[] getValueAsInts() { - if (mValue == null) { - return null; - } else if (mValue instanceof long[]) { - long[] val = (long[]) mValue; - int[] arr = new int[val.length]; - for (int i = 0; i < val.length; i++) { - arr[i] = (int) val[i]; // Truncates - } - return arr; - } - return null; - } - - /** - * Gets the value as an int. If there are more than 1 ints in this value, - * gets the first one. This method should be used for tags of type - * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. - * - * @param defaultValue the int to return if tag's value does not exist or - * cannot be converted to an int. - * @return the tag's value as a int, or the defaultValue. - */ - public int getValueAsInt(int defaultValue) { - int[] i = getValueAsInts(); - if (i == null || i.length < 1) { - return defaultValue; - } - return i[0]; - } - - /** - * Gets the value as an array of longs. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_LONG}. - * - * @return the value as as an array of longs, or null if the tag's value - * does not exist or cannot be converted to an array of longs. - */ - public long[] getValueAsLongs() { - if (mValue instanceof long[]) { - return (long[]) mValue; - } - return null; - } - - /** - * Gets the value or null if none exists. If there are more than 1 longs in - * this value, gets the first one. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. - * - * @param defaultValue the long to return if tag's value does not exist or - * cannot be converted to a long. - * @return the tag's value as a long, or the defaultValue. - */ - public long getValueAsLong(long defaultValue) { - long[] l = getValueAsLongs(); - if (l == null || l.length < 1) { - return defaultValue; - } - return l[0]; - } - - /** - * Gets the tag's value or null if none exists. - */ - public Object getValue() { - return mValue; - } - - /** - * Gets a long representation of the value. - * - * @param defaultValue value to return if there is no value or value is a - * rational with a denominator of 0. - * @return the tag's value as a long, or defaultValue if no representation - * exists. - */ - public long forceGetValueAsLong(long defaultValue) { - long[] l = getValueAsLongs(); - if (l != null && l.length >= 1) { - return l[0]; - } - byte[] b = getValueAsBytes(); - if (b != null && b.length >= 1) { - return b[0]; - } - Rational[] r = getValueAsRationals(); - if (r != null && r.length >= 1 && r[0].getDenominator() != 0) { - return (long) r[0].toDouble(); - } - return defaultValue; - } - - /** - * Gets a string representation of the value. - */ - public String forceGetValueAsString() { - if (mValue == null) { - return ""; - } else if (mValue instanceof byte[]) { - if (mDataType == TYPE_ASCII) { - return new String((byte[]) mValue, US_ASCII); - } else { - return Arrays.toString((byte[]) mValue); - } - } else if (mValue instanceof long[]) { - if (((long[]) mValue).length == 1) { - return String.valueOf(((long[]) mValue)[0]); - } else { - return Arrays.toString((long[]) mValue); - } - } else if (mValue instanceof Object[]) { - if (((Object[]) mValue).length == 1) { - Object val = ((Object[]) mValue)[0]; - if (val == null) { - return ""; - } else { - return val.toString(); - } - } else { - return Arrays.toString((Object[]) mValue); - } - } else { - return mValue.toString(); - } - } - - /** - * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG}, - * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call - * {@link #getRational(int)} instead. - * - * @exception IllegalArgumentException if the data type is - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - */ - protected long getValueAt(int index) { - if (mValue instanceof long[]) { - return ((long[]) mValue)[index]; - } else if (mValue instanceof byte[]) { - return ((byte[]) mValue)[index]; - } - throw new IllegalArgumentException("Cannot get integer value from " - + convertTypeToString(mDataType)); - } - - /** - * Gets the {@link #TYPE_ASCII} data. - * - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_ASCII}. - */ - protected String getString() { - if (mDataType != TYPE_ASCII) { - throw new IllegalArgumentException("Cannot get ASCII value from " - + convertTypeToString(mDataType)); - } - return new String((byte[]) mValue, US_ASCII); - } - - /* - * Get the converted ascii byte. Used by ExifOutputStream. - */ - protected byte[] getStringByte() { - return (byte[]) mValue; - } - - /** - * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data. - * - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - */ - protected Rational getRational(int index) { - if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) { - throw new IllegalArgumentException("Cannot get RATIONAL value from " - + convertTypeToString(mDataType)); - } - return ((Rational[]) mValue)[index]; - } - - /** - * Equivalent to getBytes(buffer, 0, buffer.length). - */ - protected void getBytes(byte[] buf) { - getBytes(buf, 0, buf.length); - } - - /** - * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data. - * - * @param buf the byte array in which to store the bytes read. - * @param offset the initial position in buffer to store the bytes. - * @param length the maximum number of bytes to store in buffer. If length > - * component count, only the valid bytes will be stored. - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - */ - protected void getBytes(byte[] buf, int offset, int length) { - if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) { - throw new IllegalArgumentException("Cannot get BYTE value from " - + convertTypeToString(mDataType)); - } - System.arraycopy(mValue, 0, buf, offset, - (length > mComponentCountActual) ? mComponentCountActual : length); - } - - /** - * Gets the offset of this tag. This is only valid if this data size > 4 and - * contains an offset to the location of the actual value. - */ - protected int getOffset() { - return mOffset; - } - - /** - * Sets the offset of this tag. - */ - protected void setOffset(int offset) { - mOffset = offset; - } - - protected void setHasDefinedCount(boolean d) { - mHasDefinedDefaultComponentCount = d; - } - - protected boolean hasDefinedCount() { - return mHasDefinedDefaultComponentCount; - } - - private boolean checkBadComponentCount(int count) { - if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) { - return true; - } - return false; - } - - private static String convertTypeToString(short type) { - switch (type) { - case TYPE_UNSIGNED_BYTE: - return "UNSIGNED_BYTE"; - case TYPE_ASCII: - return "ASCII"; - case TYPE_UNSIGNED_SHORT: - return "UNSIGNED_SHORT"; - case TYPE_UNSIGNED_LONG: - return "UNSIGNED_LONG"; - case TYPE_UNSIGNED_RATIONAL: - return "UNSIGNED_RATIONAL"; - case TYPE_UNDEFINED: - return "UNDEFINED"; - case TYPE_LONG: - return "LONG"; - case TYPE_RATIONAL: - return "RATIONAL"; - default: - return ""; - } - } - - private boolean checkOverflowForUnsignedShort(int[] value) { - for (int v : value) { - if (v > UNSIGNED_SHORT_MAX || v < 0) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedLong(long[] value) { - for (long v : value) { - if (v < 0 || v > UNSIGNED_LONG_MAX) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedLong(int[] value) { - for (int v : value) { - if (v < 0) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedRational(Rational[] value) { - for (Rational v : value) { - if (v.getNumerator() < 0 || v.getDenominator() < 0 - || v.getNumerator() > UNSIGNED_LONG_MAX - || v.getDenominator() > UNSIGNED_LONG_MAX) { - return true; - } - } - return false; - } - - private boolean checkOverflowForRational(Rational[] value) { - for (Rational v : value) { - if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN - || v.getNumerator() > LONG_MAX - || v.getDenominator() > LONG_MAX) { - return true; - } - } - return false; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj instanceof ExifTag) { - ExifTag tag = (ExifTag) obj; - if (tag.mTagId != this.mTagId - || tag.mComponentCountActual != this.mComponentCountActual - || tag.mDataType != this.mDataType) { - return false; - } - if (mValue != null) { - if (tag.mValue == null) { - return false; - } else if (mValue instanceof long[]) { - if (!(tag.mValue instanceof long[])) { - return false; - } - return Arrays.equals((long[]) mValue, (long[]) tag.mValue); - } else if (mValue instanceof Rational[]) { - if (!(tag.mValue instanceof Rational[])) { - return false; - } - return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue); - } else if (mValue instanceof byte[]) { - if (!(tag.mValue instanceof byte[])) { - return false; - } - return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue); - } else { - return mValue.equals(tag.mValue); - } - } else { - return tag.mValue == null; - } - } - return false; - } - - @Override - public String toString() { - return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: " - + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual - + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n"; - } - -} diff --git a/src/com/android/messaging/util/exif/IfdData.java b/src/com/android/messaging/util/exif/IfdData.java deleted file mode 100644 index 6b8c293..0000000 --- a/src/com/android/messaging/util/exif/IfdData.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class stores all the tags in an IFD. - * - * @see ExifData - * @see ExifTag - */ -class IfdData { - - private final int mIfdId; - private final Map mExifTags = new HashMap(); - private int mOffsetToNextIfd = 0; - private static final int[] sIfds = { - IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, - IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS - }; - /** - * Creates an IfdData with given IFD ID. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - IfdData(int ifdId) { - mIfdId = ifdId; - } - - protected static int[] getIfds() { - return sIfds; - } - - /** - * Get a array the contains all {@link ExifTag} in this IFD. - */ - protected ExifTag[] getAllTags() { - return mExifTags.values().toArray(new ExifTag[mExifTags.size()]); - } - - /** - * Gets the ID of this IFD. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - protected int getId() { - return mIfdId; - } - - /** - * Gets the {@link ExifTag} with given tag id. Return null if there is no - * such tag. - */ - protected ExifTag getTag(short tagId) { - return mExifTags.get(tagId); - } - - /** - * Adds or replaces a {@link ExifTag}. - */ - protected ExifTag setTag(ExifTag tag) { - tag.setIfd(mIfdId); - return mExifTags.put(tag.getTagId(), tag); - } - - protected boolean checkCollision(short tagId) { - return mExifTags.get(tagId) != null; - } - - /** - * Removes the tag of the given ID - */ - protected void removeTag(short tagId) { - mExifTags.remove(tagId); - } - - /** - * Gets the tags count in the IFD. - */ - protected int getTagCount() { - return mExifTags.size(); - } - - /** - * Sets the offset of next IFD. - */ - protected void setOffsetToNextIfd(int offset) { - mOffsetToNextIfd = offset; - } - - /** - * Gets the offset of next IFD. - */ - protected int getOffsetToNextIfd() { - return mOffsetToNextIfd; - } - - /** - * Returns true if all tags in this two IFDs are equal. Note that tags of - * IFDs offset or thumbnail offset will be ignored. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (obj instanceof IfdData) { - IfdData data = (IfdData) obj; - if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) { - ExifTag[] tags = data.getAllTags(); - for (ExifTag tag : tags) { - if (ExifInterface.isOffsetTag(tag.getTagId())) { - continue; - } - ExifTag tag2 = mExifTags.get(tag.getTagId()); - if (!tag.equals(tag2)) { - return false; - } - } - return true; - } - } - return false; - } -} diff --git a/src/com/android/messaging/util/exif/IfdId.java b/src/com/android/messaging/util/exif/IfdId.java deleted file mode 100644 index 06a820d..0000000 --- a/src/com/android/messaging/util/exif/IfdId.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -/** - * The constants of the IFD ID defined in EXIF spec. - */ -public interface IfdId { - public static final int TYPE_IFD_0 = 0; - public static final int TYPE_IFD_1 = 1; - public static final int TYPE_IFD_EXIF = 2; - public static final int TYPE_IFD_INTEROPERABILITY = 3; - public static final int TYPE_IFD_GPS = 4; - /* This is used in ExifData to allocate enough IfdData */ - static final int TYPE_IFD_COUNT = 5; - -} diff --git a/src/com/android/messaging/util/exif/JpegHeader.java b/src/com/android/messaging/util/exif/JpegHeader.java deleted file mode 100644 index 1dd12a5..0000000 --- a/src/com/android/messaging/util/exif/JpegHeader.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -class JpegHeader { - public static final short SOI = (short) 0xFFD8; - public static final short APP1 = (short) 0xFFE1; - public static final short APP0 = (short) 0xFFE0; - public static final short EOI = (short) 0xFFD9; - - /** - * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG, - * and DAC marker. - */ - public static final short SOF0 = (short) 0xFFC0; - public static final short SOF15 = (short) 0xFFCF; - public static final short DHT = (short) 0xFFC4; - public static final short JPG = (short) 0xFFC8; - public static final short DAC = (short) 0xFFCC; - - public static final boolean isSofMarker(short marker) { - return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG - && marker != DAC; - } -} diff --git a/src/com/android/messaging/util/exif/OrderedDataOutputStream.java b/src/com/android/messaging/util/exif/OrderedDataOutputStream.java deleted file mode 100644 index cf64805..0000000 --- a/src/com/android/messaging/util/exif/OrderedDataOutputStream.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -class OrderedDataOutputStream extends FilterOutputStream { - private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4); - - public OrderedDataOutputStream(OutputStream out) { - super(out); - } - - public OrderedDataOutputStream setByteOrder(ByteOrder order) { - mByteBuffer.order(order); - return this; - } - - public OrderedDataOutputStream writeShort(short value) throws IOException { - mByteBuffer.rewind(); - mByteBuffer.putShort(value); - out.write(mByteBuffer.array(), 0, 2); - return this; - } - - public OrderedDataOutputStream writeInt(int value) throws IOException { - mByteBuffer.rewind(); - mByteBuffer.putInt(value); - out.write(mByteBuffer.array()); - return this; - } - - public OrderedDataOutputStream writeRational(Rational rational) throws IOException { - writeInt((int) rational.getNumerator()); - writeInt((int) rational.getDenominator()); - return this; - } -} diff --git a/src/com/android/messaging/util/exif/Rational.java b/src/com/android/messaging/util/exif/Rational.java deleted file mode 100644 index b42ceb7..0000000 --- a/src/com/android/messaging/util/exif/Rational.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util.exif; - -/** - * The rational data type of EXIF tag. Contains a pair of longs representing the - * numerator and denominator of a Rational number. - */ -public class Rational { - - private final long mNumerator; - private final long mDenominator; - - /** - * Create a Rational with a given numerator and denominator. - * - * @param nominator - * @param denominator - */ - public Rational(long nominator, long denominator) { - mNumerator = nominator; - mDenominator = denominator; - } - - /** - * Create a copy of a Rational. - */ - public Rational(Rational r) { - mNumerator = r.mNumerator; - mDenominator = r.mDenominator; - } - - /** - * Gets the numerator of the rational. - */ - public long getNumerator() { - return mNumerator; - } - - /** - * Gets the denominator of the rational - */ - public long getDenominator() { - return mDenominator; - } - - /** - * Gets the rational value as type double. Will cause a divide-by-zero error - * if the denominator is 0. - */ - public double toDouble() { - return mNumerator / (double) mDenominator; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (obj instanceof Rational) { - Rational data = (Rational) obj; - return mNumerator == data.mNumerator && mDenominator == data.mDenominator; - } - return false; - } - - @Override - public String toString() { - return mNumerator + "/" + mDenominator; - } -} diff --git a/src/com/android/messaging/widget/BaseWidgetFactory.java b/src/com/android/messaging/widget/BaseWidgetFactory.java deleted file mode 100644 index 30b80ae..0000000 --- a/src/com/android/messaging/widget/BaseWidgetFactory.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Binder; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.style.StyleSpan; -import android.widget.RemoteViews; -import android.widget.RemoteViewsService; - -import com.android.messaging.R; -import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor; -import com.android.messaging.datamodel.media.AvatarRequestDescriptor; -import com.android.messaging.datamodel.media.ImageRequestDescriptor; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.LogUtil; - -/** - * Remote Views Factory for Bugle Widget. - */ -abstract class BaseWidgetFactory implements RemoteViewsService.RemoteViewsFactory { - protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG; - - protected static final int MAX_ITEMS_TO_SHOW = 25; - - /** - * Lock to avoid race condition between widgets. - */ - protected static final Object sWidgetLock = new Object(); - - protected final Context mContext; - protected final int mAppWidgetId; - protected boolean mShouldShowViewMore; - protected Cursor mCursor; - protected final AppWidgetManager mAppWidgetManager; - protected int mIconSize; - protected ImageResource mAvatarResource; - - public BaseWidgetFactory(Context context, Intent intent) { - mContext = context; - mAppWidgetId = intent.getIntExtra( - AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - mAppWidgetManager = AppWidgetManager.getInstance(context); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetFactory intent: " + intent + "widget id: " + mAppWidgetId); - } - mIconSize = (int) context.getResources() - .getDimension(R.dimen.contact_icon_view_normal_size); - - } - - @Override - public void onCreate() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onCreate"); - } - } - - @Override - public void onDestroy() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onDestroy"); - } - synchronized (sWidgetLock) { - if (mCursor != null && !mCursor.isClosed()) { - mCursor.close(); - mCursor = null; - } - } - } - - @Override - public void onDataSetChanged() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onDataSetChanged"); - } - synchronized (sWidgetLock) { - if (mCursor != null) { - mCursor.close(); - mCursor = null; - } - final long token = Binder.clearCallingIdentity(); - try { - mCursor = doQuery(); - onLoadComplete(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - protected abstract Cursor doQuery(); - - /** - * Returns the number of items that should be shown in the widget list. This method also - * updates the boolean that indicates whether the "show more" item should be shown. - * @return the number of items to be displayed in the list. - */ - @Override - public int getCount() { - synchronized (sWidgetLock) { - if (mCursor == null) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getCount: 0"); - } - return 0; - } - final int count = getItemCount(); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getCount: " + count); - } - mShouldShowViewMore = count < mCursor.getCount(); - return count + (mShouldShowViewMore ? 1 : 0); - } - } - - /** - * Returns the number of messages that should be shown in the widget. This method - * doesn't update the boolean that indicates whether the "show more" item should be included - * in the list. - * @return - */ - protected int getItemCount() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getItemCount: " + mCursor.getCount()); - } - return Math.min(mCursor.getCount(), MAX_ITEMS_TO_SHOW); - } - - /* - * Make the given text bold if the item is unread - */ - protected CharSequence boldifyIfUnread(CharSequence text, final boolean unread) { - if (!unread) { - return text; - } - final SpannableStringBuilder builder = new SpannableStringBuilder(text); - builder.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return builder; - } - - protected Bitmap getAvatarBitmap(final Uri avatarUri) { - final String avatarType = avatarUri == null ? - null : AvatarUriUtil.getAvatarType(avatarUri); - ImageRequestDescriptor descriptor; - if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) { - descriptor = new AvatarGroupRequestDescriptor(avatarUri, mIconSize, mIconSize); - } else { - descriptor = new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize); - } - - final MediaRequest imageRequest = - descriptor.buildSyncMediaRequest(mContext); - final ImageResource imageResource = - MediaResourceManager.get().requestMediaResourceSync(imageRequest); - if (imageResource != null) { - setAvatarResource(imageResource); - return mAvatarResource.getBitmap(); - } else { - releaseAvatarResource(); - return null; - } - } - - /** - * @return the "View more messages" view. When the user taps this item, they're - * taken to the conversation in Bugle. - */ - abstract protected RemoteViews getViewMoreItemsView(); - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public long getItemId(int position) { - return position; - } - - private void onLoadComplete() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onLoadComplete"); - } - final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), - getMainLayoutId()); - mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews); - } - - protected abstract int getMainLayoutId(); - - private void setAvatarResource(final ImageResource resource) { - if (mAvatarResource != resource) { - // Clear out any information for what is currently used - releaseAvatarResource(); - mAvatarResource = resource; - } - } - - private void releaseAvatarResource() { - if (mAvatarResource != null) { - mAvatarResource.release(); - } - mAvatarResource = null; - } -} diff --git a/src/com/android/messaging/widget/BaseWidgetProvider.java b/src/com/android/messaging/widget/BaseWidgetProvider.java deleted file mode 100644 index 431a6c7..0000000 --- a/src/com/android/messaging/widget/BaseWidgetProvider.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import com.android.messaging.util.LogUtil; - -public abstract class BaseWidgetProvider extends AppWidgetProvider { - protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG; - - public static final int WIDGET_CONVERSATION_REQUEST_CODE = 987; - - static final String WIDGET_SIZE_KEY = "widgetSizeKey"; - - public static final int SIZE_LARGE = 0; // undefined == 0, which is the default, large - public static final int SIZE_SMALL = 1; - public static final int SIZE_MEDIUM = 2; - public static final int SIZE_PRE_JB = 3; - - /** - * Update all widgets in the list - */ - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.onUpdate(context, appWidgetManager, appWidgetIds); - - for (int i = 0; i < appWidgetIds.length; ++i) { - updateWidget(context, appWidgetIds[i]); - } - } - - @Override - public void onReceive(Context context, Intent intent) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onReceive intent: " + intent + " for " + this.getClass()); - } - final String action = intent.getAction(); - - // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here - // we're looking for an intent sent by our app when it knows a message has - // been sent or received (or a conversation has been read) and is telling the widget it - // needs to update. - if (getAction().equals(action)) { - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, - this.getClass())); - - if (appWidgetIds.length > 0) { - // We need to update all Bugle app widgets on the home screen. - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onReceive notifyAppWidgetViewDataChanged listId: " + - getListId() + " first widgetId: " + appWidgetIds[0]); - } - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, getListId()); - } - } else { - super.onReceive(context, intent); - } - } - - protected abstract String getAction(); - - protected abstract int getListId(); - - /** - * Update the widget appWidgetId - */ - protected abstract void updateWidget(Context context, int appWidgetId); - - private int getWidgetSize(AppWidgetManager appWidgetManager, - int appWidgetId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize"); - } - - // Get the dimensions - final Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); - - // Get min width and height. - final int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); - final int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); - - // First find out rows and columns based on width provided. - final int rows = getCellsForSize(minHeight); - final int columns = getCellsForSize(minWidth); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize row: " + rows + - " columns: " + columns); - } - - int size = SIZE_MEDIUM; - if (rows == 1) { - size = SIZE_SMALL; // Our widget doesn't let itself get this small. Perhaps in the - // future will add a super-mini widget. - } else if (columns > 3) { - size = SIZE_LARGE; - } - - // put the size in the bundle so our service know what size it's dealing with. - final int savedSize = options.getInt(WIDGET_SIZE_KEY); - if (savedSize != size) { - options.putInt(WIDGET_SIZE_KEY, size); - appWidgetManager.updateAppWidgetOptions(appWidgetId, options); - - // The size changed. We have to force the widget to rebuild the list. - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, getListId()); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize old size: " + savedSize + - " new size saved: " + size); - } - } - - return size; - } - - /** - * Returns number of cells needed for given size of the widget. - * - * @param size Widget size in dp. - * @return Size in number of cells. - */ - private static int getCellsForSize(int size) { - // The hardwired sizes in this function come from the hardwired formula found in - // Android's UI guidelines for widget design: - // http://developer.android.com/guide/practices/ui_guidelines/widget_design.html - return (size + 30) / 70; - } - - @Override - public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, - int appWidgetId, Bundle newOptions) { - - final int widgetSize = getWidgetSize(appWidgetManager, appWidgetId); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetProvider.onAppWidgetOptionsChanged new size: " + - widgetSize); - } - - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); - } - - protected void deletePreferences(final int widgetId) { - } - - /** - * Remove preferences when deleting widget - */ - @Override - public void onDeleted(Context context, int[] appWidgetIds) { - super.onDeleted(context, appWidgetIds); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BaseWidgetProvider.onDeleted"); - } - - for (final int widgetId : appWidgetIds) { - deletePreferences(widgetId); - } - } - -} diff --git a/src/com/android/messaging/widget/BugleWidgetProvider.java b/src/com/android/messaging/widget/BugleWidgetProvider.java deleted file mode 100644 index 50c97b6..0000000 --- a/src/com/android/messaging/widget/BugleWidgetProvider.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.widget.RemoteViews; - -import com.android.messaging.R; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UiUtils; - -public class BugleWidgetProvider extends BaseWidgetProvider { - public static final String ACTION_NOTIFY_CONVERSATIONS_CHANGED = - "com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED"; - - public static final int WIDGET_NEW_CONVERSATION_REQUEST_CODE = 986; - - /** - * Update the widget appWidgetId - */ - @Override - protected void updateWidget(final Context context, final int appWidgetId) { - if (OsUtil.hasRequiredPermissions()) { - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - rebuildWidget(context, appWidgetId); - } - }); - } else { - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, - UiUtils.getWidgetMissingPermissionView(context)); - } - } - - @Override - protected String getAction() { - return ACTION_NOTIFY_CONVERSATIONS_CHANGED; - } - - @Override - protected int getListId() { - return R.id.conversation_list; - } - - public static void rebuildWidget(final Context context, final int appWidgetId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BugleWidgetProvider.rebuildWidget appWidgetId: " + appWidgetId); - } - final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), - R.layout.widget_conversation_list); - PendingIntent clickIntent; - - // Launch an intent to avoid ANRs - final Intent intent = new Intent(context, WidgetConversationListService.class); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); - remoteViews.setRemoteAdapter(appWidgetId, R.id.conversation_list, intent); - - remoteViews.setTextViewText(R.id.widget_label, context.getString(R.string.app_name)); - - // Open Bugle's app conversation list when click on header - clickIntent = UIIntents.get().getWidgetPendingIntentForConversationListActivity(context); - remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent); - - // On click intent for Compose - clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context, - null /*conversationId*/, WIDGET_NEW_CONVERSATION_REQUEST_CODE); - remoteViews.setOnClickPendingIntent(R.id.widget_compose, clickIntent); - - // On click intent for Conversation - // Note: the template intent has to be a "naked" intent without any extras. It turns out - // that if the template intent does have extras, those particular extras won't get - // replaced by the fill-in intent on each list item. - clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context, - null /*conversationId*/, WIDGET_CONVERSATION_REQUEST_CODE); - remoteViews.setPendingIntentTemplate(R.id.conversation_list, clickIntent); - - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews); - } - - /* - * notifyDatasetChanged call when the conversation list changes so the Bugle widget will - * update and reflect the changes - */ - public static void notifyConversationListChanged(final Context context) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "notifyConversationListChanged"); - } - final Intent intent = new Intent(ACTION_NOTIFY_CONVERSATIONS_CHANGED); - context.sendBroadcast(intent); - } -} diff --git a/src/com/android/messaging/widget/WidgetConversationListService.java b/src/com/android/messaging/widget/WidgetConversationListService.java deleted file mode 100644 index 264b98c..0000000 --- a/src/com/android/messaging/widget/WidgetConversationListService.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.RemoteViewsService; - -import com.android.messaging.R; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.conversationlist.ConversationListItemView; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.Dates; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -public class WidgetConversationListService extends RemoteViewsService { - private static final String TAG = LogUtil.BUGLE_WIDGET_TAG; - - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onGetViewFactory intent: " + intent); - } - return new WidgetConversationListFactory(getApplicationContext(), intent); - } - - /** - * Remote Views Factory for Bugle Widget. - */ - private static class WidgetConversationListFactory extends BaseWidgetFactory { - - public WidgetConversationListFactory(Context context, Intent intent) { - super(context, intent); - } - - @Override - protected Cursor doQuery() { - return mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI, - ConversationListItemData.PROJECTION, - ConversationListData.WHERE_NOT_ARCHIVED, - null, // selection args - ConversationListData.SORT_ORDER); - } - - /** - * @return the {@link RemoteViews} for a specific position in the list. - */ - @Override - public RemoteViews getViewAt(int position) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewAt position: " + position); - } - synchronized (sWidgetLock) { - // "View more conversations" view. - if (mCursor == null - || (mShouldShowViewMore && position >= getItemCount())) { - return getViewMoreItemsView(); - } - - if (!mCursor.moveToPosition(position)) { - // If we ever fail to move to a position, return the "View More conversations" - // view. - LogUtil.w(TAG, "Failed to move to position: " + position); - return getViewMoreItemsView(); - } - - final ConversationListItemData conv = new ConversationListItemData(); - conv.bind(mCursor); - - // Inflate and fill out the remote view - final RemoteViews remoteViews = new RemoteViews( - mContext.getPackageName(), R.layout.widget_conversation_list_item); - - final boolean hasUnreadMessages = !conv.getIsRead(); - final Resources resources = mContext.getResources(); - final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp(); - - final String timeStamp = conv.getIsSendRequested() ? - resources.getString(R.string.message_status_sending) : - Dates.getWidgetTimeString(conv.getTimestamp(), true /*abbreviated*/) - .toString(); - // Date/Timestamp or Sending or Error state -- all shown in the date item - remoteViews.setTextViewText(R.id.date, - boldifyIfUnread(timeStamp, hasUnreadMessages)); - - // From - remoteViews.setTextViewText(R.id.from, - boldifyIfUnread(conv.getName(), hasUnreadMessages)); - - // Notifications turned off mini-bell icon - remoteViews.setViewVisibility(R.id.conversation_notification_bell, - conv.getNotificationEnabled() ? View.GONE : View.VISIBLE); - - // On click intent. - final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext, - conv.getConversationId(), null /* draft */); - - remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item, intent); - - // Avatar - boolean includeAvatar; - if (OsUtil.isAtLeastJB()) { - final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " + - options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY)); - } - - includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) == - BugleWidgetProvider.SIZE_LARGE; - } else { - includeAvatar = true;; - } - - // Show the avatar when grande size, otherwise hide it. - remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ? - View.VISIBLE : View.GONE); - - Uri iconUri = null; - if (conv.getIcon() != null) { - iconUri = Uri.parse(conv.getIcon()); - } - remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ? - getAvatarBitmap(iconUri) : null); - - // Error - // Only show the fail icon if it is not a group conversation. - // And also require that we be the default sms app. - final boolean showError = conv.getIsFailedStatus() && - isDefaultSmsApp; - final boolean showDraft = conv.getShowDraft() && - isDefaultSmsApp; - remoteViews.setViewVisibility(R.id.conversation_failed_status_icon, - showError && includeAvatar ? - View.VISIBLE : View.GONE); - - if (showError || showDraft) { - remoteViews.setViewVisibility(R.id.snippet, View.GONE); - remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE); - remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv)); - - if (showDraft) { - // Show italicized "Draft" on third line - final String text = resources.getString( - R.string.conversation_list_item_view_draft_message); - SpannableStringBuilder builder = new SpannableStringBuilder(text); - builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new ForegroundColorSpan( - resources.getColor(R.color.widget_text_color)), - 0, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - remoteViews.setTextViewText(R.id.errorText, builder); - } else { - // Show error message on third line - int failureMessageId = R.string.message_status_download_failed; - if (conv.getIsMessageTypeOutgoing()) { - failureMessageId = MmsUtils.mapRawStatusToErrorResourceId( - conv.getMessageStatus(), - conv.getMessageRawTelephonyStatus()); - } - remoteViews.setTextViewText(R.id.errorText, - resources.getString(failureMessageId)); - } - } else { - remoteViews.setViewVisibility(R.id.errorBlock, View.GONE); - remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE); - remoteViews.setTextViewText(R.id.snippet, - boldifyIfUnread(getSnippetText(conv), hasUnreadMessages)); - } - - // Set the accessibility TalkBack text - remoteViews.setContentDescription(R.id.widget_conversation_list_item, - ConversationListItemView.buildContentDescription(mContext.getResources(), - conv, new TextPaint())); - - return remoteViews; - } - } - - private String getSnippetText(final ConversationListItemData conv) { - String snippetText = conv.getShowDraft() ? - conv.getDraftSnippetText() : conv.getSnippetText(); - final String previewContentType = conv.getShowDraft() ? - conv.getDraftPreviewContentType() : conv.getPreviewContentType(); - if (TextUtils.isEmpty(snippetText)) { - Resources resources = mContext.getResources(); - // Use the attachment type as a snippet so the preview doesn't look odd - if (ContentType.isAudioType(previewContentType)) { - snippetText = resources.getString( - R.string.conversation_list_snippet_audio_clip); - } else if (ContentType.isImageType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_picture); - } else if (ContentType.isVideoType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_video); - } else if (ContentType.isVCardType(previewContentType)) { - snippetText = resources.getString(R.string.conversation_list_snippet_vcard); - } - } - return snippetText; - } - - /** - * @return the "View more conversations" view. When the user taps this item, they're - * taken to the Bugle's conversation list. - */ - @Override - protected RemoteViews getViewMoreItemsView() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewMoreItemsView"); - } - final RemoteViews view = new RemoteViews(mContext.getPackageName(), - R.layout.widget_loading); - view.setTextViewText( - R.id.loading_text, mContext.getText(R.string.view_more_conversations)); - - // Tapping this "More conversations" item should take us to the ConversationList. - // However, the list view is primed with an intent to go to the Conversation activity. - // Each normal conversation list item sets the fill-in intent with the - // ConversationId for that particular conversation. In other words, the only place - // we can go is the ConversationActivity. We add an extra here to tell the - // ConversationActivity to really take us to the ConversationListActivity. - final Intent intent = new Intent(); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true); - view.setOnClickFillInIntent(R.id.widget_loading, intent); - return view; - } - - @Override - public RemoteViews getLoadingView() { - RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading); - view.setTextViewText( - R.id.loading_text, mContext.getText(R.string.loading_conversations)); - return view; - } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - protected int getMainLayoutId() { - return R.layout.widget_conversation_list; - } - } - -} diff --git a/src/com/android/messaging/widget/WidgetConversationProvider.java b/src/com/android/messaging/widget/WidgetConversationProvider.java deleted file mode 100644 index 6ae5614..0000000 --- a/src/com/android/messaging/widget/WidgetConversationProvider.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Looper; -import android.text.TextUtils; -import android.view.View; -import android.widget.RemoteViews; - -import com.android.messaging.R; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.WidgetPickConversationActivity; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.SafeAsyncTask; -import com.android.messaging.util.UiUtils; - -public class WidgetConversationProvider extends BaseWidgetProvider { - public static final String ACTION_NOTIFY_MESSAGES_CHANGED = - "com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED"; - - public static final int WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE = 1985; - public static final int WIDGET_CONVERSATION_REPLY_CODE = 1987; - - // Intent extras - public static final String UI_INTENT_EXTRA_RECIPIENT = "recipient"; - public static final String UI_INTENT_EXTRA_ICON = "icon"; - - /** - * Update the widget appWidgetId - */ - @Override - protected void updateWidget(final Context context, final int appWidgetId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "updateWidget appWidgetId: " + appWidgetId); - } - if (OsUtil.hasRequiredPermissions()) { - rebuildWidget(context, appWidgetId); - } else { - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, - UiUtils.getWidgetMissingPermissionView(context)); - } - } - - @Override - protected String getAction() { - return ACTION_NOTIFY_MESSAGES_CHANGED; - } - - @Override - protected int getListId() { - return R.id.message_list; - } - - public static void rebuildWidget(final Context context, final int appWidgetId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " + appWidgetId); - } - final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), - R.layout.widget_conversation); - PendingIntent clickIntent; - final UIIntents uiIntents = UIIntents.get(); - if (!isWidgetConfigured(appWidgetId)) { - // Widget has not been configured yet. Hide the normal UI elements and show the - // configuration view instead. - remoteViews.setViewVisibility(R.id.widget_label, View.GONE); - remoteViews.setViewVisibility(R.id.message_list, View.GONE); - remoteViews.setViewVisibility(R.id.launcher_icon, View.VISIBLE); - remoteViews.setViewVisibility(R.id.widget_configuration, View.VISIBLE); - - remoteViews.setOnClickPendingIntent(R.id.widget_configuration, - uiIntents.getWidgetPendingIntentForConfigurationActivity(context, appWidgetId)); - - // On click intent for Goto Conversation List - clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context); - remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent); - - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " + - appWidgetId + " going into configure state"); - } - } else { - remoteViews.setViewVisibility(R.id.widget_label, View.VISIBLE); - remoteViews.setViewVisibility(R.id.message_list, View.VISIBLE); - remoteViews.setViewVisibility(R.id.launcher_icon, View.GONE); - remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE); - - final String conversationId = - WidgetPickConversationActivity.getConversationIdPref(appWidgetId); - final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper(); - // If we're running on the UI thread, we can't do the DB access needed to get the - // conversation data. We'll do excute this again off of the UI thread. - final ConversationListItemData convData = isMainThread ? - null : getConversationData(context, conversationId); - - // Launch an intent to avoid ANRs - final Intent intent = new Intent(context, WidgetConversationService.class); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); - remoteViews.setRemoteAdapter(appWidgetId, R.id.message_list, intent); - - remoteViews.setTextViewText(R.id.widget_label, convData != null ? - convData.getName() : context.getString(R.string.app_name)); - - // On click intent for Goto Conversation List - clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context); - remoteViews.setOnClickPendingIntent(R.id.widget_goto_conversation_list, clickIntent); - - // Open the conversation when click on header - clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context, - conversationId, WIDGET_CONVERSATION_REQUEST_CODE); - remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent); - - // On click intent for Conversation - // Note: the template intent has to be a "naked" intent without any extras. It turns out - // that if the template intent does have extras, those particular extras won't get - // replaced by the fill-in intent on each list item. - clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context, - conversationId, WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE); - remoteViews.setPendingIntentTemplate(R.id.message_list, clickIntent); - - if (isMainThread) { - // We're running on the UI thread and we couldn't update all the parts of the - // widget dependent on ConversationListItemData. However, we have to update - // the widget regardless, even with those missing pieces. Here we update the - // widget again in the background. - SafeAsyncTask.executeOnThreadPool(new Runnable() { - @Override - public void run() { - rebuildWidget(context, appWidgetId); - } - }); - } - } - - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews); - - } - - /* - * notifyMessagesChanged called when the conversation changes so the widget will - * update and reflect the changes - */ - public static void notifyMessagesChanged(final Context context, final String conversationId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "notifyMessagesChanged"); - } - final Intent intent = new Intent(ACTION_NOTIFY_MESSAGES_CHANGED); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - context.sendBroadcast(intent); - } - - /* - * notifyConversationDeleted is called when a conversation is deleted. Look through all the - * widgets and if they're displaying that conversation, force the widget into its - * configuration state. - */ - public static void notifyConversationDeleted(final Context context, - final String conversationId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "notifyConversationDeleted convId: " + conversationId); - } - - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context, - WidgetConversationProvider.class))) { - // Retrieve the persisted information for this widget from preferences. - final String widgetConvId = - WidgetPickConversationActivity.getConversationIdPref(appWidgetId); - - if (widgetConvId == null || widgetConvId.equals(conversationId)) { - if (widgetConvId != null) { - WidgetPickConversationActivity.deleteConversationIdPref(appWidgetId); - } - rebuildWidget(context, appWidgetId); - } - } - } - - /* - * notifyConversationRenamed is called when a conversation is renamed. Look through all the - * widgets and if they're displaying that conversation, force the widget to rebuild itself - */ - public static void notifyConversationRenamed(final Context context, - final String conversationId) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "notifyConversationRenamed convId: " + conversationId); - } - - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context, - WidgetConversationProvider.class))) { - // Retrieve the persisted information for this widget from preferences. - final String widgetConvId = - WidgetPickConversationActivity.getConversationIdPref(appWidgetId); - - if (widgetConvId != null && widgetConvId.equals(conversationId)) { - rebuildWidget(context, appWidgetId); - } - } - } - - @Override - public void onReceive(final Context context, final Intent intent) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "WidgetConversationProvider onReceive intent: " + intent); - } - final String action = intent.getAction(); - - // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here - // we're looking for an intent sent by our app when it knows a message has - // been sent or received (or a conversation has been read) and is telling the widget it - // needs to update. - if (getAction().equals(action)) { - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, - this.getClass())); - - if (appWidgetIds.length == 0) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "WidgetConversationProvider onReceive no widget ids"); - } - return; - } - // Normally the conversation id points to a specific conversation and we only update - // widgets looking at that conversation. When the conversation id is null, that means - // there's been a massive change (such as the initial import) and we need to update - // every conversation widget. - final String conversationId = intent.getExtras() - .getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - - // Only update the widgets that match the conversation id that changed. - for (final int widgetId : appWidgetIds) { - // Retrieve the persisted information for this widget from preferences. - final String widgetConvId = - WidgetPickConversationActivity.getConversationIdPref(widgetId); - if (conversationId == null || TextUtils.equals(conversationId, widgetConvId)) { - // Update the list portion (i.e. the message list) of the widget - appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, getListId()); - } - } - } else { - super.onReceive(context, intent); - } - } - - private static ConversationListItemData getConversationData(final Context context, - final String conversationId) { - if (TextUtils.isEmpty(conversationId)) { - return null; - } - final Uri uri = MessagingContentProvider.buildConversationMetadataUri(conversationId); - Cursor cursor = null; - try { - cursor = context.getContentResolver().query(uri, - ConversationListItemData.PROJECTION, - null, // selection - null, // selection args - null); // sort order - if (cursor != null && cursor.getCount() > 0) { - final ConversationListItemData conv = new ConversationListItemData(); - cursor.moveToFirst(); - conv.bind(cursor); - return conv; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - @Override - protected void deletePreferences(final int widgetId) { - WidgetPickConversationActivity.deleteConversationIdPref(widgetId); - } - - /** - * When this widget is created, it's created for a particular conversation and that - * ConversationId is stored in shared prefs. If the associated conversation is deleted, - * the widget doesn't get deleted. Instead, it goes into a "tap to configure" state. This - * function determines whether the widget has been configured and has an associated - * ConversationId. - */ - public static boolean isWidgetConfigured(final int appWidgetId) { - final String conversationId = - WidgetPickConversationActivity.getConversationIdPref(appWidgetId); - return !TextUtils.isEmpty(conversationId); - } - -} diff --git a/src/com/android/messaging/widget/WidgetConversationService.java b/src/com/android/messaging/widget/WidgetConversationService.java deleted file mode 100644 index 4fd3934..0000000 --- a/src/com/android/messaging/widget/WidgetConversationService.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.widget; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Bundle; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.text.style.ForegroundColorSpan; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.RemoteViewsService; - -import com.android.messaging.R; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.media.ImageResource; -import com.android.messaging.datamodel.media.MediaRequest; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor; -import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor; -import com.android.messaging.datamodel.media.UriImageRequestDescriptor; -import com.android.messaging.datamodel.media.VideoThumbnailRequest; -import com.android.messaging.sms.MmsUtils; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.AvatarUriUtil; -import com.android.messaging.util.Dates; -import com.android.messaging.util.LogUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import java.util.List; - -public class WidgetConversationService extends RemoteViewsService { - private static final String TAG = LogUtil.BUGLE_WIDGET_TAG; - - private static final int IMAGE_ATTACHMENT_SIZE = 400; - - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onGetViewFactory intent: " + intent); - } - return new WidgetConversationFactory(getApplicationContext(), intent); - } - - /** - * Remote Views Factory for the conversation widget. - */ - private static class WidgetConversationFactory extends BaseWidgetFactory { - private ImageResource mImageResource; - private String mConversationId; - - public WidgetConversationFactory(Context context, Intent intent) { - super(context, intent); - - mConversationId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "BugleFactory intent: " + intent + "widget id: " + mAppWidgetId); - } - mIconSize = (int) context.getResources() - .getDimension(R.dimen.contact_icon_view_normal_size); - } - - @Override - public void onCreate() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "onCreate"); - } - super.onCreate(); - - // If the conversation for this widget has been removed, we want to update the widget to - // "Tap to configure" mode. - if (!WidgetConversationProvider.isWidgetConfigured(mAppWidgetId)) { - WidgetConversationProvider.rebuildWidget(mContext, mAppWidgetId); - } - } - - @Override - protected Cursor doQuery() { - if (TextUtils.isEmpty(mConversationId)) { - LogUtil.w(TAG, "doQuery no conversation id"); - return null; - } - final Uri uri = MessagingContentProvider.buildConversationMessagesUri(mConversationId); - if (uri != null) { - LogUtil.w(TAG, "doQuery uri: " + uri.toString()); - } - return mContext.getContentResolver().query(uri, - ConversationMessageData.getProjection(), - null, // where - null, // selection args - null // sort order - ); - } - - /** - * @return the {@link RemoteViews} for a specific position in the list. - */ - @Override - public RemoteViews getViewAt(final int originalPosition) { - synchronized (sWidgetLock) { - // "View more messages" view. - if (mCursor == null - || (mShouldShowViewMore && originalPosition == 0)) { - return getViewMoreItemsView(); - } - // The message cursor is in reverse order for performance reasons. - final int position = getCount() - originalPosition - 1; - if (!mCursor.moveToPosition(position)) { - // If we ever fail to move to a position, return the "View More messages" - // view. - LogUtil.w(TAG, "Failed to move to position: " + position); - return getViewMoreItemsView(); - } - - final ConversationMessageData message = new ConversationMessageData(); - message.bind(mCursor); - - // Inflate and fill out the remote view - final RemoteViews remoteViews = new RemoteViews( - mContext.getPackageName(), message.getIsIncoming() ? - R.layout.widget_message_item_incoming : - R.layout.widget_message_item_outgoing); - - final boolean hasUnreadMessages = false; //!message.getIsRead(); - - // Date - remoteViews.setTextViewText(R.id.date, boldifyIfUnread( - Dates.getWidgetTimeString(message.getReceivedTimeStamp(), - false /*abbreviated*/), - hasUnreadMessages)); - - // On click intent. - final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext, - mConversationId, null /* draft */); - - // Attachments - int attachmentStringId = 0; - remoteViews.setViewVisibility(R.id.attachmentFrame, View.GONE); - - int scrollToPosition = originalPosition; - final int cursorCount = mCursor.getCount(); - if (cursorCount > MAX_ITEMS_TO_SHOW) { - scrollToPosition += cursorCount - MAX_ITEMS_TO_SHOW; - } - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewAt position: " + originalPosition + - " computed position: " + position + - " scrollToPosition: " + scrollToPosition + - " cursorCount: " + cursorCount + - " MAX_ITEMS_TO_SHOW: " + MAX_ITEMS_TO_SHOW); - } - - intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, scrollToPosition); - if (message.hasAttachments()) { - final List attachments = message.getAttachments(); - for (MessagePartData part : attachments) { - final boolean videoWithThumbnail = part.isVideo() - && (VideoThumbnailRequest.shouldShowIncomingVideoThumbnails() - || !message.getIsIncoming()); - if (part.isImage() || videoWithThumbnail) { - final Uri uri = part.getContentUri(); - remoteViews.setViewVisibility(R.id.attachmentFrame, View.VISIBLE); - remoteViews.setViewVisibility(R.id.playButton, part.isVideo() ? - View.VISIBLE : View.GONE); - remoteViews.setImageViewBitmap(R.id.attachment, - getAttachmentBitmap(part)); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI , - uri.toString()); - intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE , - part.getContentType()); - break; - } else if (part.isVideo()) { - attachmentStringId = R.string.conversation_list_snippet_video; - break; - } - if (part.isAudio()) { - attachmentStringId = R.string.conversation_list_snippet_audio_clip; - break; - } - if (part.isVCard()) { - attachmentStringId = R.string.conversation_list_snippet_vcard; - break; - } - } - } - - remoteViews.setOnClickFillInIntent(message.getIsIncoming() ? - R.id.widget_message_item_incoming : - R.id.widget_message_item_outgoing, - intent); - - // Avatar - boolean includeAvatar; - if (OsUtil.isAtLeastJB()) { - final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId); - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " + - options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY)); - } - - includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) - == BugleWidgetProvider.SIZE_LARGE; - } else { - includeAvatar = true; - } - - // Show the avatar (and shadow) when grande size, otherwise hide it. - remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ? - View.VISIBLE : View.GONE); - remoteViews.setViewVisibility(R.id.avatarShadow, includeAvatar ? - View.VISIBLE : View.GONE); - - final Uri avatarUri = AvatarUriUtil.createAvatarUri( - message.getSenderProfilePhotoUri(), - message.getSenderFullName(), - message.getSenderNormalizedDestination(), - message.getSenderContactLookupKey()); - - remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ? - getAvatarBitmap(avatarUri) : null); - - String text = message.getText(); - if (attachmentStringId != 0) { - final String attachment = mContext.getString(attachmentStringId); - if (!TextUtils.isEmpty(text)) { - text += '\n' + attachment; - } else { - text = attachment; - } - } - - remoteViews.setViewVisibility(R.id.message, View.VISIBLE); - updateViewContent(text, message, remoteViews); - - return remoteViews; - } - } - - // updateViewContent figures out what to show in the message and date fields based on - // the message status. This code came from ConversationMessageView.updateViewContent, but - // had to be simplified to work with our simple widget list item. - // updateViewContent also builds the accessibility content description for the list item. - private void updateViewContent(final String messageText, - final ConversationMessageData message, - final RemoteViews remoteViews) { - int titleResId = -1; - int statusResId = -1; - boolean showInRed = false; - String statusText = null; - switch(message.getStatus()) { - case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD: - case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD: - titleResId = R.string.message_title_downloading; - statusResId = R.string.message_status_downloading; - break; - - case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_manual_download; - statusResId = R.string.message_status_download; - } - break; - - case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_download_failed; - statusResId = R.string.message_status_download_error; - showInRed = true; - } - break; - - case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED: - if (!OsUtil.isSecondaryUser()) { - titleResId = R.string.message_title_download_failed; - statusResId = R.string.message_status_download; - showInRed = true; - } - break; - - case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND: - case MessageData.BUGLE_STATUS_OUTGOING_SENDING: - statusResId = R.string.message_status_sending; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_RESENDING: - case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY: - statusResId = R.string.message_status_send_retrying; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER: - statusResId = R.string.message_status_send_failed_emergency_number; - showInRed = true; - break; - - case MessageData.BUGLE_STATUS_OUTGOING_FAILED: - // don't show the error state unless we're the default sms app - if (PhoneUtils.getDefault().isDefaultSmsApp()) { - statusResId = MmsUtils.mapRawStatusToErrorResourceId( - message.getStatus(), message.getRawTelephonyStatus()); - showInRed = true; - break; - } - // FALL THROUGH HERE - - case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE: - case MessageData.BUGLE_STATUS_INCOMING_COMPLETE: - default: - if (!message.getCanClusterWithNextMessage()) { - statusText = Dates.getWidgetTimeString(message.getReceivedTimeStamp(), - false /*abbreviated*/).toString(); - } - break; - } - - // Build the content description while we're populating the various fields. - final StringBuilder description = new StringBuilder(); - final String separator = mContext.getString(R.string.enumeration_comma); - // Sender information - final boolean hasPlainTextMessage = !(TextUtils.isEmpty(message.getText())); - if (message.getIsIncoming()) { - int senderResId = hasPlainTextMessage - ? R.string.incoming_text_sender_content_description - : R.string.incoming_sender_content_description; - description.append(mContext.getString(senderResId, message.getSenderDisplayName())); - } else { - int senderResId = hasPlainTextMessage - ? R.string.outgoing_text_sender_content_description - : R.string.outgoing_sender_content_description; - description.append(mContext.getString(senderResId)); - } - - final boolean titleVisible = (titleResId >= 0); - if (titleVisible) { - final String titleText = mContext.getString(titleResId); - remoteViews.setTextViewText(R.id.message, titleText); - - final String mmsInfoText = mContext.getString( - R.string.mms_info, - Formatter.formatFileSize(mContext, message.getSmsMessageSize()), - DateUtils.formatDateTime( - mContext, - message.getMmsExpiry(), - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_NUMERIC_DATE | - DateUtils.FORMAT_NO_YEAR)); - remoteViews.setTextViewText(R.id.date, mmsInfoText); - description.append(separator); - description.append(mmsInfoText); - } else if (!TextUtils.isEmpty(messageText)) { - remoteViews.setTextViewText(R.id.message, messageText); - description.append(separator); - description.append(messageText); - } else { - remoteViews.setViewVisibility(R.id.message, View.GONE); - } - - final String subjectText = MmsUtils.cleanseMmsSubject(mContext.getResources(), - message.getMmsSubject()); - if (!TextUtils.isEmpty(subjectText)) { - description.append(separator); - description.append(subjectText); - } - - if (statusResId >= 0) { - statusText = mContext.getString(statusResId); - final Spannable colorStr = new SpannableString(statusText); - if (showInRed) { - colorStr.setSpan(new ForegroundColorSpan( - mContext.getResources().getColor(R.color.timestamp_text_failed)), - 0, statusText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - remoteViews.setTextViewText(R.id.date, colorStr); - description.append(separator); - description.append(colorStr); - } else { - description.append(separator); - description.append(Dates.getWidgetTimeString(message.getReceivedTimeStamp(), - false /*abbreviated*/)); - } - - if (message.hasAttachments()) { - final List attachments = message.getAttachments(); - int stringId; - for (MessagePartData part : attachments) { - if (part.isImage()) { - stringId = R.string.conversation_list_snippet_picture; - } else if (part.isVideo()) { - stringId = R.string.conversation_list_snippet_video; - } else if (part.isAudio()) { - stringId = R.string.conversation_list_snippet_audio_clip; - } else if (part.isVCard()) { - stringId = R.string.conversation_list_snippet_vcard; - } else { - stringId = 0; - } - if (stringId > 0) { - description.append(separator); - description.append(mContext.getString(stringId)); - } - } - } - remoteViews.setContentDescription(message.getIsIncoming() ? - R.id.widget_message_item_incoming : - R.id.widget_message_item_outgoing, description); - } - - private Bitmap getAttachmentBitmap(final MessagePartData part) { - UriImageRequestDescriptor descriptor; - if (part.isImage()) { - descriptor = new MessagePartImageRequestDescriptor(part, - IMAGE_ATTACHMENT_SIZE, // desiredWidth - IMAGE_ATTACHMENT_SIZE, // desiredHeight - true // isStatic - ); - } else if (part.isVideo()) { - descriptor = new MessagePartVideoThumbnailRequestDescriptor(part); - } else { - return null; - } - - final MediaRequest imageRequest = - descriptor.buildSyncMediaRequest(mContext); - final ImageResource imageResource = - MediaResourceManager.get().requestMediaResourceSync(imageRequest); - if (imageResource != null && imageResource.getBitmap() != null) { - setImageResource(imageResource); - return Bitmap.createBitmap(imageResource.getBitmap()); - } else { - releaseImageResource(); - return null; - } - } - - /** - * @return the "View more messages" view. When the user taps this item, they're - * taken to the conversation in Bugle. - */ - @Override - protected RemoteViews getViewMoreItemsView() { - if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { - LogUtil.v(TAG, "getViewMoreConversationsView"); - } - final RemoteViews view = new RemoteViews(mContext.getPackageName(), - R.layout.widget_loading); - view.setTextViewText( - R.id.loading_text, mContext.getText(R.string.view_more_messages)); - - // Tapping this "More messages" item should take us to the conversation. - final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext, - mConversationId, null /* draft */); - view.setOnClickFillInIntent(R.id.widget_loading, intent); - return view; - } - - @Override - public RemoteViews getLoadingView() { - final RemoteViews view = new RemoteViews(mContext.getPackageName(), - R.layout.widget_loading); - view.setTextViewText( - R.id.loading_text, mContext.getText(R.string.loading_messages)); - return view; - } - - @Override - public int getViewTypeCount() { - return 3; // Number of different list items that can be returned - - // 1- incoming list item - // 2- outgoing list item - // 3- more items list item - } - - @Override - protected int getMainLayoutId() { - return R.layout.widget_conversation; - } - - private void setImageResource(final ImageResource resource) { - if (mImageResource != resource) { - // Clear out any information for what is currently used - releaseImageResource(); - mImageResource = resource; - } - } - - private void releaseImageResource() { - if (mImageResource != null) { - mImageResource.release(); - } - mImageResource = null; - } - } - -} diff --git a/tests/Android.mk b/tests/Android.mk deleted file mode 100644 index f3f4752..0000000 --- a/tests/Android.mk +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -# Only compile source java files in this apk. -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_PACKAGE_NAME := messagingtests - -LOCAL_INSTRUMENTATION_FOR := messaging - -LOCAL_JACK_ENABLED := disabled - -# Matching ../Android.mk -LOCAL_SDK_VERSION := current - -LOCAL_CERTIFICATE := platform - -LOCAL_STATIC_JAVA_LIBRARIES := \ - mockito-target - -include $(BUILD_PACKAGE) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml deleted file mode 100644 index 867d8e1..0000000 --- a/tests/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - diff --git a/tests/src/com/android/messaging/BugleTestCase.java b/tests/src/com/android/messaging/BugleTestCase.java deleted file mode 100644 index 64c6c86..0000000 --- a/tests/src/com/android/messaging/BugleTestCase.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.Context; -import android.test.AndroidTestCase; - -/* - * Base class for service tests that takes care of housekeeping that is common amongst basic test - * cases. - */ -public abstract class BugleTestCase extends AndroidTestCase { - - static { - // Set flag during loading of test cases to prevent application initialization starting - setTestsRunning(); - } - - public static void setTestsRunning() { - BugleApplication.setTestsRunning(); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - TestUtil.testSetup(super.getContext(), this); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - TestUtil.testTeardown(this); - } - - @Override - public Context getContext() { - // This doesn't really get the "application context" - just the fake context - // that the factory has been initialized with for each test case. - return Factory.get().getApplicationContext(); - } - - public Context getTestContext() { - return super.getContext(); - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/FakeContentProvider.java b/tests/src/com/android/messaging/FakeContentProvider.java deleted file mode 100644 index 53c29ba..0000000 --- a/tests/src/com/android/messaging/FakeContentProvider.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.ContentProvider; -import android.content.ContentProviderClient; -import android.content.ContentValues; -import android.content.Context; -import android.content.pm.ProviderInfo; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.support.v4.util.SimpleArrayMap; -import android.text.TextUtils; - -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.util.LogUtil; - -import java.util.ArrayList; - -public class FakeContentProvider extends ContentProvider { - - private static class ContentOverride { - private final String uri; - private final String where; - private final String args; - private final String[] columns; - private final Object[][] data; - - ContentOverride(final String uri, final String where, final String args, - final String[] columns, final Object[][] data) { - this.uri = uri; - this.where = where; - this.args = args; - this.columns = columns; - this.data = data; - } - - boolean match(final String uri, final String where, final String[] args) { - if (!this.uri.equals(uri) || !TextUtils.equals(this.where, where)) { - return false; - } - - if (this.args == null || args == null) { - return this.args == null && args == null; - } - - return this.args.equals(TextUtils.join(";", args)); - } - } - - private final Context mGlobalContext; - private final ArrayList mOverrides = new ArrayList(); - private final SimpleArrayMap mTypes = new SimpleArrayMap(); - private final ContentProviderClient mProvider; - private final Uri mUri; - - public FakeContentProvider(final Context context, final Uri uri, final boolean canDelegate) { - mGlobalContext = context; - mUri = uri; - if (canDelegate) { - mProvider = mGlobalContext.getContentResolver().acquireContentProviderClient(mUri); - } else { - mProvider = null; - } - - ProviderInfo providerInfo = new ProviderInfo(); - providerInfo.authority = uri.getAuthority(); - - this.attachInfo(mGlobalContext, providerInfo); - } - - public void addOverrideData(final Uri uri, final String where, final String args, - final String[] columns, final Object[][] data) { - mOverrides.add(new ContentOverride(uri.toString(), where, args, columns, data)); - } - - public void addOverrideType(final Uri uri, final String type) { - mTypes.put(uri.toString(), type); - } - - @Override - public boolean onCreate() { - return false; - } - - @Override - public void shutdown() { - if (mProvider != null) { - mProvider.release(); - } - } - - @Override - public Cursor query(final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: query " + uri.toString() - + " for " + (projection == null ? null : TextUtils.join(",", projection)) - + " where " + selection - + " with " + (selectionArgs == null ? null : TextUtils.join(";", selectionArgs))); - - for(final ContentOverride content : mOverrides) { - if (content.match(uri.toString(), selection, selectionArgs)) { - return new FakeCursor(projection, content.columns, content.data); - } - } - if (mProvider != null) { - try { - LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: delgating"); - - final Cursor cursor = mProvider.query(uri, projection, selection, selectionArgs, - sortOrder); - - LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: response size " - + cursor.getCount() + " contains " + TextUtils.join(",", - cursor.getColumnNames()) + " type(0) " + cursor.getType(0)); - - return cursor; - } catch (final RemoteException e) { - e.printStackTrace(); - } - } - return null; - } - - @Override - public String getType(final Uri uri) { - String type = mTypes.get(uri.toString()); - if (type == null) { - try { - type = mProvider.getType(uri); - } catch (final RemoteException e) { - e.printStackTrace(); - } - } - return type; - } - - @Override - public Uri insert(final Uri uri, final ContentValues values) { - // TODO: Add code to track insert operations and return correct status - throw new UnsupportedOperationException(); - } - - @Override - public int delete(final Uri uri, final String selection, final String[] selectionArgs) { - // TODO: Add code to track delete operations and return correct status - throw new UnsupportedOperationException(); - } - - @Override - public int update(final Uri uri, final ContentValues values, final String selection, - final String[] selectionArgs) { - // TODO: Add code to track update operations and return correct status - throw new UnsupportedOperationException(); - } - - public Bundle call(final String callingPkg, final String method, final String arg, - final Bundle extras) { - return null; - } -} diff --git a/tests/src/com/android/messaging/FakeContext.java b/tests/src/com/android/messaging/FakeContext.java deleted file mode 100644 index 671e0f3..0000000 --- a/tests/src/com/android/messaging/FakeContext.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.ComponentName; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.test.RenamingDelegatingContext; -import android.test.mock.MockContentResolver; -import android.util.Log; - -import java.util.ArrayList; - -public class FakeContext extends RenamingDelegatingContext { - private static final String TAG = "FakeContext"; - - public interface FakeContextHost { - public String getServiceClassName(); - public void startServiceForStub(Intent intent); - public void onStartCommandForStub(Intent intent, int flags, int startid); - } - - ArrayList mStartedIntents; - boolean mServiceStarted = false; - private final FakeContextHost mService; - private final MockContentResolver mContentResolver; - - - public FakeContext(final Context context, final FakeContextHost service) { - super(context, "test_"); - mService = service; - mStartedIntents = new ArrayList(); - mContentResolver = new MockContentResolver(); - } - - public FakeContext(final Context context) { - this(context, null); - } - - public ArrayList extractIntents() { - final ArrayList intents = mStartedIntents; - mStartedIntents = new ArrayList(); - return intents; - } - - @Override - public ComponentName startService(final Intent intent) { - // Record that a startService occurred with the intent that was passed. - Log.d(TAG, "MockContext receiving startService. intent=" + intent.toString()); - mStartedIntents.add(intent); - if (mService == null) { - return super.startService(intent); - } else if (intent.getComponent() != null && - intent.getComponent().getClassName().equals(mService.getServiceClassName())) { - if (!mServiceStarted) { - Log.d(TAG, "MockContext first start service."); - mService.startServiceForStub(intent); - } else { - Log.d(TAG, "MockContext not first start service. Calling onStartCommand."); - mService.onStartCommandForStub(intent, 0, 0); - } - mServiceStarted = true; - return new ComponentName(this, intent.getComponent().getClassName()); - } - return null; - } - - @Override - public ContentResolver getContentResolver() { - // If you want to use a content provider in your test, then you need to add it - // explicitly. - return mContentResolver; - } - - public void addContentProvider(final String name, final ContentProvider provider) { - mContentResolver.addProvider(name, provider); - } - - public void addDefaultProvider(final Context context, final Uri uri) { - final FakeContentProvider provider = new FakeContentProvider(context, uri, true); - mContentResolver.addProvider(uri.getAuthority(), provider); - } - -} diff --git a/tests/src/com/android/messaging/FakeFactory.java b/tests/src/com/android/messaging/FakeFactory.java deleted file mode 100644 index 41ede77..0000000 --- a/tests/src/com/android/messaging/FakeFactory.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.content.ContentProvider; -import android.content.Context; -import android.net.Uri; -import android.provider.Settings; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; - -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver; -import com.android.messaging.datamodel.media.MediaCacheManager; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.sms.ApnDatabase; -import com.android.messaging.sms.BugleCarrierConfigValuesLoader; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.BuglePrefs; -import com.android.messaging.util.FakeBugleGservices; -import com.android.messaging.util.FakeBuglePrefs; -import com.android.messaging.util.MediaUtil; -import com.android.messaging.util.OsUtil; -import com.android.messaging.util.PhoneUtils; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.List; - -public class FakeFactory extends Factory { - private Context mContext; - private FakeContext mFakeContext; - private BugleGservices mBugleGservices; - private BuglePrefs mBuglePrefs; - private DataModel mDataModel; - private UIIntents mUIIntents; - private MemoryCacheManager mMemoryCacheManager; - private MediaResourceManager mMediaResourceManager; - private MediaCacheManager mMediaCacheManager; - @Mock protected PhoneUtils mPhoneUtils; - private MediaUtil mMediaUtil; - private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader; - - private FakeFactory() { - } - - public static FakeFactory registerWithFakeContext(final Context context, - final FakeContext fake) { - // In tests we currently NEVER run the application/factory initialization - Assert.isTrue(!sRegistered); - Assert.isTrue(!sInitialized); - - final FakeFactory factory = new FakeFactory(); - Factory.setInstance(factory); - - // At this point Factory is published. Services can now get initialized and depend on - // Factory.get(). - factory.mContext = context; - factory.mFakeContext = fake; - factory.mMediaResourceManager = Mockito.mock(MediaResourceManager.class); - factory.mBugleGservices = new FakeBugleGservices(); - factory.mBuglePrefs = new FakeBuglePrefs(); - factory.mPhoneUtils = Mockito.mock(PhoneUtils.class); - - ApnDatabase.initializeAppContext(context); - - Mockito.when(factory.mPhoneUtils.getCanonicalBySystemLocale(Matchers.anyString())) - .thenAnswer(new Answer() { - @Override - public String answer(final InvocationOnMock invocation) throws Throwable { - final Object[] args = invocation.getArguments(); - return (String) args[0]; - } - } - ); - Mockito.when(factory.mPhoneUtils.getCanonicalBySimLocale(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public String answer(final InvocationOnMock invocation) throws Throwable { - final Object[] args = invocation.getArguments(); - return (String) args[0]; - } - } - ); - Mockito.when(factory.mPhoneUtils.formatForDisplay(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public String answer(final InvocationOnMock invocation) throws Throwable { - return (String) invocation.getArguments()[0]; - } - } - ); - if (OsUtil.isAtLeastL_MR1()) { - Mockito.when(factory.mPhoneUtils.toLMr1()).thenReturn( - new PhoneUtils.LMr1() { - @Override - public SubscriptionInfo getActiveSubscriptionInfo() { - return null; - } - - @Override - public List getActiveSubscriptionInfoList() { - return null; - } - - @Override - public void registerOnSubscriptionsChangedListener( - final SubscriptionManager.OnSubscriptionsChangedListener listener) { - } - } - ); - } - // By default only allow reading of system settings (that we provide) - can delegate - // to real provider if required. - final FakeContentProvider settings = new FakeContentProvider(context, - Settings.System.CONTENT_URI, false); - settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "time_12_24", - new String[] { "value" }, new Object[][] { { "12" } }); - settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "sound_effects_enabled", - new String[] { "value" }, new Object[][] { { 1 } }); - - factory.withProvider(Settings.System.CONTENT_URI, settings); - - return factory; - } - - public static FakeFactory register(final Context applicationContext) { - final FakeContext context = new FakeContext(applicationContext); - return registerWithFakeContext(applicationContext, context); - } - - public static FakeFactory registerWithoutFakeContext(final Context applicationContext) { - return registerWithFakeContext(applicationContext, null); - } - - @Override - public void onRequiredPermissionsAcquired() { - } - - @Override - public Context getApplicationContext() { - return ((mFakeContext != null) ? mFakeContext : mContext ); - } - - @Override - public DataModel getDataModel() { - return mDataModel; - } - - @Override - public BugleGservices getBugleGservices() { - return mBugleGservices; - } - - @Override - public BuglePrefs getApplicationPrefs() { - return mBuglePrefs; - } - - @Override - public BuglePrefs getWidgetPrefs() { - return mBuglePrefs; - } - - @Override - public BuglePrefs getSubscriptionPrefs(final int subId) { - return mBuglePrefs; - } - - @Override - public UIIntents getUIIntents() { - return mUIIntents; - } - - @Override - public MemoryCacheManager getMemoryCacheManager() { - return mMemoryCacheManager; - } - - @Override - public MediaResourceManager getMediaResourceManager() { - return mMediaResourceManager; - } - - @Override - public MediaCacheManager getMediaCacheManager() { - return mMediaCacheManager; - } - - @Override - public PhoneUtils getPhoneUtils(final int subId) { - return mPhoneUtils; - } - - @Override - public MediaUtil getMediaUtil() { - return mMediaUtil; - } - - @Override - public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() { - return mCarrierConfigValuesLoader; - } - - @Override - public ContactContentObserver getContactContentObserver() { - return null; - } - - @Override - public void reclaimMemory() { - } - - @Override - public void onActivityResume() { - } - - public FakeFactory withDataModel(final DataModel dataModel) { - this.mDataModel = dataModel; - return this; - } - - public FakeFactory withUIIntents(final UIIntents uiIntents) { - this.mUIIntents = uiIntents; - return this; - } - - public FakeFactory withMemoryCacheManager(final MemoryCacheManager memoryCacheManager) { - this.mMemoryCacheManager = memoryCacheManager; - return this; - } - - public FakeFactory withBugleGservices(final BugleGservices bugleGservices) { - this.mBugleGservices = bugleGservices; - return this; - } - - public FakeFactory withMediaCacheManager(final MediaCacheManager mediaCacheManager) { - this.mMediaCacheManager = mediaCacheManager; - return this; - } - - public FakeFactory withProvider(final Uri uri, final ContentProvider provider) { - if (mFakeContext != null) { - mFakeContext.addContentProvider(uri.getAuthority(), provider); - } - return this; - } - - public FakeFactory withDefaultProvider(final Uri uri) { - if (mFakeContext != null) { - mFakeContext.addDefaultProvider(this.mContext, uri); - } - return this; - } - - public FakeFactory withMediaUtil(final MediaUtil mediaUtil) { - this.mMediaUtil = mediaUtil; - return this; - } - - public FakeFactory withCarrierConfigValuesLoader( - final BugleCarrierConfigValuesLoader carrierConfigValuesLoader) { - this.mCarrierConfigValuesLoader = carrierConfigValuesLoader; - return this; - } -} diff --git a/tests/src/com/android/messaging/TestUtil.java b/tests/src/com/android/messaging/TestUtil.java deleted file mode 100644 index 77a8450..0000000 --- a/tests/src/com/android/messaging/TestUtil.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging; - -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Context; -import android.os.PowerManager; -import android.test.InstrumentationTestCase; - -import com.android.messaging.util.LogUtil; -import com.android.messaging.widget.BugleWidgetProvider; -import com.android.messaging.widget.WidgetConversationProvider; - -import junit.framework.TestCase; - -import org.mockito.MockitoAnnotations; - -/** - * Helpers that can be called from all test base classes to 'reset' state and prevent as much as - * possible having side effects leak from one test to another. - */ -public class TestUtil { - public static void testSetup(final Context context, final TestCase testCase) { - haltIfTestsAreNotAbleToRun(context); - - // Workaround to get mockito to work. - // See https://code.google.com/p/dexmaker/issues/detail?id=2. TODO: Apparently - // solvable by using a different runner. - System.setProperty("dexmaker.dexcache", - context.getCacheDir().getPath()); - - // Initialize @Mock objects. - MockitoAnnotations.initMocks(testCase); - - // Tests have to explicitly override this - Factory.setInstance(null); - } - - public static void testTeardown(final TestCase testCase) { - if (testCase instanceof InstrumentationTestCase) { - // Make sure the test case is finished running or we'll get NPEs when accessing - // Fragment.get() - ((InstrumentationTestCase) testCase).getInstrumentation().waitForIdleSync(); - } - Factory.setInstance(null); - } - - private static void haltIfTestsAreNotAbleToRun(final Context context) { - final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - if (!pm.isScreenOn()) { - // Ideally we could turn it on for you using the WindowManager, but we currently run - // the tests independently of the activity life cycle. - LogUtil.wtf(LogUtil.BUGLE_TAG, "You need to turn on your screen to run tests!"); - } - - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - int [] conversationWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, - WidgetConversationProvider.class)); - int [] conversationListWidgetIds = appWidgetManager.getAppWidgetIds(new - ComponentName(context, BugleWidgetProvider.class)); - - if ((conversationWidgetIds.length > 0) || (conversationListWidgetIds.length > 0)) { - // Currently widgets asynchronously access our content providers and singletons which - // interacts badly with our test setup and tear down. - LogUtil.wtf(LogUtil.BUGLE_TAG, "You currently can't reliably run unit tests" + - " with a Messaging widget on your desktop!"); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/BindingTest.java b/tests/src/com/android/messaging/datamodel/BindingTest.java deleted file mode 100644 index 9205657..0000000 --- a/tests/src/com/android/messaging/datamodel/BindingTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.datamodel.binding.BindableData; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; - -/** - * Test binding - */ -@SmallTest -public class BindingTest extends BugleTestCase { - private static final Object TEST_DATA_ID = "myDataId"; - private static final Object YOUR_DATA_ID = "yourDataId"; - - public void testBindingStartsUnbound() { - final Binding binding = BindingBase.createBinding(this); - assertNull(binding.getBindingId()); - } - - public void testDataStartsUnbound() { - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - assertFalse(data.isBound()); - } - - public void testBindingUpdatesDataAndBindee() { - final Binding binding = BindingBase.createBinding(this); - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - binding.bind(data); - assertTrue(binding.isBound()); - assertEquals(binding.getData(), data); - assertTrue(data.isBound(binding.getBindingId())); - assertFalse(data.isBound("SomeRandomString")); - assertNotNull(binding.getBindingId()); - assertFalse(data.mListenersUnregistered); - } - - public void testRebindingFails() { - final Binding binding = BindingBase.createBinding(this); - final TestBindableData yours = new TestBindableData(YOUR_DATA_ID); - binding.bind(yours); - assertEquals(binding.getData(), yours); - assertTrue(yours.isBound(binding.getBindingId())); - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - try { - binding.bind(data); - fail(); - } catch (final IllegalStateException e) { - } - assertTrue(binding.isBound()); - assertEquals(binding.getData(), yours); - assertTrue(yours.isBound(binding.getBindingId())); - } - - public void testUnbindingClearsDataAndBindee() { - final Binding binding = BindingBase.createBinding(this); - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - binding.bind(data); - assertTrue(data.isBound(binding.getBindingId())); - assertTrue(binding.isBound()); - binding.unbind(); - try { - final TestBindableData other = binding.getData(); - fail(); - } catch (final IllegalStateException e) { - } - assertFalse(data.isBound()); - assertNull(binding.getBindingId()); - assertTrue(data.mListenersUnregistered); - } - - public void testUnbindingAndRebinding() { - final Binding binding = BindingBase.createBinding(this); - final TestBindableData yours = new TestBindableData(YOUR_DATA_ID); - binding.bind(yours); - assertEquals(binding.getData(), yours); - assertTrue(yours.isBound(binding.getBindingId())); - binding.unbind(); - assertFalse(yours.isBound()); - assertNull(binding.getBindingId()); - - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - binding.bind(data); - assertEquals(binding.getData(), data); - assertTrue(data.isBound(binding.getBindingId())); - assertFalse(data.isBound("SomeRandomString")); - assertTrue(binding.isBound()); - assertNotNull(binding.getBindingId()); - } - - public void testBindingReference() { - final Binding binding = BindingBase.createBinding(this); - final TestBindableData data = new TestBindableData(TEST_DATA_ID); - binding.bind(data); - assertEquals(binding.getData(), data); - assertTrue(data.isBound(binding.getBindingId())); - - final ImmutableBindingRef bindingRef = - BindingBase.createBindingReference(binding); - assertEquals(bindingRef.getData(), data); - assertTrue(data.isBound(bindingRef.getBindingId())); - - binding.unbind(); - assertFalse(binding.isBound()); - assertNull(binding.getBindingId()); - assertFalse(bindingRef.isBound()); - assertNull(bindingRef.getBindingId()); - } - - static class TestBindableData extends BindableData { - private final Object mDataId; - public boolean mListenersUnregistered; - - public TestBindableData(final Object dataId) { - mDataId = dataId; - mListenersUnregistered = false; - } - - @Override - public void unregisterListeners() { - mListenersUnregistered = true; - } - - @Override - public boolean isBound() { - return super.isBound(); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java deleted file mode 100644 index 6d0aaa3..0000000 --- a/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.R; - -import org.mockito.Mock; - -import java.util.HashSet; -import java.util.Set; - -@SmallTest -public class BitmapPoolTest extends BugleTestCase { - private static final int POOL_SIZE = 5; - private static final int IMAGE_DIM = 1; - private static final String NAME = "BitmapPoolTest"; - - @Mock private MemoryCacheManager mockMemoryCacheManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()) - .withMemoryCacheManager(mockMemoryCacheManager); - } - - private Set fillPoolAndGetPoolContents(final BitmapPool pool, final int width, - final int height) { - final Set returnedBitmaps = new HashSet(); - for (int i = 0; i < POOL_SIZE; i++) { - final Bitmap temp = pool.createOrReuseBitmap(width, height); - assertFalse(returnedBitmaps.contains(temp)); - returnedBitmaps.add(temp); - } - for (final Bitmap b : returnedBitmaps) { - pool.reclaimBitmap(b); - } - assertTrue(pool.isFull(width, height)); - return returnedBitmaps; - } - - public void testCreateAndPutBackInPoolTest() { - final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); - final Bitmap bitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM); - assertFalse(bitmap.isRecycled()); - assertFalse(pool.isFull(IMAGE_DIM, IMAGE_DIM)); - pool.reclaimBitmap(bitmap); - - // Don't recycle because the pool isn't full yet. - assertFalse(bitmap.isRecycled()); - } - - public void testCreateBeyondFullAndCheckReuseTest() { - final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); - final Set returnedBitmaps = - fillPoolAndGetPoolContents(pool, IMAGE_DIM, IMAGE_DIM); - final Bitmap overflowBitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM); - assertFalse(overflowBitmap.isRecycled()); - assertTrue(returnedBitmaps.contains(overflowBitmap)); - } - - /** - * Make sure that we have the correct options to create mutable for bitmap pool reuse. - */ - public void testAssertBitmapOptionsAreMutable() { - final BitmapFactory.Options options = - BitmapPool.getBitmapOptionsForPool(false, IMAGE_DIM, IMAGE_DIM); - assertTrue(options.inMutable); - } - - public void testDecodeFromResourceBitmap() { - final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME); - final BitmapFactory.Options options = - BitmapPool.getBitmapOptionsForPool(true, IMAGE_DIM, IMAGE_DIM); - final Resources resources = getContext().getResources(); - final Bitmap resourceBitmap = pool.decodeSampledBitmapFromResource( - R.drawable.msg_bubble_incoming, resources, options, IMAGE_DIM, IMAGE_DIM); - assertNotNull(resourceBitmap); - assertTrue(resourceBitmap.getByteCount() > 0); - } -} diff --git a/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java deleted file mode 100644 index 42eb647..0000000 --- a/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.app.Service; -import android.test.ServiceTestCase; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.TestUtil; - - -/* - * Base class for service tests that takes care of housekeeping that is commong amongst our service - * test case. - */ -public abstract class BugleServiceTestCase extends ServiceTestCase { - - static { - // Set flag during loading of test cases to prevent application initialization starting - BugleTestCase.setTestsRunning(); - } - - public BugleServiceTestCase(final Class serviceClass) { - super(serviceClass); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - TestUtil.testSetup(getContext(), this); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - TestUtil.testTeardown(this); - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/datamodel/ConversationListTest.java b/tests/src/com/android/messaging/datamodel/ConversationListTest.java deleted file mode 100644 index f45696b..0000000 --- a/tests/src/com/android/messaging/datamodel/ConversationListTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; - -@SmallTest -public class ConversationListTest extends BugleTestCase { - public void testTesting() { - assertTrue(true); - } -} diff --git a/tests/src/com/android/messaging/datamodel/DataModelTest.java b/tests/src/com/android/messaging/datamodel/DataModelTest.java deleted file mode 100644 index 71723a4..0000000 --- a/tests/src/com/android/messaging/datamodel/DataModelTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; - -import org.mockito.Mock; - -public class DataModelTest extends BugleTestCase { - - DataModel dataModel; - @Mock protected ConversationDataListener mockConversationDataListener; - @Mock protected ConversationListDataListener mockConversationListDataListener; - - @Override - protected void setUp() throws Exception { - super.setUp(); - dataModel = new DataModelImpl(getTestContext()); - FakeFactory.register(mContext) - .withDataModel(dataModel); - } - - @SmallTest - public void testCreateConversationList() { - final ConversationListData list = dataModel.createConversationListData(getContext(), - mockConversationListDataListener, true); - assertTrue(list instanceof ConversationListData); - final ConversationData conv = dataModel.createConversationData(getContext(), - mockConversationDataListener, "testConversation"); - assertTrue(conv instanceof ConversationData); - } - - private static final String FOCUSED_CONV_ID = "focused_conv_id"; - - @SmallTest - public void testFocusedConversationIsObservable() { - dataModel.setFocusedConversation(FOCUSED_CONV_ID); - assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); - dataModel.setFocusedConversation(null); - assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); - } - - @SmallTest - public void testConversationIsObservableInList() { - dataModel.setConversationListScrolledToNewestConversation(true); - assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); - dataModel.setConversationListScrolledToNewestConversation(false); - assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID)); - } -} diff --git a/tests/src/com/android/messaging/datamodel/FakeCursor.java b/tests/src/com/android/messaging/datamodel/FakeCursor.java deleted file mode 100644 index 59d1b89..0000000 --- a/tests/src/com/android/messaging/datamodel/FakeCursor.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.os.Bundle; -import android.test.mock.MockCursor; - -import java.util.ArrayList; - -/** - * A simple in memory fake cursor that can be used for UI tests. - */ -public class FakeCursor extends MockCursor { - private final ArrayList mProjection; - private final String[] mColumnNamesOfData; - private final Object[][] mData; - private int mIndex; - - public FakeCursor(final String[] projection, final String[] columnNames, - final Object[][] data) { - mColumnNamesOfData = columnNames; - mData = data; - mIndex = -1; - mProjection = new ArrayList(projection.length); - for (final String column : projection) { - mProjection.add(getColumnIndex(column)); - } - } - - public Object getAt(final String columnName, final int row) { - final int dataIdx = getColumnIndex(columnName); - return (dataIdx < 0 || row < 0 || row >= mData.length) ? 0 : mData[row][dataIdx]; - } - - @Override - public int getCount() { - return mData.length; - } - - @Override - public boolean isFirst() { - return mIndex == 0; - } - - @Override - public boolean isLast() { - return mIndex == mData.length - 1; - } - - @Override - public boolean moveToFirst() { - if (mData.length == 0) { - return false; - } - mIndex = 0; - return true; - } - - @Override - public boolean moveToPosition(final int position) { - if (position < 0 || position >= mData.length) { - return false; - } - mIndex = position; - return true; - } - - @Override - public int getPosition() { - return mIndex; - } - - @Override - public boolean moveToPrevious() { - if (mIndex <= 0) { - return false; - } - mIndex--; - return true; - } - - @Override - public boolean moveToNext() { - if (mIndex == mData.length - 1) { - return false; - } - - mIndex++; - return true; - } - - @Override - public int getColumnCount() { - return mColumnNamesOfData.length; - } - - @Override - public int getColumnIndex(final String columnName) { - for (int i = 0 ; i < mColumnNamesOfData.length ; i++) { - if (mColumnNamesOfData[i].equals(columnName)) { - return i; - } - } - return -1; - } - - @Override - public int getColumnIndexOrThrow(final String columnName) { - final int result = getColumnIndex(columnName); - if (result == -1) { - throw new IllegalArgumentException(); - } - - return result; - } - - @Override - public String getString(final int columnIndex) { - final int dataIdx = mProjection.get(columnIndex); - final Object obj = (dataIdx < 0 ? null : mData[mIndex][dataIdx]); - return (obj == null ? null : obj.toString()); - } - - @Override - public int getInt(final int columnIndex) { - final int dataIdx = mProjection.get(columnIndex); - return (dataIdx < 0 ? 0 : (Integer) mData[mIndex][dataIdx]); - } - - @Override - public long getLong(final int columnIndex) { - final int dataIdx = mProjection.get(columnIndex); - return (dataIdx < 0 ? 0 : (Long) mData[mIndex][dataIdx]); - } - - @Override - public void close() { - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public Bundle getExtras() { return null; } -} diff --git a/tests/src/com/android/messaging/datamodel/FakeDataModel.java b/tests/src/com/android/messaging/datamodel/FakeDataModel.java deleted file mode 100644 index 5e80eab..0000000 --- a/tests/src/com/android/messaging/datamodel/FakeDataModel.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.test.RenamingDelegatingContext; - -import com.android.messaging.datamodel.action.ActionService; -import com.android.messaging.datamodel.action.BackgroundWorker; -import com.android.messaging.datamodel.data.BlockedParticipantsData; -import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.ContactPickerData; -import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.LaunchConversationData; -import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantListItemData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData; -import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener; -import com.android.messaging.datamodel.data.PeopleOptionsItemData; -import com.android.messaging.datamodel.data.SettingsData; -import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.datamodel.data.VCardContactItemData; -import com.android.messaging.util.ConnectivityUtil; - -public class FakeDataModel extends DataModel { - private BackgroundWorker mWorker; - private ActionService mActionService; - private final DatabaseHelper mDatabaseHelper; - private ConversationListData mConversationListData; - private ContactPickerData mContactPickerData; - private MediaPickerData mMediaPickerData; - private PeopleAndOptionsData mPeopleAndOptionsData; - private ConnectivityUtil mConnectivityUtil; - private SyncManager mSyncManager; - private SettingsData mSettingsData; - private DraftMessageData mDraftMessageData; - - public FakeDataModel(final Context context) { - super(); - if (context instanceof RenamingDelegatingContext) { - mDatabaseHelper = DatabaseHelper.getNewInstanceForTest(context); - } else { - mDatabaseHelper = null; - } - } - - @Override - public BackgroundWorker getBackgroundWorkerForActionService() { - return mWorker; - } - - public FakeDataModel withBackgroundWorkerForActionService(final BackgroundWorker worker) { - mWorker = worker; - return this; - } - - public FakeDataModel withActionService(final ActionService ActionService) { - mActionService = ActionService; - return this; - } - - public FakeDataModel withConversationListData(final ConversationListData conversationListData) { - mConversationListData = conversationListData; - return this; - } - - public FakeDataModel withContactPickerData(final ContactPickerData contactPickerData) { - mContactPickerData = contactPickerData; - return this; - } - - public FakeDataModel withMediaPickerData(final MediaPickerData mediaPickerData) { - mMediaPickerData = mediaPickerData; - return this; - } - - public FakeDataModel withConnectivityUtil(final ConnectivityUtil connectivityUtil) { - mConnectivityUtil = connectivityUtil; - return this; - } - - public FakeDataModel withSyncManager(final SyncManager syncManager) { - mSyncManager = syncManager; - return this; - } - - public FakeDataModel withPeopleAndOptionsData(final PeopleAndOptionsData peopleAndOptionsData) { - mPeopleAndOptionsData = peopleAndOptionsData; - return this; - } - - public FakeDataModel withSettingsData(final SettingsData settingsData) { - mSettingsData = settingsData; - return this; - } - - public FakeDataModel withDraftMessageData(final DraftMessageData draftMessageData) { - mDraftMessageData = draftMessageData; - return this; - } - - @Override - public ConversationListData createConversationListData(final Context context, - final ConversationListDataListener listener, final boolean archivedMode) { - return mConversationListData; - } - - @Override - public ConversationData createConversationData(final Context context, - final ConversationDataListener listener, final String conversationId) { - throw new IllegalStateException("Add withXXX or mock this method"); - } - - @Override - public ContactListItemData createContactListItemData() { - // This is a lightweight data holder object for each individual list item for which - // we don't perform any data request, so we can directly return a new instance. - return new ContactListItemData(); - } - - @Override - public ContactPickerData createContactPickerData(final Context context, - final ContactPickerDataListener listener) { - return mContactPickerData; - } - - @Override - public MediaPickerData createMediaPickerData(final Context context) { - return mMediaPickerData; - } - - @Override - public GalleryGridItemData createGalleryGridItemData() { - // This is a lightweight data holder object for each individual grid item for which - // we don't perform any data request, so we can directly return a new instance. - return new GalleryGridItemData(); - } - - @Override - public LaunchConversationData createLaunchConversationData( - final LaunchConversationDataListener listener) { - return new LaunchConversationData(listener); - } - - @Override - public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) { - return new PeopleOptionsItemData(context); - } - - @Override - public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId, - final Context context, final PeopleAndOptionsDataListener listener) { - return mPeopleAndOptionsData; - } - - @Override - public VCardContactItemData createVCardContactItemData(final Context context, - final MessagePartData data) { - return new VCardContactItemData(context, data); - } - - @Override - public VCardContactItemData createVCardContactItemData(final Context context, - final Uri vCardUri) { - return new VCardContactItemData(context, vCardUri); - } - - @Override - public ParticipantListItemData createParticipantListItemData( - final ParticipantData participant) { - return new ParticipantListItemData(participant); - } - - @Override - public SubscriptionListData createSubscriptonListData(Context context) { - return new SubscriptionListData(context); - } - - @Override - public SettingsData createSettingsData(Context context, SettingsDataListener listener) { - return mSettingsData; - } - - @Override - public DraftMessageData createDraftMessageData(String conversationId) { - return mDraftMessageData; - } - - @Override - public ActionService getActionService() { - return mActionService; - } - - @Override - public ConnectivityUtil getConnectivityUtil() { - return mConnectivityUtil; - } - - @Override - public SyncManager getSyncManager() { - return mSyncManager; - } - - @Override - public DatabaseWrapper getDatabase() { - // Note this will crash unless the application context is redirected... - // This is by design so that tests do not inadvertently use the real database - return mDatabaseHelper.getDatabase(); - } - - @Override - void onCreateTables(final SQLiteDatabase db) { - TestDataFactory.createTestData(db); - } - - @Override - public void onActivityResume() { - } - - @Override - public void onApplicationCreated() { - } - - @Override - public BlockedParticipantsData createBlockedParticipantsData(Context context, - BlockedParticipantsDataListener listener) { - return new BlockedParticipantsData(context, listener); - } -} diff --git a/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java deleted file mode 100644 index 6b78a07..0000000 --- a/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel; - -import android.database.Cursor; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.util.ContactUtil; - -@SmallTest -public class FrequentContactsCursorBuilderTest extends BugleTestCase { - - private void verifyBuiltCursor(final Cursor expected, final Cursor actual) { - final int rowCount = expected.getCount(); - final int columnCount = expected.getColumnCount(); - assertEquals(rowCount, actual.getCount()); - assertEquals(columnCount, actual.getColumnCount()); - for (int i = 0; i < rowCount; i++) { - expected.moveToPosition(i); - actual.moveToPosition(i); - assertEquals(expected.getLong(ContactUtil.INDEX_DATA_ID), - actual.getLong(ContactUtil.INDEX_DATA_ID)); - assertEquals(expected.getLong(ContactUtil.INDEX_CONTACT_ID), - actual.getLong(ContactUtil.INDEX_CONTACT_ID)); - assertEquals(expected.getString(ContactUtil.INDEX_LOOKUP_KEY), - actual.getString(ContactUtil.INDEX_LOOKUP_KEY)); - assertEquals(expected.getString(ContactUtil.INDEX_DISPLAY_NAME), - actual.getString(ContactUtil.INDEX_DISPLAY_NAME)); - assertEquals(expected.getString(ContactUtil.INDEX_PHOTO_URI), - actual.getString(ContactUtil.INDEX_PHOTO_URI)); - assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL), - actual.getString(ContactUtil.INDEX_PHONE_EMAIL)); - assertEquals(expected.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE), - actual.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE)); - assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL), - actual.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL)); - } - } - - public void testIncompleteBuild() { - final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); - assertNull(builder.build()); - assertNull(builder.setFrequents(TestDataFactory.getStrequentContactsCursor()).build()); - builder.resetBuilder(); - assertNull(builder.build()); - assertNull(builder.setAllContacts(TestDataFactory.getAllContactListCursor()).build()); - } - - public void testBuildOnce() { - final Cursor cursor = new FrequentContactsCursorBuilder() - .setAllContacts(TestDataFactory.getAllContactListCursor()) - .setFrequents(TestDataFactory.getStrequentContactsCursor()) - .build(); - assertNotNull(cursor); - verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), cursor); - } - - public void testBuildTwice() { - final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder(); - final Cursor firstCursor = builder - .setAllContacts(TestDataFactory.getAllContactListCursor()) - .setFrequents(TestDataFactory.getStrequentContactsCursor()) - .build(); - assertNotNull(firstCursor); - builder.resetBuilder(); - assertNull(builder.build()); - - final Cursor secondCursor = builder - .setAllContacts(TestDataFactory.getAllContactListCursor()) - .setFrequents(TestDataFactory.getStrequentContactsCursor()) - .build(); - assertNotNull(firstCursor); - verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), secondCursor); - } -} diff --git a/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java deleted file mode 100644 index 5da9e27..0000000 --- a/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache; - -import org.mockito.Mockito; - -@SmallTest -public class MemoryCacheManagerTest extends AndroidTestCase { - - public void testRegisterCachesGetReclaimed() { - final MemoryCache mockMemoryCache = Mockito.mock(MemoryCache.class); - final MemoryCache otherMockMemoryCache = Mockito.mock(MemoryCache.class); - final MemoryCacheManager memoryCacheManager = new MemoryCacheManager(); - - memoryCacheManager.registerMemoryCache(mockMemoryCache); - memoryCacheManager.registerMemoryCache(otherMockMemoryCache); - memoryCacheManager.reclaimMemory(); - memoryCacheManager.unregisterMemoryCache(otherMockMemoryCache); - memoryCacheManager.reclaimMemory(); - - Mockito.verify(mockMemoryCache, Mockito.times(2)).reclaim(); - Mockito.verify(otherMockMemoryCache, Mockito.times(1)).reclaim(); - } -} diff --git a/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java deleted file mode 100644 index cd1d6c7..0000000 --- a/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.test.suitebuilder.annotation.SmallTest; -import android.text.TextUtils; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeContentProvider; -import com.android.messaging.FakeContext; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery; -import com.android.messaging.util.ContactUtil; - -import org.junit.Assert; - -/** - * Utility class for testing ParticipantRefresh class for different scenarios. - */ -@SmallTest -public class ParticipantRefreshTest extends BugleTestCase { - private FakeContext mContext; - FakeFactory mFakeFactory; - - @Override - public void setUp() throws Exception { - super.setUp(); - - mContext = new FakeContext(getTestContext()); - - final ContentProvider provider = new MessagingContentProvider(); - provider.attachInfo(mContext, null); - mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); - - final FakeDataModel fakeDataModel = new FakeDataModel(mContext); - mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext) - .withDataModel(fakeDataModel); - } - - /** - * Add some phonelookup result into take PhoneLookup content provider. This will be - * used for doing phone lookup during participant refresh. - */ - private void addPhoneLookup(final String phone, final Object[][] lookupResult) { - final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri(); - final FakeContentProvider phoneLookup = new FakeContentProvider(mContext, - uri, false); - phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION, - lookupResult); - mFakeFactory.withProvider(uri, phoneLookup); - } - - /** - * Add some participant to test database. - */ - private void addParticipant(final String normalizedDestination, final long contactId, - final String name, final String photoUrl) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - final ContentValues values = new ContentValues(); - - values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination); - values.put(ParticipantColumns.CONTACT_ID, contactId); - values.put(ParticipantColumns.FULL_NAME, name); - values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl); - - db.beginTransaction(); - try { - db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Verify that participant in the database has expected contacdtId, name and photoUrl fields. - */ - private void verifyParticipant(final String normalizedDestination, final long contactId, - final String name, final String photoUrl) { - final DatabaseWrapper db = DataModel.get().getDatabase(); - db.beginTransaction(); - try { - final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?"; - final String[] selectionArgs = new String[] { normalizedDestination }; - - final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE, - ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null); - - if (cursor == null || cursor.getCount() != 1) { - Assert.fail("Should have participants for:" + normalizedDestination); - return; - } - - cursor.moveToFirst(); - final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID); - final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME); - final String currentPhotoUrl = - cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI); - if (currentContactId != contactId) { - Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + - " expected=" + contactId + " actual=" + currentContactId); - return; - } - - if (!TextUtils.equals(currentName, name)) { - Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination + - " expected=" + name + " actual=" + currentName); - return; - } - - if (!TextUtils.equals(currentPhotoUrl, photoUrl)) { - Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination + - " expected=" + photoUrl + " actual=" + currentPhotoUrl); - return; - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - /** - * Verify that incremental refresh will resolve previously not resolved participants. - */ - public void testIncrementalRefreshNotResolvedSingleMatch() { - addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, - null, null); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that incremental refresh will resolve previously not resolved participants. - */ - public void testIncrementalRefreshNotResolvedMultiMatch() { - addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED, - null, null); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, - { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that incremental refresh will not touch already-resolved participants. - */ - public void testIncrementalRefreshResolvedSingleMatch() { - addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); - verifyParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); - } - - /** - * Verify that full refresh will correct already-resolved participants if needed - */ - public void testFullRefreshResolvedSingleMatch() { - addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that incremental refresh will not touch participant that is marked as not found. - */ - public void testIncrementalRefreshNotFound() { - addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, - null, null); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL); - verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, - null, null); - } - - /** - * Verify that full refresh will resolve participant that is marked as not found. - */ - public void testFullRefreshNotFound() { - addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, - null, null); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that refresh take consideration of current contact_id when having multiple matches. - */ - public void testFullRefreshResolvedMultiMatch1() { - addParticipant("650-123-1233", 1, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, - { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that refresh take consideration of current contact_id when having multiple matches. - */ - public void testFullRefreshResolvedMultiMatch2() { - addParticipant("650-123-1233", 2, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, - { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", 2, "Joe", "content://photo/joe"); - } - - /** - * Verify that refresh take first contact in case current contact_id no longer matches. - */ - public void testFullRefreshResolvedMultiMatch3() { - addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] { - { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }, - { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null } - }); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", 1, "John", "content://photo/john"); - } - - /** - * Verify that refresh take first contact in case current contact_id no longer matches. - */ - public void testFullRefreshResolvedBeforeButNotFoundNow() { - addParticipant("650-123-1233", 3, "Joh", "content://photo/joh"); - addPhoneLookup("650-123-1233", new Object[][] {}); - - ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL); - verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND, - null, null); - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java deleted file mode 100644 index 039bec9..0000000 --- a/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.Factory; -import com.android.messaging.FakeContext; -import com.android.messaging.FakeContext.FakeContextHost; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.BugleServiceTestCase; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; -import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener; -import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; - -import java.util.ArrayList; - -@MediumTest -public class ActionServiceSystemTest extends BugleServiceTestCase - implements ActionCompletedListener, ActionExecutedListener, FakeContextHost { - private static final String TAG = "ActionServiceSystemTest"; - - static { - // Set flag during loading of test cases to prevent application initialization starting - BugleTestCase.setTestsRunning(); - } - - @Override - public void onActionSucceeded(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - final TestChatAction test = (TestChatAction) action; - assertEquals("Expect correct action parameter", parameter, test.parameter); - final ResultTracker tracker = (ResultTracker) data; - tracker.completionResult = result; - synchronized(tracker) { - tracker.notifyAll(); - } - } - - @Override - public void onActionFailed(final ActionMonitor monitor, final Action action, - final Object data, final Object result) { - final TestChatAction test = (TestChatAction) action; - assertEquals("Expect correct action parameter", parameter, test.parameter); - final ResultTracker tracker = (ResultTracker) data; - tracker.completionResult = result; - synchronized(tracker) { - tracker.notifyAll(); - } - } - - @Override - public void onActionExecuted(final ActionMonitor monitor, final Action action, - final Object data, final Object result) { - final TestChatAction test = (TestChatAction) action; - assertEquals("Expect correct action parameter", parameter, test.parameter); - final ResultTracker tracker = (ResultTracker) data; - tracker.executionResult = result; - } - - public ActionServiceSystemTest() { - super(ActionServiceImpl.class); - } - - public void testChatActionSucceeds() { - final ResultTracker tracker = new ResultTracker(); - - final ActionService service = DataModel.get().getActionService(); - final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); - final TestChatAction initial = new TestChatAction(monitor.getActionKey(), parameter); - - assertNull("Expect completion result to start null", tracker.completionResult); - assertNull("Expect execution result to start null", tracker.executionResult); - - final Parcel parcel = Parcel.obtain(); - parcel.writeParcelable(initial, 0); - parcel.setDataPosition(0); - final TestChatAction action = parcel.readParcelable(mContext.getClassLoader()); - - synchronized(mWorker) { - try { - action.start(monitor); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for execution", false); - } - } - - assertEquals("Expect to see 1 server request queued", 1, - mWorker.getRequestsMade().size()); - final Action request = mWorker.getRequestsMade().get(0); - assertTrue("Expect Test type", request instanceof TestChatAction); - - final Bundle response = new Bundle(); - response.putString(TestChatAction.RESPONSE_TEST, processResponseResult); - synchronized(tracker) { - try { - request.markBackgroundWorkStarting(); - request.markBackgroundWorkQueued(); - - request.markBackgroundWorkStarting(); - request.markBackgroundCompletionQueued(); - service.handleResponseFromBackgroundWorker(request, response); - // Wait for callback across threads - tracker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for response processing", false); - } - } - - // TODO - //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); - assertEquals("Expect completion result set", processResponseResult, - tracker.completionResult); - } - - public void testChatActionFails() { - final ResultTracker tracker = new ResultTracker(); - - final ActionService service = DataModel.get().getActionService(); - final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); - final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); - - assertNull("Expect completion result to start null", tracker.completionResult); - assertNull("Expect execution result to start null", tracker.executionResult); - - synchronized(mWorker) { - try { - action.start(monitor); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for requests", false); - } - } - - final ArrayList intents = mContext.extractIntents(); - assertNotNull(intents); - assertEquals("Expect to see one intent", intents.size(), 1); - - assertEquals("Expect to see 1 server request queued", 1, - mWorker.getRequestsMade().size()); - final Action request = mWorker.getRequestsMade().get(0); - assertTrue("Expect Test type", request instanceof TestChatAction); - - synchronized(tracker) { - try { - request.markBackgroundWorkStarting(); - request.markBackgroundWorkQueued(); - - request.markBackgroundWorkStarting(); - request.markBackgroundCompletionQueued(); - service.handleFailureFromBackgroundWorker(request, new Exception("It went wrong")); - // Wait for callback across threads - tracker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for response processing", false); - } - } - - // TODO - //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); - assertEquals("Expect completion result set", processFailureResult, - tracker.completionResult); - } - - public void testChatActionNoMonitor() { - final ActionService service = DataModel.get().getActionService(); - final TestChatAction action = - new TestChatAction(Action.generateUniqueActionKey(null), parameter); - - synchronized(mWorker) { - try { - action.start(); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for execution", false); - } - } - - assertEquals("Expect to see 1 server request queued", 1, - mWorker.getRequestsMade().size()); - Action request = mWorker.getRequestsMade().get(0); - assertTrue("Expect Test type", request instanceof TestChatAction); - - final Bundle response = new Bundle(); - response.putString(TestChatAction.RESPONSE_TEST, processResponseResult); - synchronized(mWorker) { - try { - service.handleResponseFromBackgroundWorker(request, response); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for response processing", false); - } - } - - assertEquals("Expect to see second server request queued", - 2, mWorker.getRequestsMade().size()); - request = mWorker.getRequestsMade().get(1); - assertTrue("Expect other type", - request instanceof TestChatActionOther); - } - - public void testChatActionUnregisterListener() { - final ResultTracker tracker = new ResultTracker(); - - final ActionService service = DataModel.get().getActionService(); - final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); - final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); - - assertNull("Expect completion result to start null", tracker.completionResult); - assertNull("Expect execution result to start null", tracker.executionResult); - - synchronized(mWorker) { - try { - action.start(monitor); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for execution", false); - } - } - - assertEquals("Expect to see 1 server request queued", 1, - mWorker.getRequestsMade().size()); - final Action request = mWorker.getRequestsMade().get(0); - assertTrue("Expect Test type", request instanceof TestChatAction); - - monitor.unregister(); - - final Bundle response = new Bundle(); - synchronized(mWorker) { - try { - request.markBackgroundWorkStarting(); - request.markBackgroundWorkQueued(); - - request.markBackgroundWorkStarting(); - request.markBackgroundCompletionQueued(); - service.handleResponseFromBackgroundWorker(request, response); - // Wait for callback across threads - mWorker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for response processing", false); - } - } - - //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult); - assertEquals("Expect completion never called", null, tracker.completionResult); - } - - StubBackgroundWorker mWorker; - FakeContext mContext; - StubLoader mLoader; - - private static final String parameter = "parameter"; - private static final Object executeActionResult = "executeActionResult"; - private static final String processResponseResult = "processResponseResult"; - private static final Object processFailureResult = "processFailureResult"; - - @Override - public void setUp() throws Exception { - super.setUp(); - Log.d(TAG, "ChatActionTest setUp"); - - mContext = new FakeContext(getContext(), this); - mWorker = new StubBackgroundWorker(); - FakeFactory.registerWithFakeContext(getContext(), mContext) - .withDataModel(new FakeDataModel(mContext) - .withBackgroundWorkerForActionService(mWorker) - .withActionService(new ActionService()) - .withConnectivityUtil(new StubConnectivityUtil(mContext))); - - mLoader = new StubLoader(); - setContext(Factory.get().getApplicationContext()); - } - - @Override - public String getServiceClassName() { - return ActionServiceImpl.class.getName(); - } - - @Override - public void startServiceForStub(final Intent intent) { - this.startService(intent); - } - - @Override - public void onStartCommandForStub(final Intent intent, final int flags, final int startId) { - this.getService().onStartCommand(intent, flags, startId); - } - - public static class TestChatAction extends Action implements Parcelable { - public static String RESPONSE_TEST = "response_test"; - public static String KEY_PARAMETER = "parameter"; - - protected TestChatAction(final String key, final String parameter) { - super(key); - this.actionParameters.putString(KEY_PARAMETER, parameter); - // Cache parameter as a member variable - this.parameter = parameter; - } - - // An example parameter - public final String parameter; - - /** - * Process the action locally - runs on datamodel service thread - */ - @Override - protected Object executeAction() { - requestBackgroundWork(); - return executeActionResult; - } - - /** - * Process the response from the server - runs on datamodel service thread - */ - @Override - protected Object processBackgroundResponse(final Bundle response) { - requestBackgroundWork(new TestChatActionOther(null, parameter)); - return response.get(RESPONSE_TEST); - } - - /** - * Called in case of failures when sending requests - runs on datamodel service thread - */ - @Override - protected Object processBackgroundFailure() { - return processFailureResult; - } - - private TestChatAction(final Parcel in) { - super(in); - // Cache parameter as a member variable - parameter = actionParameters.getString(KEY_PARAMETER); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public TestChatAction createFromParcel(final Parcel in) { - return new TestChatAction(in); - } - - @Override - public TestChatAction[] newArray(final int size) { - return new TestChatAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } - } - - public static class TestChatActionOther extends Action implements Parcelable { - protected TestChatActionOther(final String key, final String parameter) { - super(generateUniqueActionKey(key)); - this.parameter = parameter; - } - - public final String parameter; - - private TestChatActionOther(final Parcel in) { - super(in); - parameter = in.readString(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public TestChatActionOther createFromParcel(final Parcel in) { - return new TestChatActionOther(in); - } - - @Override - public TestChatActionOther[] newArray(final int size) { - return new TestChatActionOther[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - parcel.writeString(parameter); - } - } - - /** - * An operation that notifies a listener upon completion - */ - public static class TestChatActionMonitor extends ActionMonitor { - /** - * Create action state wrapping an BlockUserAction instance - * @param account - account in which to block the user - * @param baseKey - suggested action key from BlockUserAction - * @param data - optional action specific data that is handed back to listener - * @param listener - action completed listener - */ - public TestChatActionMonitor(final String baseKey, final Object data, - final ActionCompletedListener completed, final ActionExecutedListener executed) { - super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); - setCompletedListener(completed); - setExecutedListener(executed); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java deleted file mode 100644 index 02cddae..0000000 --- a/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Looper; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.Process; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import com.android.messaging.Factory; -import com.android.messaging.FakeContext; -import com.android.messaging.FakeContext.FakeContextHost; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.BugleServiceTestCase; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; -import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener; -import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader; -import com.android.messaging.util.WakeLockHelper; - -import java.util.ArrayList; - -@MediumTest -public class ActionServiceTest extends BugleServiceTestCase - implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener { - private static final String TAG = "ActionServiceTest"; - - @Override - public void onActionStateChanged(final Action action, final int state) { - mStates.add(state); - } - - @Override - public void onActionSucceeded(final ActionMonitor monitor, - final Action action, final Object data, final Object result) { - final TestChatAction test = (TestChatAction) action; - assertNotSame(test.dontRelyOnMe, dontRelyOnMe); - // This will be true - but only briefly - assertEquals(test.dontRelyOnMe, becauseIChange); - - final ResultTracker tracker = (ResultTracker) data; - tracker.completionResult = result; - synchronized(tracker) { - tracker.notifyAll(); - } - } - - @Override - public void onActionFailed(final ActionMonitor monitor, final Action action, - final Object data, final Object result) { - final TestChatAction test = (TestChatAction) action; - assertNotSame(test.dontRelyOnMe, dontRelyOnMe); - // This will be true - but only briefly - assertEquals(test.dontRelyOnMe, becauseIChange); - - final ResultTracker tracker = (ResultTracker) data; - tracker.completionResult = result; - synchronized(tracker) { - tracker.notifyAll(); - } - } - - /** - * For a dummy action verify that the service intent is constructed and queued correctly and - * that when that intent is processed it actually executes the action. - */ - public void testChatServiceCreatesIntentAndExecutesAction() { - final ResultTracker tracker = new ResultTracker(); - - final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this); - final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter); - - action.dontRelyOnMe = dontRelyOnMe; - assertFalse("Expect service initially stopped", mServiceStarted); - - action.start(monitor); - - assertTrue("Expect service started", mServiceStarted); - - final ArrayList intents = mContext.extractIntents(); - assertNotNull(intents); - assertEquals("Expect to see 1 server request queued", 1, intents.size()); - final Intent intent = intents.get(0); - assertEquals("Check pid", intent.getIntExtra(WakeLockHelper.EXTRA_CALLING_PID, 0), - Process.myPid()); - assertEquals("Check opcode", intent.getIntExtra(ActionServiceImpl.EXTRA_OP_CODE, 0), - ActionServiceImpl.OP_START_ACTION); - assertTrue("Check wakelock held", ActionServiceImpl.sWakeLock.isHeld(intent)); - - synchronized(tracker) { - try { - this.startService(intent); - // Wait for callback across threads - tracker.wait(2000); - } catch (final InterruptedException e) { - assertTrue("Interrupted waiting for response processing", false); - } - } - - assertEquals("Expect three states ", mStates.size(), 3); - assertEquals("State-0 should be STATE_QUEUED", (int)mStates.get(0), - ActionMonitor.STATE_QUEUED); - assertEquals("State-1 should be STATE_EXECUTING", (int)mStates.get(1), - ActionMonitor.STATE_EXECUTING); - assertEquals("State-2 should be STATE_COMPLETE", (int)mStates.get(2), - ActionMonitor.STATE_COMPLETE); - // TODO: Should find a way to reliably wait, this is a bit of a hack - if (ActionServiceImpl.sWakeLock.isHeld(intent)) { - Log.d(TAG, "ActionServiceTest: waiting for wakelock release"); - try { - Thread.sleep(100); - } catch (final InterruptedException e) { - } - } - assertFalse("Check wakelock released", ActionServiceImpl.sWakeLock.isHeld(intent)); - } - - StubBackgroundWorker mWorker; - FakeContext mContext; - StubLoader mLoader; - ActionService mService; - - ArrayList mStates; - - private static final String parameter = "parameter"; - private static final Object dontRelyOnMe = "dontRelyOnMe"; - private static final Object becauseIChange = "becauseIChange"; - private static final Object executeActionResult = "executeActionResult"; - private static final Object processResponseResult = "processResponseResult"; - private static final Object processFailureResult = "processFailureResult"; - - public ActionServiceTest() { - super(ActionServiceImpl.class); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - Log.d(TAG, "ChatActionTest setUp"); - - sLooper = Looper.myLooper(); - - mWorker = new StubBackgroundWorker(); - mContext = new FakeContext(getContext(), this); - FakeFactory.registerWithFakeContext(getContext(),mContext) - .withDataModel(new FakeDataModel(mContext) - .withBackgroundWorkerForActionService(mWorker) - .withActionService(new ActionService()) - .withConnectivityUtil(new StubConnectivityUtil(mContext))); - - mStates = new ArrayList(); - setContext(Factory.get().getApplicationContext()); - } - - @Override - public String getServiceClassName() { - return ActionServiceImpl.class.getName(); - } - - boolean mServiceStarted = false; - - @Override - public void startServiceForStub(final Intent intent) { - // Do nothing until later - assertFalse(mServiceStarted); - mServiceStarted = true; - } - - @Override - public void onStartCommandForStub(final Intent intent, final int flags, final int startId) { - assertTrue(mServiceStarted); - } - - private static Looper sLooper; - public static void assertRunsOnOtherThread() { - assertTrue (Looper.myLooper() != Looper.getMainLooper()); - assertTrue (Looper.myLooper() != sLooper); - } - - public static class TestChatAction extends Action implements Parcelable { - public static String RESPONSE_TEST = "response_test"; - public static String KEY_PARAMETER = "parameter"; - - protected TestChatAction(final String key, final String parameter) { - super(key); - this.actionParameters.putString(KEY_PARAMETER, parameter); - } - - transient Object dontRelyOnMe; - - /** - * Process the action locally - runs on service thread - */ - @Override - protected Object executeAction() { - this.dontRelyOnMe = becauseIChange; - assertRunsOnOtherThread(); - return executeActionResult; - } - - /** - * Process the response from the server - runs on service thread - */ - @Override - protected Object processBackgroundResponse(final Bundle response) { - assertRunsOnOtherThread(); - return processResponseResult; - } - - /** - * Called in case of failures when sending requests - runs on service thread - */ - @Override - protected Object processBackgroundFailure() { - assertRunsOnOtherThread(); - return processFailureResult; - } - - private TestChatAction(final Parcel in) { - super(in); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public TestChatAction createFromParcel(final Parcel in) { - return new TestChatAction(in); - } - - @Override - public TestChatAction[] newArray(final int size) { - return new TestChatAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - } - } - - /** - * An operation that notifies a listener upon state changes, execution and completion - */ - public static class TestChatActionMonitor extends ActionMonitor { - public TestChatActionMonitor(final String baseKey, final Object data, - final ActionStateChangedListener listener, final ActionCompletedListener executed) { - super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data); - setStateChangedListener(listener); - setCompletedListener(executed); - assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTest.java b/tests/src/com/android/messaging/datamodel/action/ActionTest.java deleted file mode 100644 index aefa25e..0000000 --- a/tests/src/com/android/messaging/datamodel/action/ActionTest.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.DataModelImpl; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubChatActionMonitor; - -import java.util.ArrayList; - -@MediumTest -public class ActionTest extends BugleTestCase { - private static final String parameter = "parameter"; - private static final Object executeActionResult = "executeActionResult"; - private static final Object processResponseResult = "processResponseResult"; - private static final Object processFailureResult = "processFailureResult"; - - private static final String mActionKey = "TheActionKey"; - private static final Object mData = "PrivateData"; - private StubChatActionMonitor mMonitor; - private TestChatAction mAction; - - private ArrayList mTransitions; - - @Override - public void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()) - .withDataModel(new DataModelImpl(getContext())); - - mMonitor = new StubChatActionMonitor(ActionMonitor.STATE_CREATED, mActionKey, - mData); - mAction = new TestChatAction(mActionKey, parameter); - mTransitions = mMonitor.getTransitions(); - } - - private void verifyState(final int count, final int from, final int to) { - assertEquals(to, mMonitor.getState()); - assertEquals(mTransitions.size(), count); - verifyTransition(count-1, from , to); - } - - private void verifyTransition(final int index, final int from, final int to) { - assertTrue(mTransitions.size() > index); - assertEquals(mAction, mTransitions.get(index).action); - assertEquals(from, mTransitions.get(index).from); - assertEquals(to, mTransitions.get(index).to); - } - - @SmallTest - public void testActionStartTransitionsCorrectly() { - mMonitor.setState(ActionMonitor.STATE_CREATED); - - ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.markStart(); - assertEquals("After start state: STATE_QUEUED", ActionMonitor.STATE_QUEUED, - mMonitor.getState()); - verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); - - ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor); - - assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - } - - @SmallTest - public void testActionStartAssertsFromIncorrectState() { - mMonitor.setState(ActionMonitor.STATE_UNDEFINED); - - ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - try { - mAction.markStart(); - fail("Expect assertion when starting from STATE_UNDEFINED"); - } catch (final IllegalStateException ex){ - } - ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor); - - assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - } - - public void testActionTransitionsEndToEndWithRequests() { - assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, - mMonitor.getState()); - - ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.markStart(); - - verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); - - mAction.markBeginExecute(); - - verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); - - final Object result = mAction.executeAction(); - mAction.requestBackgroundWork(); - - assertEquals("Check executeAction result", result, executeActionResult); - - mAction.markEndExecute(result); - - verifyState(3, ActionMonitor.STATE_EXECUTING, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); - - mAction.markBackgroundWorkStarting(); - - verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); - - mAction.markBackgroundWorkQueued(); - - verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); - - mAction.markBackgroundWorkStarting(); - - verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); - - final Bundle response = null; - - mAction.markBackgroundCompletionQueued(); - - verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED); - - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.processBackgroundWorkResponse(response); - - verifyTransition(7, ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED, - ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE); - - verifyState(9, ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, - ActionMonitor.STATE_COMPLETE); - - assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - } - - public void testActionTransitionsEndToEndFailsRequests() { - assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, - mMonitor.getState()); - - ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.markStart(); - - verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); - - mAction.markBeginExecute(); - - verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); - - final Object result = mAction.executeAction(); - mAction.requestBackgroundWork(); - - assertEquals("Check executeAction result", result, executeActionResult); - - mAction.markEndExecute(result); - - verifyState(3, ActionMonitor.STATE_EXECUTING, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); - - mAction.markBackgroundWorkStarting(); - - verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); - - mAction.markBackgroundWorkQueued(); - - verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); - - mAction.markBackgroundWorkStarting(); - - verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, - ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); - - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.processBackgroundWorkFailure(); - - verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, - ActionMonitor.STATE_COMPLETE); - - assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - } - - public void testActionTransitionsEndToEndNoRequests() { - assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED, - mMonitor.getState()); - - ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor); - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.markStart(); - - verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED); - - mAction.markBeginExecute(); - - verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING); - - final Object result = mAction.executeAction(); - - assertEquals("Check executeAction result", result, executeActionResult); - - assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor); - - mAction.markEndExecute(result); - - verifyState(3, ActionMonitor.STATE_EXECUTING, - ActionMonitor.STATE_COMPLETE); - - assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey)); - assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor)); - } - - public static class TestChatAction extends Action implements Parcelable { - protected TestChatAction(final String key, final String parameter) { - super(key); - this.parameter = parameter; - } - - public final String parameter; - - /** - * Process the action locally - runs on service thread - */ - @Override - protected Object executeAction() { - assertEquals("Check parameter", parameter, ActionTest.parameter); - return executeActionResult; - } - - /** - * Process the response from the server - runs on service thread - */ - @Override - protected Object processBackgroundResponse(final Bundle response) { - assertEquals("Check parameter", parameter, ActionTest.parameter); - return processResponseResult; - } - - /** - * Called in case of failures when sending requests - runs on service thread - */ - @Override - protected Object processBackgroundFailure() { - assertEquals("Check parameter", parameter, ActionTest.parameter); - return processFailureResult; - } - - private TestChatAction(final Parcel in) { - super(in); - parameter = in.readString(); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public TestChatAction createFromParcel(final Parcel in) { - return new TestChatAction(in); - } - - @Override - public TestChatAction[] newArray(final int size) { - return new TestChatAction[size]; - } - }; - - @Override - public void writeToParcel(final Parcel parcel, final int flags) { - writeActionToParcel(parcel, flags); - parcel.writeString(parameter); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java deleted file mode 100644 index d72a0f9..0000000 --- a/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Bundle; - -import com.android.messaging.util.ConnectivityUtil; - -import java.util.ArrayList; -import java.util.List; - -public class ActionTestHelpers { - private static final String TAG = "DataModelTestHelpers"; - - static class StubLoader extends ContentObserver { - ArrayList mUriList = new ArrayList(); - - StubLoader() { - super(null); - } - - public void clear() { - mUriList.clear(); - } - - @Override - public void onChange(final boolean selfChange) { - // Handle change. - mUriList.add(null); - } - - // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument. - // Only supported on platform 16 and above... - @Override - public void onChange(final boolean selfChange, final Uri uri) { - // Handle change. - mUriList.add(uri); - } - } - - static class StubBackgroundWorker extends BackgroundWorker { - public StubBackgroundWorker() { - super(); - mActions = new ArrayList(); - } - - ArrayList mActions; - public ArrayList getRequestsMade() { - return mActions; - } - - @Override - public void queueBackgroundWork(final List actions) { - mActions.addAll(actions); - - synchronized(this) { - this.notifyAll(); - } - } - } - - static class ResultTracker { - public Object executionResult; - public Object completionResult; - } - - static class StubChatActionMonitor extends ActionMonitor { - static public class StateTransition { - Action action; - int from; - int to; - public StateTransition(final Action action, final int from, final int to) { - this.action = action; - this.from = from; - this.to = to; - } - } - - private final ArrayList mTransitions; - public ArrayList getTransitions() { - return mTransitions; - } - - protected StubChatActionMonitor(final int initialState, final String actionKey, - final Object data) { - super(initialState, actionKey, data); - mTransitions = new ArrayList(); - } - - @Override - protected void updateState(final Action action, final int expectedState, - final int state) { - mTransitions.add(new StateTransition(action, mState, state)); - super.updateState(action, expectedState, state); - } - - public void setState(final int state) { - mState = state; - } - - public int getState() { - return mState; - } - } - - public static class StubActionService extends ActionService { - public static class StubActionServiceCallLog { - public final Action action; - public final Action request; - public final Bundle response; - public final Exception exception; - public final Action update; - - public StubActionServiceCallLog(final Action action, - final Action request, - final Bundle response, - final Exception exception, - final Action update) { - this.action = action; - this.request = request; - this.response = response; - this.exception = exception; - this.update = update; - } - } - - private final ArrayList mServiceCalls = - new ArrayList(); - - public ArrayList getCalls() { - return mServiceCalls; - } - - @Override - public void startAction(final Action action) { - mServiceCalls.add(new StubActionServiceCallLog(action, null, null, null, null)); - synchronized(this) { - this.notifyAll(); - } - } - - @Override - public void handleResponseFromBackgroundWorker(final Action request, - final Bundle response) { - mServiceCalls.add(new StubActionServiceCallLog(null, request, response, null, null)); - synchronized(this) { - this.notifyAll(); - } - } - - @Override - protected void handleFailureFromBackgroundWorker(final Action request, - final Exception exception) { - mServiceCalls.add(new StubActionServiceCallLog(null, request, null, exception, null)); - synchronized(this) { - this.notifyAll(); - } - } - } - - public static class StubConnectivityUtil extends ConnectivityUtil { - public StubConnectivityUtil(final Context context) { - super(context); - } - - @Override - public void registerForSignalStrength() { - } - - @Override - public void unregisterForSignalStrength() { - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java deleted file mode 100644 index b05b022..0000000 --- a/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.pm.ProviderInfo; -import android.database.Cursor; -import android.net.Uri; -import android.test.mock.MockContentProvider; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeContext; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.sms.MmsUtils; - -import org.mockito.Mock; - -import java.util.ArrayList; - -@SmallTest -public class GetOrCreateConversationActionTest extends BugleTestCase { - - @Mock GetOrCreateConversationActionListener mockListener; - - public void testGetOrCreateConversation() { - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final ArrayList recipients = new ArrayList(); - recipients.add("5551234567"); - recipients.add("5551234568"); - - // Generate a list of partially formed participants - final ArrayList participants = new - ArrayList(); - - - for (final String recipient : recipients) { - participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient)); - } - - // Test that we properly stubbed the SMS provider to return a thread id - final long threadId = MmsUtils.getOrCreateThreadId(mContext, recipients); - assertEquals(TestDataFactory.SMS_MMS_THREAD_ID_CURSOR_VALUE, threadId); - - final String blankId = BugleDatabaseOperations.getExistingConversation(db, threadId, false); - assertNull("Conversation already exists", blankId); - - ArrayList calls = mService.getCalls(); - - GetOrCreateConversationActionMonitor monitor = - GetOrCreateConversationAction.getOrCreateConversation(participants, null, - mockListener); - - assertEquals("Failed to start service once for action", calls.size(), 1); - assertTrue("Action not GetOrCreateConversationAction", calls.get(0).action instanceof - GetOrCreateConversationAction); - - GetOrCreateConversationAction action = (GetOrCreateConversationAction) - calls.get(0).action; - - Object result = action.executeAction(); - - assertTrue(result instanceof String); - - // Make sure that we created a new conversation - assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result)); - - // Now get the conversation that we just created again - monitor = GetOrCreateConversationAction.getOrCreateConversation(participants, null, - mockListener); - - calls = mService.getCalls(); - assertEquals("Failed to start service for second action", calls.size(), 2); - assertTrue("Action not GetOrCreateConversationAction", calls.get(1).action instanceof - GetOrCreateConversationAction); - action = (GetOrCreateConversationAction)calls.get(1).action; - result = action.executeAction(); - - assertTrue(result instanceof String); - - final String conversationId = (String) result; - - // Make sure that we found the same conversation id - assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result)); - - final ArrayList conversationParticipants = - BugleDatabaseOperations.getParticipantsForConversation(db, conversationId); - - assertEquals("Participant count mismatch", recipients.size(), - conversationParticipants.size()); - for(final ParticipantData participant : conversationParticipants) { - assertTrue(recipients.contains(participant.getSendDestination())); - } - - final Uri conversationParticipantsUri = - MessagingContentProvider.buildConversationParticipantsUri(conversationId); - final Cursor cursor = mContext.getContentResolver().query(conversationParticipantsUri, - ParticipantData.ParticipantsQuery.PROJECTION, null, null, null); - - int countSelf = 0; - while(cursor.moveToNext()) { - final ParticipantData participant = ParticipantData.getFromCursor(cursor); - if (participant.isSelf()) { - countSelf++; - } else { - assertTrue(recipients.contains(participant.getSendDestination())); - } - } - cursor.close(); - assertEquals("Expect one self participant in conversations", 1, countSelf); - assertEquals("Cursor count mismatch", recipients.size(), cursor.getCount() - countSelf); - - final String realId = BugleDatabaseOperations.getExistingConversation(db, threadId, false); - assertEquals("Conversation already exists", realId, conversationId); - } - - private FakeContext mContext; - private StubActionService mService; - - @Override - public void setUp() throws Exception { - super.setUp(); - - mContext = new FakeContext(getTestContext()); - - final MockContentProvider mockProvider = new MockContentProvider() { - @Override - public Cursor query(final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - return TestDataFactory.getSmsMmsThreadIdCursor(); - } - }; - - mContext.addContentProvider("mms-sms", mockProvider); - final MessagingContentProvider provider = new MessagingContentProvider(); - final ProviderInfo providerInfo = new ProviderInfo(); - providerInfo.authority = MessagingContentProvider.AUTHORITY; - provider.attachInfo(mContext, providerInfo); - mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider); - - mService = new StubActionService(); - final FakeDataModel fakeDataModel = new FakeDataModel(mContext) - .withActionService(mService); - FakeFactory.registerWithFakeContext(getTestContext(), mContext) - .withDataModel(fakeDataModel); - provider.setDatabaseForTest(fakeDataModel.getDatabase()); - } -} diff --git a/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java deleted file mode 100644 index 0405c90..0000000 --- a/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.action; - -import android.content.ContentProvider; -import android.content.pm.ProviderInfo; -import android.database.Cursor; -import android.net.Uri; -import android.test.suitebuilder.annotation.SmallTest; -import android.text.TextUtils; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeContext; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseWrapper; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.MediaScratchFileProvider; -import com.android.messaging.datamodel.MessagingContentProvider; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil; -import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.util.ContentType; - -import org.mockito.Mock; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; - -@SmallTest -public class ReadWriteDraftMessageActionTest extends BugleTestCase { - - @Mock ReadDraftDataActionListener mockListener; - - // TODO: Add test cases - // 1. Make sure drafts can include attachments and multiple parts - // 2. Make sure attachments get cleaned up appropriately - // 3. Make sure messageId and partIds not reused (currently each draft is a new message). - public void testWriteDraft() { - final String draftMessage = "draftMessage"; - final long threadId = 1234567; - final boolean senderBlocked = false; - final String participantNumber = "5551234567"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String conversationId = getOrCreateConversation(db, participantNumber, threadId, - senderBlocked); - final String selfId = getOrCreateSelfId(db); - - // Should clear/stub DB - final ArrayList calls = mService.getCalls(); - - final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, - draftMessage); - - WriteDraftMessageAction.writeDraftMessage(conversationId, message); - - assertEquals("Failed to start service once for action", calls.size(), 1); - assertTrue("Action not SaveDraftMessageAction", - calls.get(0).action instanceof WriteDraftMessageAction); - - final Action save = calls.get(0).action; - - final Object result = save.executeAction(); - - assertTrue("Expect row number string as result", result instanceof String); - final String messageId = (String) result; - - // Should check DB - final MessageData actual = BugleDatabaseOperations.readMessage(db, messageId); - assertNotNull("Database missing draft", actual); - assertEquals("Draft text changed", draftMessage, actual.getMessageText()); - } - - private static String getOrCreateSelfId(final DatabaseWrapper db) { - db.beginTransaction(); - final String selfId = BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, - ParticipantData.getSelfParticipant(ParticipantData.DEFAULT_SELF_SUB_ID)); - db.setTransactionSuccessful(); - db.endTransaction(); - return selfId; - } - - private static String getOrCreateConversation(final DatabaseWrapper db, - final String participantNumber, final long threadId, final boolean senderBlocked) { - final ArrayList participants = - new ArrayList(); - participants.add(ParticipantData.getFromRawPhoneBySystemLocale(participantNumber)); - - final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - senderBlocked, participants, false, false, null); - assertNotNull("No conversation", conversationId); - return conversationId; - } - - public void testReadDraft() { - final Object data = "data"; - final String draftMessage = "draftMessage"; - final long threadId = 1234567; - final boolean senderBlocked = false; - final String participantNumber = "5552345678"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String conversationId = getOrCreateConversation(db, participantNumber, threadId, - senderBlocked); - final String selfId = getOrCreateSelfId(db); - - // Should clear/stub DB - final ArrayList calls = mService.getCalls(); - - final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, - draftMessage); - - BugleDatabaseOperations.updateDraftMessageData(db, conversationId, message, - BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - - final ActionMonitor monitor = - ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener); - - assertEquals("Unexpected number of calls to service", 1, calls.size()); - assertTrue("Action not of type ReadDraftMessageAction", - calls.get(0).action instanceof ReadDraftDataAction); - - final Action read = calls.get(0).action; - - final Object result = read.executeAction(); - - assertTrue(result instanceof ReadDraftDataAction.DraftData); - final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; - - assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText()); - assertEquals("Draft self differs", selfId, draft.message.getSelfId()); - assertEquals("Draft conversation differs", conversationId, - draft.conversation.getConversationId()); - } - - public void testReadDraftForNewConversation() { - final Object data = "data"; - long threadId = 1234567; - final boolean senderBlocked = false; - long phoneNumber = 5557654567L; - final String notConversationId = "ThisIsNotValidConversationId"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - final String selfId = getOrCreateSelfId(db); - - // Unless set a new conversation should have a null draft message - final MessageData blank = BugleDatabaseOperations.readDraftMessageData(db, - notConversationId, selfId); - assertNull(blank); - - String conversationId = null; - do { - conversationId = BugleDatabaseOperations.getExistingConversation(db, - threadId, senderBlocked); - threadId++; - phoneNumber++; - } - while(!TextUtils.isEmpty(conversationId)); - - final ArrayList participants = - new ArrayList(); - participants.add(ParticipantData.getFromRawPhoneBySystemLocale(Long.toString(phoneNumber))); - - conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId, - senderBlocked, participants, false, false, null); - assertNotNull("No conversation", conversationId); - - final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, conversationId, - selfId); - assertNull(actual); - - // Should clear/stub DB - final ArrayList calls = mService.getCalls(); - - final ActionMonitor monitor = - ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener); - - assertEquals("Unexpected number of calls to service", 1, calls.size()); - assertTrue("Action not of type ReadDraftMessageAction", - calls.get(0).action instanceof ReadDraftDataAction); - - final Action read = calls.get(0).action; - - final Object result = read.executeAction(); - - assertTrue(result instanceof ReadDraftDataAction.DraftData); - final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; - - assertEquals("Draft message text differs", "", draft.message.getMessageText()); - assertEquals("Draft self differs", selfId, draft.message.getSelfId()); - assertEquals("Draft conversation differs", conversationId, - draft.conversation.getConversationId()); - } - - public void testWriteAndReadDraft() { - final Object data = "data"; - final String draftMessage = "draftMessage"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - final Cursor conversations = db.query(DatabaseHelper.CONVERSATIONS_TABLE, - new String[] { ConversationColumns._ID, ConversationColumns.CURRENT_SELF_ID }, null, - null, null /* groupBy */, null /* having */, null /* orderBy */); - - if (conversations.moveToFirst()) { - final String conversationId = conversations.getString(0); - final String selfId = getOrCreateSelfId(db); - - // Should clear/stub DB - final ArrayList calls = mService.getCalls(); - - final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, - draftMessage); - - WriteDraftMessageAction.writeDraftMessage(conversationId, message); - - assertEquals("Failed to start service once for action", calls.size(), 1); - assertTrue("Action not SaveDraftMessageAction", - calls.get(0).action instanceof WriteDraftMessageAction); - - final Action save = calls.get(0).action; - - Object result = save.executeAction(); - - assertTrue("Expect row number string as result", result instanceof String); - - // Should check DB - - final ActionMonitor monitor = - ReadDraftDataAction.readDraftData(conversationId, null, data, - mockListener); - - assertEquals("Expect two calls queued", 2, calls.size()); - assertTrue("Expect action", calls.get(1).action instanceof ReadDraftDataAction); - - final Action read = calls.get(1).action; - - result = read.executeAction(); - - assertTrue(result instanceof ReadDraftDataAction.DraftData); - final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result; - - assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText()); - // The conversation's self id is used as the draft's self id. - assertEquals("Draft self differs", conversations.getString(1), - draft.message.getSelfId()); - assertEquals("Draft conversation differs", conversationId, - draft.conversation.getConversationId()); - } else { - fail("No conversations in database"); - } - } - - public void testUpdateDraft() { - final String initialMessage = "initialMessage"; - final String draftMessage = "draftMessage"; - final long threadId = 1234567; - final boolean senderBlocked = false; - final String participantNumber = "5553456789"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String conversationId = getOrCreateConversation(db, participantNumber, threadId, - senderBlocked); - final String selfId = getOrCreateSelfId(db); - - final ArrayList calls = mService.getCalls(); - - // Insert initial message - MessageData initial = MessageData.createDraftSmsMessage(conversationId, selfId, - initialMessage); - - BugleDatabaseOperations.updateDraftMessageData(db, conversationId, initial, - BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - - initial = BugleDatabaseOperations.readDraftMessageData(db, - conversationId, selfId); - assertEquals("Initial text mismatch", initialMessage, initial.getMessageText()); - - // Now update the draft - final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId, - draftMessage); - WriteDraftMessageAction.writeDraftMessage(conversationId, message); - - assertEquals("Failed to start service once for action", calls.size(), 1); - assertTrue("Action not SaveDraftMessageAction", - calls.get(0).action instanceof WriteDraftMessageAction); - - final Action save = calls.get(0).action; - - final Object result = save.executeAction(); - - assertTrue("Expect row number string as result", result instanceof String); - - // Check DB - final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, - conversationId, selfId); - assertNotNull("Database missing draft", actual); - assertEquals("Draft text mismatch", draftMessage, actual.getMessageText()); - assertNull("Draft messageId should be null", actual.getMessageId()); - } - - public void testBugleDatabaseDraftOperations() { - final String initialMessage = "initialMessage"; - final String draftMessage = "draftMessage"; - final long threadId = 1234599; - final boolean senderBlocked = false; - final String participantNumber = "5553456798"; - final String subject = "subject here"; - - final DatabaseWrapper db = DataModel.get().getDatabase(); - - final String conversationId = getOrCreateConversation(db, participantNumber, threadId, - senderBlocked); - final String selfId = getOrCreateSelfId(db); - - final String text = "This is some text"; - final Uri mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); - OutputStream outputStream = null; - try { - outputStream = mContext.getContentResolver().openOutputStream(mOutputUri); - outputStream.write(text.getBytes()); - } catch (final FileNotFoundException e) { - fail("Cannot open output file"); - } catch (final IOException e) { - fail("Cannot write output file"); - } - - final MessageData initial = - MessageData.createDraftMmsMessage(conversationId, selfId, initialMessage, subject); - initial.addPart(MessagePartData.createMediaMessagePart(ContentType.MULTIPART_MIXED, - mOutputUri, 0, 0)); - - final String initialMessageId = BugleDatabaseOperations.updateDraftMessageData(db, - conversationId, initial, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - assertNotNull(initialMessageId); - - final MessageData initialDraft = BugleDatabaseOperations.readMessage(db, initialMessageId); - assertNotNull(initialDraft); - int cnt = 0; - for(final MessagePartData part : initialDraft.getParts()) { - if (part.isAttachment()) { - assertEquals(part.getContentUri(), mOutputUri); - } else { - assertEquals(part.getText(), initialMessage); - } - cnt++; - } - assertEquals("Wrong number of parts", 2, cnt); - - InputStream inputStream = null; - try { - inputStream = mContext.getContentResolver().openInputStream(mOutputUri); - final byte[] buffer = new byte[256]; - final int read = inputStream.read(buffer); - assertEquals(read, text.getBytes().length); - } catch (final FileNotFoundException e) { - fail("Cannot open input file"); - } catch (final IOException e) { - fail("Cannot read input file"); - } - - final String moreText = "This is some more text"; - final Uri mAnotherUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt"); - outputStream = null; - try { - outputStream = mContext.getContentResolver().openOutputStream(mAnotherUri); - outputStream.write(moreText.getBytes()); - } catch (final FileNotFoundException e) { - fail("Cannot open output file"); - } catch (final IOException e) { - fail("Cannot write output file"); - } - - final MessageData another = - MessageData.createDraftMmsMessage(conversationId, selfId, draftMessage, subject); - another.addPart(MessagePartData.createMediaMessagePart(ContentType.MMS_MULTIPART_MIXED, - mAnotherUri, 0, 0)); - - final String anotherMessageId = BugleDatabaseOperations.updateDraftMessageData(db, - conversationId, another, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - assertNotNull(anotherMessageId); - - final MessageData anotherDraft = BugleDatabaseOperations.readMessage(db, anotherMessageId); - assertNotNull(anotherDraft); - cnt = 0; - for(final MessagePartData part : anotherDraft.getParts()) { - if (part.isAttachment()) { - assertEquals(part.getContentUri(), mAnotherUri); - } else { - assertEquals(part.getText(), draftMessage); - } - cnt++; - } - assertEquals("Wrong number of parts", 2, cnt); - - inputStream = null; - try { - inputStream = mContext.getContentResolver().openInputStream(mOutputUri); - assertNull("Original draft content should have been deleted", inputStream); - } catch (final FileNotFoundException e) { - } - inputStream = null; - try { - inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); - final byte[] buffer = new byte[256]; - final int read = inputStream.read(buffer); - assertEquals(read, moreText.getBytes().length); - } catch (final FileNotFoundException e) { - fail("Cannot open input file"); - } catch (final IOException e) { - fail("Cannot read input file"); - } - - final MessageData last = null; - final String lastPartId = BugleDatabaseOperations.updateDraftMessageData(db, - conversationId, last, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT); - assertNull(lastPartId); - - inputStream = null; - try { - inputStream = mContext.getContentResolver().openInputStream(mAnotherUri); - assertNull("Original draft content should have been deleted", inputStream); - } catch (final FileNotFoundException e) { - } - - } - - private StubActionService mService; - - @Override - public void setUp() throws Exception { - super.setUp(); - - final FakeContext context = new FakeContext(getTestContext()); - - final ContentProvider bugleProvider = new MessagingContentProvider(); - final ProviderInfo bugleProviderInfo = new ProviderInfo(); - bugleProviderInfo.authority = MessagingContentProvider.AUTHORITY; - bugleProvider.attachInfo(mContext, bugleProviderInfo); - context.addContentProvider(MessagingContentProvider.AUTHORITY, bugleProvider); - final ContentProvider mediaProvider = new MediaScratchFileProvider(); - final ProviderInfo mediaProviderInfo = new ProviderInfo(); - mediaProviderInfo.authority = MediaScratchFileProvider.AUTHORITY; - mediaProvider.attachInfo(mContext, mediaProviderInfo); - context.addContentProvider(MediaScratchFileProvider.AUTHORITY, mediaProvider); - - mService = new StubActionService(); - final FakeDataModel fakeDataModel = new FakeDataModel(context) - .withActionService(mService) - .withConnectivityUtil(new StubConnectivityUtil(context)); - FakeFactory.registerWithFakeContext(getTestContext(), context) - .withDataModel(fakeDataModel); - - } -} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java deleted file mode 100644 index c6b9b20..0000000 --- a/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.data.ConversationMessageData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; - -@SmallTest -public class ConversationMessageDataTest extends BugleTestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()); - } - - public void testBindFirstMessage() { - final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); - final ConversationMessageData data = new ConversationMessageData(); - testCursor.moveToFirst(); - data.bind(testCursor); - // TODO: Add before checking in all bound fields... - assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 0).equals( - MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, - 0), data.getSenderProfilePhotoUri()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 0), - data.getSenderFullName()); - } - - public void testBindTwice() { - final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); - final ConversationMessageData data = new ConversationMessageData(); - testCursor.moveToPosition(1); - data.bind(testCursor); - assertEquals(TestDataFactory.getMessageText(testCursor, 1), data.getText()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 1), - data.getReceivedTimeStamp()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 1).equals( - MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, - 1), data.getSenderProfilePhotoUri()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 1), - data.getSenderFullName()); - testCursor.moveToPosition(2); - data.bind(testCursor); - assertEquals(TestDataFactory.getMessageText(testCursor, 2), data.getText()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 2), - data.getReceivedTimeStamp()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 2).equals( - MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, - 2), data.getSenderProfilePhotoUri()); - assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 2), - data.getSenderFullName()); - } - - public void testMessageClustering() { - final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor(); - final ConversationMessageData data = new ConversationMessageData(); - testCursor.moveToPosition(0); - data.bind(testCursor); - assertFalse(data.getCanClusterWithPreviousMessage()); - assertFalse(data.getCanClusterWithNextMessage()); - - testCursor.moveToPosition(1); - data.bind(testCursor); - assertFalse(data.getCanClusterWithPreviousMessage()); - assertFalse(data.getCanClusterWithNextMessage()); - - testCursor.moveToPosition(2); - data.bind(testCursor); - assertFalse(data.getCanClusterWithPreviousMessage()); - assertTrue(data.getCanClusterWithNextMessage()); // 2 and 3 can be clustered - testCursor.moveToPosition(3); - - data.bind(testCursor); - assertTrue(data.getCanClusterWithPreviousMessage()); // 2 and 3 can be clustered - assertFalse(data.getCanClusterWithNextMessage()); - } -} diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java deleted file mode 100644 index 527a600..0000000 --- a/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.data; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.data.ConversationParticipantsData; -import com.android.messaging.datamodel.data.ParticipantData; - -@SmallTest -public class ConversationParticipantsDataTest extends BugleTestCase { - public void testBindParticipants() { - final FakeCursor testCursor = TestDataFactory.getConversationParticipantsCursor(); - final ConversationParticipantsData data = new ConversationParticipantsData(); - data.bind(testCursor); - - assertEquals(data.getParticipantListExcludingSelf().size(), testCursor.getCount()); - final ParticipantData participant2 = data.getParticipantById("2"); - assertNotNull(participant2); - assertEquals(participant2.getFirstName(), testCursor.getAt( - ParticipantColumns.FIRST_NAME, 1) ); - assertEquals(participant2.getSendDestination(), testCursor.getAt( - ParticipantColumns.SEND_DESTINATION, 1)); - } -} diff --git a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java deleted file mode 100644 index 8527e2b..0000000 --- a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.datamodel.data; - -import android.content.ContentValues; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.provider.MediaStore.Images.Media; - -import com.android.messaging.datamodel.BugleDatabaseOperations; -import com.android.messaging.datamodel.DatabaseHelper; -import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns; -import com.android.messaging.datamodel.DatabaseHelper.MessageColumns; -import com.android.messaging.datamodel.DatabaseHelper.PartColumns; -import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns; -import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContactUtil; -import com.android.messaging.util.ContentType; - -import java.util.Arrays; -import java.util.List; - -/** - * A factory for fake objects that can be useful for multiple tests. - */ -public class TestDataFactory { - private final static String[] sConversationListCursorColumns = new String[] { - ConversationListViewColumns._ID, - ConversationListViewColumns.NAME, - ConversationListViewColumns.ICON, - ConversationListViewColumns.SNIPPET_TEXT, - ConversationListViewColumns.PREVIEW_URI, - ConversationListViewColumns.SORT_TIMESTAMP, - ConversationListViewColumns.READ, - ConversationListViewColumns.PREVIEW_CONTENT_TYPE, - ConversationListViewColumns.MESSAGE_STATUS, - }; - - private final static String[] sContactCursorColumns = new String[] { - Phone.CONTACT_ID, - Phone.DISPLAY_NAME_PRIMARY, - Phone.PHOTO_THUMBNAIL_URI, - Phone.NUMBER, - Phone.TYPE, - Phone.LABEL, - Phone.LOOKUP_KEY, - Phone._ID, - Phone.SORT_KEY_PRIMARY, - }; - - private final static String[] sFrequentContactCursorColumns = new String[] { - Contacts._ID, - Contacts.DISPLAY_NAME, - Contacts.PHOTO_URI, - Phone.LOOKUP_KEY, - }; - - private final static String[] sConversationMessageCursorColumns = new String[] { - ConversationMessageViewColumns._ID, - ConversationMessageViewColumns.CONVERSATION_ID, - ConversationMessageViewColumns.PARTICIPANT_ID, - ConversationMessageViewColumns.SENT_TIMESTAMP, - ConversationMessageViewColumns.RECEIVED_TIMESTAMP, - ConversationMessageViewColumns.STATUS, - ConversationMessageViewColumns.SENDER_FULL_NAME, - ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI, - ConversationMessageViewColumns.PARTS_IDS, - ConversationMessageViewColumns.PARTS_CONTENT_TYPES, - ConversationMessageViewColumns.PARTS_CONTENT_URIS, - ConversationMessageViewColumns.PARTS_WIDTHS, - ConversationMessageViewColumns.PARTS_HEIGHTS, - ConversationMessageViewColumns.PARTS_TEXTS, - ConversationMessageViewColumns.PARTS_COUNT - }; - - private final static String[] sGalleryCursorColumns = new String[] { - Media._ID, - Media.DATA, - Media.WIDTH, - Media.HEIGHT, - Media.MIME_TYPE - }; - - public static FakeCursor getConversationListCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(1), "name1", "content://icon1", - "snippetText1", "content://snippetUri1", Long.valueOf(10), 1, - ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, - new Object[] { Long.valueOf(2), "name2", "content://icon2", - "snippetText2", "content://snippetUri2", Long.valueOf(20) + 24*60*60*1000, - 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE}, - new Object[] { Long.valueOf(3), "name3", "content://icon3", - "snippetText3", "content://snippetUri3", Long.valueOf(30) + 2*24*60*60*1000, - 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_OUTGOING_COMPLETE} - }; - return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, - cursorData); - } - public static final int CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX = 0; - public static final int CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX = 1; - - public static FakeCursor getEmptyConversationListCursor() { - return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns, - new Object[][] {}); - } - - public static FakeCursor getConversationMessageCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(0), Long.valueOf(1), Long.valueOf(1), - Long.valueOf(10), Long.valueOf(10), - MessageData.BUGLE_STATUS_INCOMING_COMPLETE, "Alice", null, - "0", "text/plain", "''", -1, -1, "msg0", 1}, - new Object[] { Long.valueOf(1), Long.valueOf(1), Long.valueOf(2), - Long.valueOf(20), Long.valueOf(20), - MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Bob", null, - "1", "text/plain", "''", -1, -1, "msg1", 1}, - new Object[] { Long.valueOf(2), Long.valueOf(1), Long.valueOf(1), - Long.valueOf(30), Long.valueOf(30), - MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, - "2", "contentType3", "'content://fakeUri3'", "0", "0", "msg1", 1}, - new Object[] { Long.valueOf(3), Long.valueOf(1), Long.valueOf(1), - Long.valueOf(40), Long.valueOf(40), - MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null, - "3|4", "'contentType4'|'text/plain'", "'content://fakeUri4'|''", "0|-1", "0|-1", "''|'msg3'", 2}, - }; - return new FakeCursor( - ConversationMessageData.getProjection(), - sConversationMessageCursorColumns, - cursorData); - } - - public static String getMessageText(final FakeCursor messageCursor, final int row) { - final String allPartsText = messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row) - .toString(); - final int partsCount = (Integer) messageCursor.getAt( - ConversationMessageViewColumns.PARTS_COUNT, row); - final String messageId = messageCursor.getAt( - ConversationMessageViewColumns._ID, row).toString(); - final List parts = ConversationMessageData.makeParts( - messageCursor.getAt(ConversationMessageViewColumns.PARTS_IDS, row).toString(), - messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_TYPES, row).toString(), - messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_URIS, row).toString(), - messageCursor.getAt(ConversationMessageViewColumns.PARTS_WIDTHS, row).toString(), - messageCursor.getAt(ConversationMessageViewColumns.PARTS_HEIGHTS, row).toString(), - messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row).toString(), - partsCount, - messageId); - - for (final MessagePartData part : parts) { - if (part.isText()) { - return part.getText(); - } - } - return null; - } - - // Indexes where to find consecutive and non consecutive messages from same participant - // (respect to index - 1). - public static final int MESSAGE_WITH_SAME_PARTICIPANT_AS_PREVIOUS = 3; - public static final int MESSAGE_WITH_DIFFERENT_PARTICIPANT_AS_PREVIOUS = 2; - - public static FakeCursor getConversationParticipantsCursor() { - final String[] sConversationParticipantsCursorColumns = new String[] { - ParticipantColumns._ID, - ParticipantColumns.SUB_ID, - ParticipantColumns.NORMALIZED_DESTINATION, - ParticipantColumns.SEND_DESTINATION, - ParticipantColumns.FULL_NAME, - ParticipantColumns.FIRST_NAME, - ParticipantColumns.PROFILE_PHOTO_URI, - }; - - final Object[][] cursorData = new Object[][] { - new Object[] { 1, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15554567890", - "(555)456-7890", "alice in wonderland", "alice", "alice.png" }, - new Object[] { 2, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551011121", - "(555)101-1121", "bob the baker", "bob", "bob.png"}, - new Object[] { 3, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551314152", - "(555)131-4152", "charles in charge", "charles", "charles.png" }, - }; - - return new FakeCursor(ParticipantData.ParticipantsQuery.PROJECTION, - sConversationParticipantsCursorColumns, cursorData); - } - - public static final int CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX = 0; - public static final int CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX = 2; - - /** - * Returns a cursor for the all contacts list consumable by ContactPickerFragment. - */ - public static FakeCursor getAllContactListCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(0), "John Smith", "content://uri1", - "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 0 }, - new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", - "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1 }, - new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", - "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 }, - new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3", - "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 3 }, - new Object[] { Long.valueOf(3), "Mike Jones", "content://uri3", - "425-555-1236", Phone.TYPE_MAIN, "", "5", Long.valueOf(4), 4 }, - }; - return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns, - cursorData); - } - - /** - * Returns a cursor for the frequent contacts list consumable by ContactPickerFragment. - * Note: make it so that this cursor is the generated result of getStrequentContactsCursor() - * and getAllContactListCursor(), i.e., expand the entries in getStrequentContactsCursor() - * with the details from getAllContactListCursor() - */ - public static FakeCursor getFrequentContactListCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3", - "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 0 }, - new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", - "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1}, - new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", - "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 }, - new Object[] { Long.valueOf(0), "John Smith", "content://uri1", - "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 3 }, - }; - return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns, - cursorData); - } - - /** - * Returns a strequent (starred + frequent) cursor (like the one produced by android contact - * provider's CONTENT_STREQUENT_URI query) that's consumable by FrequentContactsCursorBuilder. - */ - public static FakeCursor getStrequentContactsCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(0), "Anna Kinney", "content://uri1", "3" }, - new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", "1" }, - new Object[] { Long.valueOf(2), "John Smith", "content://uri3", "0" }, - // Email-only entry that shouldn't be included in the result. - new Object[] { Long.valueOf(3), "Email Contact", "content://uri4", "100" }, - }; - return new FakeCursor(ContactUtil.FrequentContactQuery.PROJECTION, - sFrequentContactCursorColumns, cursorData); - } - - public static final int SMS_MMS_THREAD_ID_CURSOR_VALUE = 123456789; - - public static FakeCursor getSmsMmsThreadIdCursor() { - final String[] ID_PROJECTION = { BaseColumns._ID }; - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(SMS_MMS_THREAD_ID_CURSOR_VALUE) }, - }; - return new FakeCursor(ID_PROJECTION, ID_PROJECTION, cursorData); - } - - public static FakeCursor getGalleryGridCursor() { - final Object[][] cursorData = new Object[][] { - new Object[] { Long.valueOf(0), "/sdcard/image1", 100, 100, "image/jpeg" }, - new Object[] { Long.valueOf(1), "/sdcard/image2", 200, 200, "image/png" }, - new Object[] { Long.valueOf(2), "/sdcard/image3", 300, 300, "image/jpeg" }, - }; - return new FakeCursor(GalleryGridItemData.IMAGE_PROJECTION, sGalleryCursorColumns, - cursorData); - } - - public static final int NUM_TEST_CONVERSATIONS = 10; - - /** - * Create test data in our db. - * - * Ideally this will create more realistic data with more variety. - */ - public static void createTestData(final SQLiteDatabase db) { - BugleDatabaseOperations.clearParticipantIdCache(); - - // Timestamp for 1 day ago - final long yesterday = System.currentTimeMillis() - (24 * 60 * 60 * 1000); - - final ContentValues conversationValues = new ContentValues(); - for (int i = 1; i <= NUM_TEST_CONVERSATIONS; i++) { - conversationValues.put(ConversationColumns.NAME, "Conversation " + i); - final long conversationId = db.insert(DatabaseHelper.CONVERSATIONS_TABLE, null, - conversationValues); - - final ContentValues messageValues = new ContentValues(); - for (int m = 1; m <= 25; m++) { - // Move forward ten minutes per conversation, 1 minute per message. - final long messageTime = yesterday + (i * 10 * 60 * 1000) + (m * 60 * 1000); - messageValues.put(MessageColumns.RECEIVED_TIMESTAMP, messageTime); - messageValues.put(MessageColumns.CONVERSATION_ID, conversationId); - messageValues.put(MessageColumns.SENDER_PARTICIPANT_ID, - Math.abs(("" + messageTime).hashCode()) % 2); - final long messageId = db.insert(DatabaseHelper.MESSAGES_TABLE, null, messageValues); - - // Create a text part for this message - final ContentValues partValues = new ContentValues(); - partValues.put(PartColumns.MESSAGE_ID, messageId); - partValues.put(PartColumns.CONVERSATION_ID, conversationId); - partValues.put(PartColumns.TEXT, "Conversation: " + conversationId + - " Message: " + m); - db.insert(DatabaseHelper.PARTS_TABLE, null, partValues); - - // Update the snippet for this conversation to the latest message inserted - conversationValues.clear(); - conversationValues.put(ConversationColumns.LATEST_MESSAGE_ID, messageId); - final int updatedCount = db.update(DatabaseHelper.CONVERSATIONS_TABLE, - conversationValues, - "_id=?", new String[]{String.valueOf(conversationId)}); - Assert.isTrue(updatedCount == 1); - } - } - } - - public static List getTestDraftAttachments() { - final MessagePartData[] retParts = new MessagePartData[] { - new MessagePartData(ContentType.IMAGE_JPEG, Uri.parse("content://image"), - 100, 100), - new MessagePartData(ContentType.VIDEO_3GPP, Uri.parse("content://video"), - 100, 100), - new MessagePartData(ContentType.TEXT_VCARD, Uri.parse("content://vcard"), - 0, 0), - new MessagePartData(ContentType.AUDIO_3GPP, Uri.parse("content://audio"), - 0, 0) - }; - return Arrays.asList(retParts); - } -} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java deleted file mode 100644 index 7f6ac3f..0000000 --- a/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.text.TextUtils; - -import java.util.List; - -public class FakeImageRequest implements MediaRequest { - public static final String INVALID_KEY = "invalid"; - private final String mKey; - private final int mSize; - - public FakeImageRequest(final String key, final int size) { - mKey = key; - mSize = size; - } - - @Override - public String getKey() { - return mKey; - } - - @Override - public FakeImageResource loadMediaBlocking(List> chainedTask) - throws Exception { - if (TextUtils.equals(mKey, INVALID_KEY)) { - throw new Exception(); - } else { - return new FakeImageResource(mSize, mKey); - } - } - - @Override - public int getCacheId() { - return FakeMediaCacheManager.FAKE_IMAGE_CACHE; - } - - @SuppressWarnings("unchecked") - @Override - public MediaCache getMediaCache() { - return (MediaCache) MediaCacheManager.get().getOrCreateMediaCacheById( - getCacheId()); - } - - @Override - public int getRequestType() { - return MediaRequest.REQUEST_LOAD_MEDIA; - } - - @Override - public MediaRequestDescriptor getDescriptor() { - return null; - } -} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java deleted file mode 100644 index 969854f..0000000 --- a/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -public class FakeImageResource extends RefCountedMediaResource { - private boolean mClosed = false; - private boolean mCached = false; - private final int mSize; - private final String mImageId; - - public FakeImageResource(final int size, final String imageId) { - super(null); - mSize = size; - mImageId = imageId; - } - - public boolean isClosed() { - return mClosed; - } - - public String getImageId() { - return mImageId; - } - - public void setCached(final boolean cached) { - mCached = cached; - } - - public boolean getCached() { - return mCached; - } - - @Override - public int getMediaSize() { - return mSize; - } - - @Override - protected void close() { - mClosed = true; - } -} diff --git a/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java deleted file mode 100644 index 34b3a55..0000000 --- a/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -public class FakeMediaCacheManager extends MediaCacheManager { - // List of available fake cache ids. - public static final int FAKE_IMAGE_CACHE = 1; - public static final int FAKE_BATCH_IMAGE_CACHE = 2; - - @Override - public MediaCache createMediaCacheById(final int id) { - switch (id) { - case FAKE_IMAGE_CACHE: - // Make a cache of only 3 KB of data. - return new MediaCache(3, FAKE_IMAGE_CACHE, "FakeImageCache"); - - case FAKE_BATCH_IMAGE_CACHE: - return new MediaCache(10, FAKE_BATCH_IMAGE_CACHE, - "FakeBatchImageCache"); - } - return null; - } -} diff --git a/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java deleted file mode 100644 index 2cfec7d..0000000 --- a/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.content.ContentResolver; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.util.ImageUtils; - -import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; -import org.mockito.Mockito; -import org.mockito.Spy; - -import java.io.IOException; - -@SmallTest -public class ImageRequestTest extends BugleTestCase { - private static final int DOWNSAMPLE_IMAGE_SIZE = 2; - - @Spy protected ImageUtils spyImageUtils; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()) - .withMemoryCacheManager(new MemoryCacheManager()) - .withMediaCacheManager(new BugleMediaCacheManager()); - spyImageUtils = Mockito.spy(new ImageUtils()); - ImageUtils.set(spyImageUtils); - } - - public void testLoadImageUnspecifiedSize() { - final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + - getContext().getPackageName() + "/" + R.drawable.ic_audio_light; - final Uri uri = Uri.parse(uriString); - final UriImageRequest imageRequest = new UriImageRequest(getContext(), - new UriImageRequestDescriptor(uri)); - try { - final ImageResource imageResource = imageRequest.loadMediaBlocking(null); - final ArgumentCaptor options = - ArgumentCaptor.forClass(BitmapFactory.Options.class); - Mockito.verify(spyImageUtils).calculateInSampleSize( - options.capture(), - Matchers.eq(ImageRequest.UNSPECIFIED_SIZE), - Matchers.eq(ImageRequest.UNSPECIFIED_SIZE)); - assertEquals(1, options.getValue().inSampleSize); - assertNotNull(imageResource); - assertNotNull(imageResource.getBitmap()); - - // Make sure there's no scaling on the bitmap. - final int bitmapWidth = imageResource.getBitmap().getWidth(); - final int bitmapHeight = imageResource.getBitmap().getHeight(); - assertEquals(options.getValue().outWidth, bitmapWidth); - assertEquals(options.getValue().outHeight, bitmapHeight); - } catch (final IOException e) { - fail("IO exception while trying to load image resource"); - } - } - - public void testLoadImageWithDownsampling() { - final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + - getContext().getPackageName() + "/" + R.drawable.ic_audio_light; - final Uri uri = Uri.parse(uriString); - final UriImageRequest imageRequest = new UriImageRequest(getContext(), - new UriImageRequestDescriptor(uri, DOWNSAMPLE_IMAGE_SIZE, DOWNSAMPLE_IMAGE_SIZE, - false, true /* isStatic */, false /* cropToCircle */, - ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */, - ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */)); - try { - final ImageResource imageResource = imageRequest.loadMediaBlocking(null); - final ArgumentCaptor options = - ArgumentCaptor.forClass(BitmapFactory.Options.class); - Mockito.verify(spyImageUtils).calculateInSampleSize( - options.capture(), - Matchers.eq(DOWNSAMPLE_IMAGE_SIZE), Matchers.eq(DOWNSAMPLE_IMAGE_SIZE)); - assertNotSame(1, options.getValue().inSampleSize); - assertNotNull(imageResource); - assertNotNull(imageResource.getBitmap()); - - // Make sure there's down sampling on the bitmap. - final int bitmapWidth = imageResource.getBitmap().getWidth(); - final int bitmapHeight = imageResource.getBitmap().getHeight(); - assertTrue(bitmapWidth >= DOWNSAMPLE_IMAGE_SIZE && - bitmapHeight >= DOWNSAMPLE_IMAGE_SIZE && - (bitmapWidth <= DOWNSAMPLE_IMAGE_SIZE * 4 || - bitmapHeight <= DOWNSAMPLE_IMAGE_SIZE * 4)); - } catch (final IOException e) { - fail("IO exception while trying to load image resource"); - } - } -} diff --git a/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java deleted file mode 100644 index d214067..0000000 --- a/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener; - -import java.util.concurrent.CountDownLatch; - -@SmallTest -public class MediaResourceManagerTest extends BugleTestCase { - private static final int KB = 1024; - - // Loaded image resource from the MediaResourceManager callback. - private FakeImageResource mImageResource; - private BindableMediaRequest mImageRequest; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()) - .withMemoryCacheManager(new MemoryCacheManager()) - .withMediaCacheManager(new FakeMediaCacheManager()); - } - - public void testLoadFromCache() { - final MediaResourceManager mediaResourceManager = - new MediaResourceManager(); - MediaCacheManager.get().reclaim(); - assertNotNull(mediaResourceManager); - - // Load one image of 1KB - loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); - assertEquals("image1", mImageResource.getImageId()); - final FakeImageResource loadedResource = mImageResource; - - // Load the same image. - loadImage(mediaResourceManager, "image1", 1 * KB, true /* shouldBeCached */, false); - assertEquals(loadedResource, mImageResource); - } - - public void testCacheEviction() { - final MediaResourceManager mediaResourceManager = - new MediaResourceManager(); - MediaCacheManager.get().reclaim(); - assertNotNull(mediaResourceManager); - - // Load one image of 1KB - loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); - assertEquals("image1", mImageResource.getImageId()); - - // Load another image - loadImage(mediaResourceManager, "image2", 2 * KB, false /* shouldBeCached */, false); - assertEquals("image2", mImageResource.getImageId()); - - // Load another image. This should fill the cache and cause eviction of image1. - loadImage(mediaResourceManager, "image3", 2 * KB, false /* shouldBeCached */, false); - assertEquals("image3", mImageResource.getImageId()); - - // Load image1. It shouldn't be cached any more. - loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); - assertEquals("image1", mImageResource.getImageId()); - } - - public void testReclaimMemoryFromMediaCache() { - final MediaResourceManager mediaResourceManager = - new MediaResourceManager(); - MediaCacheManager.get().reclaim(); - assertNotNull(mediaResourceManager); - - // Load one image of 1KB - loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); - assertEquals("image1", mImageResource.getImageId()); - - // Purge everything from the cache, now the image should no longer be cached. - MediaCacheManager.get().reclaim(); - - // The image resource should have no ref left. - assertEquals(0, mImageResource.getRefCount()); - assertTrue(mImageResource.isClosed()); - loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false); - assertEquals("image1", mImageResource.getImageId()); - } - - public void testLoadInvalidImage() { - final MediaResourceManager mediaResourceManager = - new MediaResourceManager(); - MediaCacheManager.get().reclaim(); - assertNotNull(mediaResourceManager); - - // Test the failure case with invalid resource. - loadImage(mediaResourceManager, FakeImageRequest.INVALID_KEY, 1 * KB, false, - true /* shouldFail */); - } - - public void testLoadImageSynchronously() { - final MediaResourceManager mediaResourceManager = - new MediaResourceManager(); - MediaCacheManager.get().reclaim(); - assertNotNull(mediaResourceManager); - - // Test a normal sync load. - final FakeImageRequest request = new FakeImageRequest("image1", 1 * KB); - final FakeImageResource resource = mediaResourceManager.requestMediaResourceSync(request); - assertNotNull(resource); - assertFalse(resource.isClosed()); - assertNotSame(0, resource.getRefCount()); - resource.release(); - - // Test a failed sync load. - final FakeImageRequest invalidRequest = - new FakeImageRequest(FakeImageRequest.INVALID_KEY, 1 * KB); - assertNull(mediaResourceManager.requestMediaResourceSync(invalidRequest)); - } - - private void loadImage(final MediaResourceManager manager, final String key, - final int size, final boolean shouldBeCached, final boolean shouldFail) { - try { - final CountDownLatch signal = new CountDownLatch(1); - mImageRequest = AsyncMediaRequestWrapper.createWith(new FakeImageRequest(key, size), - createAssertListener(shouldBeCached, shouldFail, signal)); - mImageRequest.bind("1"); - manager.requestMediaResourceAsync(mImageRequest); - - // Wait for the asynchronous callback before proceeding. - signal.await(); - } catch (final InterruptedException e) { - fail("Something interrupted the signal await."); - } - } - - private MediaResourceLoadListener createAssertListener( - final boolean shouldBeCached, final boolean shouldFail, final CountDownLatch signal) { - return new MediaResourceLoadListener() { - @Override - public void onMediaResourceLoaded(final MediaRequest request, - final FakeImageResource resource, final boolean isCached) { - assertEquals(mImageRequest, request); - assertNotNull(resource); - assertFalse(resource.isClosed()); - assertNotSame(0, resource.getRefCount()); - assertFalse(shouldFail); - assertEquals(shouldBeCached, resource.getCached()); - resource.setCached(true); - mImageResource = resource; - signal.countDown(); - } - - @Override - public void onMediaResourceLoadError( - final MediaRequest request, final Exception exception) { - assertTrue(shouldFail); - mImageResource = null; - signal.countDown(); - }}; - } -} diff --git a/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java b/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java deleted file mode 100644 index 5ea6aa7..0000000 --- a/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; - - -/** - * Purpose of this class is providing a workaround for https://b/14561718 - */ -public class ActivityInstrumentationTestCaseIntent extends Intent { - public ActivityInstrumentationTestCaseIntent(Context packageContext, Class cls) { - super(packageContext, cls); - } - @Override - public Intent setComponent(ComponentName component) { - // Ignore the ComponentName set, as the one ActivityUnitTest does is wrong (and actually - // unnecessary). - return this; - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java deleted file mode 100644 index 60cddff..0000000 --- a/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Activity; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.TestUtil; - -/** - * Helper class that extends ActivityInstrumentationTestCase2 to provide some extra common - * initialization (eg. Mockito boilerplate). - */ -public class BugleActivityInstrumentationTestCase - extends android.test.ActivityInstrumentationTestCase2 { - - static { - // Set flag during loading of test cases to prevent application initialization starting - BugleTestCase.setTestsRunning(); - } - - public BugleActivityInstrumentationTestCase(final Class activityClass) { - super(activityClass); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setActivityInitialTouchMode(false); - TestUtil.testSetup(getInstrumentation().getTargetContext(), this); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - TestUtil.testTeardown(this); - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/BugleActivityTest.java b/tests/src/com/android/messaging/ui/BugleActivityTest.java deleted file mode 100644 index 05e32fa..0000000 --- a/tests/src/com/android/messaging/ui/BugleActivityTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.DataModel; - -import org.mockito.Mock; -import org.mockito.Mockito; - -public abstract class BugleActivityTest extends BugleActivityUnitTestCase { - @Mock protected DataModel mDataModel; - - public BugleActivityTest() { - super(BugleActionBarActivity.class); - } - - @SuppressWarnings("unchecked") - @Override - protected void setUp() throws Exception { - super.setUp(); - - // Create activity - final ActivityInstrumentationTestCaseIntent intent = - new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(), - TestActivity.class); - startActivity(intent, null, null); - - FakeFactory.register(getInstrumentation().getTargetContext()) - .withDataModel(mDataModel); - } - - public void testOnResumeDataModelCallback() { - getInstrumentation().callActivityOnStart(getActivity()); - getInstrumentation().callActivityOnResume(getActivity()); - Mockito.verify(mDataModel).onActivityResume(); - } -} diff --git a/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java deleted file mode 100644 index dcbd785..0000000 --- a/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.app.Activity; -import android.view.ContextThemeWrapper; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.R; -import com.android.messaging.TestUtil; - -/** - * Base class for activity unit test cases, provides boilerplate setup/teardown. - */ -public abstract class BugleActivityUnitTestCase extends - android.test.ActivityUnitTestCase { - - static { - // Set flag during loading of test cases to prevent application initialization starting - BugleTestCase.setTestsRunning(); - } - - public BugleActivityUnitTestCase(final Class activityClass) { - super(activityClass); - } - - @SuppressWarnings("unchecked") - @Override - protected void setUp() throws Exception { - super.setUp(); - TestUtil.testSetup(getInstrumentation().getTargetContext(), this); - - setActivityContext(new ContextThemeWrapper(getInstrumentation().getTargetContext(), - R.style.Theme_AppCompat_Light_DarkActionBar)); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - TestUtil.testTeardown(this); - } -} diff --git a/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java b/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java deleted file mode 100644 index 011cb82..0000000 --- a/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.support.v4.view.ViewPager; -import android.view.View; -import android.view.ViewGroup; -import android.widget.SimpleCursorAdapter; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; - -public class CustomHeaderViewPagerTest extends ViewTest { - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getInstrumentation().getTargetContext()); - } - - public void testBindFirstLevel() { - final CustomHeaderViewPager view = new CustomHeaderViewPager(getActivity(), null); - final SimpleCursorAdapter adapter = - new SimpleCursorAdapter(getActivity(), 0, null, null, null, 0); - final CustomHeaderPagerViewHolder[] viewHolders = { - new FakeListViewHolder(getActivity(), adapter), - new FakeListViewHolder(getActivity(), adapter) - }; - - view.setViewHolders(viewHolders); - final ViewPager pager = (ViewPager) view.findViewById(R.id.pager); - final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.tab_strip); - final ViewPagerTabStrip realTab = (ViewPagerTabStrip) tabStrip.getChildAt(0); - - assertEquals(2, realTab.getChildCount()); - View headerTitleButton = realTab.getChildAt(1); - // Click on the first page. Now the view pager should switch to that page accordingly. - clickButton(headerTitleButton); - assertEquals(1, pager.getCurrentItem()); - } - - @Override - protected int getLayoutIdForView() { - // All set up should be done by creating a CustomHeaderViewPager which handles inflating - // the layout - return 0; - } -} diff --git a/tests/src/com/android/messaging/ui/FakeListViewHolder.java b/tests/src/com/android/messaging/ui/FakeListViewHolder.java deleted file mode 100644 index d4de885..0000000 --- a/tests/src/com/android/messaging/ui/FakeListViewHolder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui; - -import android.content.Context; -import android.widget.CursorAdapter; - -import com.android.messaging.R; - -/** - * A fake {@link CustomHeaderPagerListViewHolder} for CustomHeaderViewPager tests only. - */ -public class FakeListViewHolder extends CustomHeaderPagerListViewHolder { - public FakeListViewHolder(final Context context, final CursorAdapter adapter) { - super(context, adapter); - } - - @Override - protected int getLayoutResId() { - return 0; - } - - @Override - protected int getPageTitleResId() { - return android.R.string.untitled; - } - - @Override - protected int getEmptyViewResId() { - return R.id.empty_view; - } - - @Override - protected int getListViewResId() { - return android.R.id.list; - } - - @Override - protected int getEmptyViewTitleResId() { - return R.string.contact_list_empty_text; - } - - @Override - protected int getEmptyViewImageResId() { - return R.drawable.ic_oobe_freq_list; - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/FragmentTestCase.java b/tests/src/com/android/messaging/ui/FragmentTestCase.java deleted file mode 100644 index eb65dc6..0000000 --- a/tests/src/com/android/messaging/ui/FragmentTestCase.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.res.Configuration; -import android.view.View; - -/** - * Helper class that extends Bugle.ui.ActivityInstrumentationTestCase to provide common behavior - * across fragment tests. - */ -public abstract class FragmentTestCase - extends BugleActivityInstrumentationTestCase { - - protected T mFragment; - protected Class mFragmentClass; - - public FragmentTestCase(final Class fragmentClass) { - super(TestActivity.class); - mFragmentClass = fragmentClass; - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - protected T getFragment() { - // Fragment creation deferred (typically until test time) so that factory/appcontext is - // ready. - if (mFragment == null) { - try { - mFragment = mFragmentClass.newInstance(); - } catch (final InstantiationException e) { - throw new IllegalStateException("Failed to instantiate fragment"); - } catch (final IllegalAccessException e) { - throw new IllegalStateException("Failed to instantiate fragment"); - } - } - - return mFragment; - } - - protected void attachFragment() { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final FragmentManager fragmentManager = getActivity().getFragmentManager(); - fragmentManager.beginTransaction().add(mFragment, null /* tag */).commit(); - } - }); - - getInstrumentation().waitForIdleSync(); - } - - @Override - protected void tearDown() throws Exception { - // In landscape mode, sleep for a second first. - // The reason is: our UI tests don't wait for the UI thread to finish settling down - // before exiting (because they can't know when the UI thread is done). In portrait mode, - // things generally work fine here -- the UI thread is done by the time the test is done. - // In landscape mode, though, since the launcher is in portrait mode, there is a lot of - // extra work that happens in our UI when the app launches into landscape mode, and the - // UI is often not done by the time the test finishes running. So then our teardown - // nulls out the Factory, and then the UI keeps running and derefs the null factory, - // and things blow up. - // So ... as a cheap hack, sleep for one second before finishing the teardown of UI - // tests, but only do it in landscape mode (so that developers running it in portrait - // mode can still run the tests faster). - if (this.getInstrumentation().getTargetContext().getResources(). - getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - super.tearDown(); - } - - protected void clickButton(final View view) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - view.performClick(); - } - }); - getInstrumentation().waitForIdleSync(); - } - - protected void setFocus(final View view, final boolean focused) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (focused) { - view.requestFocus(); - } else { - view.clearFocus(); - } - } - }); - getInstrumentation().waitForIdleSync(); - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java b/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java deleted file mode 100644 index cf9a647..0000000 --- a/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - - -import android.content.Context; -import android.net.Uri; -import android.test.suitebuilder.annotation.MediumTest; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.messaging.FakeFactory; -import com.android.messaging.datamodel.data.MessagePartData; - -import java.util.Arrays; -import java.util.Collections; - -@MediumTest -public class MultiAttachmentLayoutTest extends ViewTest { - @Override - protected void setUp() throws Exception { - super.setUp(); - final Context context = getInstrumentation().getTargetContext(); - FakeFactory.register(context); - } - - @Override - protected MultiAttachmentLayout getView() { - if (mView == null) { - // View creation deferred (typically until test time) so that factory/appcontext is - // ready. - mView = new MultiAttachmentLayout(getActivity(), null); - mView.setLayoutParams(new ViewGroup.LayoutParams(100, 100)); - } - return mView; - } - - protected void verifyContent( - final MultiAttachmentLayout view, - final int imageCount, - final int plusCount) { - final int count = view.getChildCount(); - int actualImageCount = 0; - final boolean needPlusText = plusCount > 0; - boolean hasPlusText = false; - for (int i = 0; i < count; i++) { - final View child = view.getChildAt(i); - if (child instanceof AsyncImageView) { - actualImageCount++; - } else if (child instanceof TextView) { - assertTrue(plusCount > 0); - assertTrue(((TextView) child).getText().toString().contains("" + plusCount)); - hasPlusText = true; - } else { - // Nothing other than image and overflow text view should appear in this layout. - fail("unexpected view in layout. view = " + child); - } - } - assertEquals(imageCount, actualImageCount); - assertEquals(needPlusText, hasPlusText); - } - - public void testBindTwoAttachments() { - final MultiAttachmentLayout view = getView(); - final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri1"), 100, 100); - final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri2"), 100, 100); - - view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2), - null /* transitionRect */, 2); - verifyContent(view, 2, 0); - } - - public void testBindFiveAttachments() { - final MultiAttachmentLayout view = getView(); - final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri1"), 100, 100); - final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri2"), 100, 100); - final MessagePartData testAttachment3 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri3"), 100, 100); - final MessagePartData testAttachment4 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri4"), 100, 100); - final MessagePartData testAttachment5 = MessagePartData.createMediaMessagePart( - "image/jpeg", Uri.parse("content://uri5"), 100, 100); - - view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2, testAttachment3, - testAttachment4, testAttachment5), null /* transitionRect */, 5); - verifyContent(view, 4, 1); - } - - public void testBindTwice() { - // Put the above two tests together so we can simulate binding twice. - testBindTwoAttachments(); - testBindFiveAttachments(); - } - - private Iterable createAttachmentList(final MessagePartData... attachments) { - return Collections.unmodifiableList(Arrays.asList(attachments)); - } - - @Override - protected int getLayoutIdForView() { - return 0; // We construct the view with getView(). - } -} \ No newline at end of file diff --git a/tests/src/com/android/messaging/ui/ViewTest.java b/tests/src/com/android/messaging/ui/ViewTest.java deleted file mode 100644 index c4e8431..0000000 --- a/tests/src/com/android/messaging/ui/ViewTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui; - -import android.view.View; - - -/** - * Base class for view tests. Derived class just has to provide a layout id. Tests can then just - * call getView() to get a created view and test its behavior. - */ -public abstract class ViewTest extends BugleActivityUnitTestCase { - public ViewTest() { - super(TestActivity.class); - } - - protected T mView; - - @SuppressWarnings("unchecked") - @Override - protected void setUp() throws Exception { - super.setUp(); - - // Create activity - final ActivityInstrumentationTestCaseIntent intent = - new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(), - TestActivity.class); - startActivity(intent, null, null); - } - - @SuppressWarnings("unchecked") - protected T getView() { - if (mView == null) { - // View creation deferred (typically until test time) so that factory/appcontext is - // ready. - mView = (T) getActivity().getLayoutInflater().inflate(getLayoutIdForView(), null); - } - return mView; - } - - protected void clickButton(final View view) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - view.performClick(); - } - }); - getInstrumentation().waitForIdleSync(); - } - - protected abstract int getLayoutIdForView(); -} diff --git a/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java b/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java deleted file mode 100644 index 30c711b..0000000 --- a/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.attachmentchooser; - -import android.app.Fragment; -import android.test.suitebuilder.annotation.LargeTest; -import android.widget.CheckBox; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.FragmentTestCase; -import com.android.messaging.ui.TestActivity; -import com.android.messaging.ui.TestActivity.FragmentEventListener; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment; -import com.android.messaging.ui.attachmentchooser.AttachmentGridItemView; -import com.android.messaging.ui.attachmentchooser.AttachmentGridView; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost; -import com.android.messaging.ui.conversationlist.ConversationListFragment; - -import org.mockito.ArgumentMatcher; -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - - -/** - * Unit tests for {@link ConversationListFragment}. - */ -@LargeTest -public class AttachmentChooserFragmentTest extends FragmentTestCase { - - @Mock protected DataModel mockDataModel; - @Mock protected DraftMessageData mockDraftMessageData; - @Mock protected AttachmentChooserFragmentHost mockHost; - - private static final String CONVERSATION_ID = "cid"; - - /** A custom argument matcher that checks whether the set argument passed in is a set - * with identical attachment data as the given set. - */ - private class IsSetOfGivenAttachments extends ArgumentMatcher> { - private final Set mGivenParts; - public IsSetOfGivenAttachments(final Set givenParts) { - mGivenParts = givenParts; - } - - @Override - public boolean matches(final Object set) { - @SuppressWarnings("unchecked") - final Set actualSet = (Set) set; - if (actualSet.size() != mGivenParts.size()) { - return false; - } - return mGivenParts.containsAll(actualSet) && actualSet.containsAll(mGivenParts); - } - } - - public AttachmentChooserFragmentTest() { - super(AttachmentChooserFragment.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(this.getInstrumentation().getTargetContext()) - .withDataModel(mockDataModel); - } - - private void loadWith(final List attachments) { - Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.doReturn(mockDraftMessageData) - .when(mockDataModel) - .createDraftMessageData(Mockito.anyString()); - Mockito.doReturn(attachments) - .when(mockDraftMessageData) - .getReadOnlyAttachments(); - Mockito.when(mockDataModel.createDraftMessageData( - Matchers.anyString())) - .thenReturn(mockDraftMessageData); - - // Create fragment synchronously to avoid need for volatile, synchronization etc. - final AttachmentChooserFragment fragment = getFragment(); - // Binding to model happens when attaching fragment to activity, so hook into test - // activity to do so. - getActivity().setFragmentEventListener(new FragmentEventListener() { - @Override - public void onAttachFragment(final Fragment attachedFragment) { - if (fragment == attachedFragment) { - fragment.setConversationId(CONVERSATION_ID); - } - } - }); - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - fragment.setHost(mockHost); - getActivity().setFragment(fragment); - Mockito.verify(mockDataModel).createDraftMessageData( - Mockito.matches(CONVERSATION_ID)); - Mockito.verify(mockDraftMessageData).loadFromStorage( - Matchers.eq(fragment.mBinding), Matchers.eq((MessageData) null), - Matchers.eq(false)); - } - }); - // Now load the cursor - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - fragment.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); - } - }); - getInstrumentation().waitForIdleSync(); - } - - public void testUnselect() { - final List attachments = TestDataFactory.getTestDraftAttachments(); - loadWith(attachments); - final AttachmentGridView attachmentGridView = (AttachmentGridView) - getFragment().getView().findViewById(R.id.grid); - assertEquals("bad view count", attachments.size(), - attachmentGridView.getAdapter().getCount()); - - final AttachmentGridItemView itemView = (AttachmentGridItemView) - attachmentGridView.getChildAt(0); - assertEquals(attachmentGridView, itemView.testGetHostInterface()); - final CheckBox checkBox = (CheckBox) itemView.findViewById(R.id.checkbox); - assertEquals(true, checkBox.isChecked()); - assertEquals(true, attachmentGridView.isItemSelected(itemView.mAttachmentData)); - clickButton(checkBox); - assertEquals(false, checkBox.isChecked()); - assertEquals(false, attachmentGridView.isItemSelected(itemView.mAttachmentData)); - - final AttachmentGridItemView itemView2 = (AttachmentGridItemView) - attachmentGridView.getChildAt(1); - final CheckBox checkBox2 = (CheckBox) itemView2.findViewById(R.id.checkbox); - clickButton(checkBox2); - - getFragment().confirmSelection(); - final MessagePartData[] attachmentsToRemove = new MessagePartData[] { - itemView.mAttachmentData, itemView2.mAttachmentData }; - Mockito.verify(mockDraftMessageData).removeExistingAttachments(Matchers.argThat( - new IsSetOfGivenAttachments(new HashSet<>(Arrays.asList(attachmentsToRemove))))); - Mockito.verify(mockDraftMessageData).saveToStorage(Matchers.eq(getFragment().mBinding)); - Mockito.verify(mockHost).onConfirmSelection(); - } -} diff --git a/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java b/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java deleted file mode 100644 index de4c583..0000000 --- a/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.view.View; -import android.widget.TextView; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.data.ContactListItemData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.ContactIconView; -import com.android.messaging.ui.ViewTest; - -import org.mockito.Mock; -import org.mockito.Mockito; - -public class ContactListItemViewTest extends ViewTest { - - @Mock ContactListItemView.HostInterface mockHost; - - @Override - protected void setUp() throws Exception { - super.setUp(); - final Context context = getInstrumentation().getTargetContext(); - FakeFactory.register(context) - .withDataModel(new FakeDataModel(context)); - } - - protected void verifyAddedContactForData(final ContactListItemData data, - final ContactListItemView view) { - Mockito.verify(mockHost).onContactListItemClicked(data, view); - } - - protected void verifyContent( - final ContactListItemView view, - final String contactName, - final String contactDetail, - final String avatarUrl, - final boolean showAvatar) { - final TextView contactNameView = (TextView) view.findViewById(R.id.contact_name); - final TextView contactDetailView = (TextView) view.findViewById(R.id.contact_details); - final ContactIconView avatarView = (ContactIconView) view.findViewById(R.id.contact_icon); - - assertNotNull(contactNameView); - assertEquals(contactName, contactNameView.getText()); - assertNotNull(contactDetail); - assertEquals(contactDetail, contactDetailView.getText()); - assertNotNull(avatarView); - if (showAvatar) { - assertTrue(avatarView.mImageRequestBinding.isBound()); - assertEquals(View.VISIBLE, avatarView.getVisibility()); - } else { - assertFalse(avatarView.mImageRequestBinding.isBound()); - assertEquals(View.INVISIBLE, avatarView.getVisibility()); - } - } - - public void testBindFirstLevel() { - final ContactListItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); - final int row = TestDataFactory.CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX; - cursor.moveToPosition(row); - view.bind(cursor, mockHost, false, null); - verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row), - (String) cursor.getAt(Phone.NUMBER, row), - (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), true); - } - - public void testBindSecondLevel() { - final ContactListItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); - final int row = TestDataFactory.CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX; - cursor.moveToPosition(row); - view.bind(cursor, mockHost, false, null); - verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row), - (String) cursor.getAt(Phone.NUMBER, row), - (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), false); - } - - public void testClickAddedContact() { - final ContactListItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); - cursor.moveToFirst(); - - view.bind(cursor, mockHost, false, null); - view.performClick(); - verifyAddedContactForData(view.mData, view); - } - - public void testBindTwice() { - final ContactListItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getAllContactListCursor(); - - cursor.moveToFirst(); - view.bind(cursor, mockHost, false, null); - - cursor.moveToNext(); - view.bind(cursor, mockHost, false, null); - verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, 1), - (String) cursor.getAt(Phone.NUMBER, 1), - (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, 1), true); - } - - @Override - protected int getLayoutIdForView() { - return R.layout.contact_list_item_view; - } -} diff --git a/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java b/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java deleted file mode 100644 index 5b1503b..0000000 --- a/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.contact; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.view.ViewPager; -import android.test.suitebuilder.annotation.LargeTest; -import android.view.View; -import android.widget.ListView; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.action.ActionTestHelpers; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService; -import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog; -import com.android.messaging.datamodel.action.GetOrCreateConversationAction; -import com.android.messaging.datamodel.data.ContactPickerData; -import com.android.messaging.datamodel.data.ParticipantData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.CustomHeaderViewPagerAdapter; -import com.android.messaging.ui.FragmentTestCase; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - - -/** - * Unit tests for {@link ContactPickerFragment}. - */ -@LargeTest -public class ContactPickerFragmentTest - extends FragmentTestCase { - - @Mock protected ContactPickerData mMockContactPickerData; - @Mock protected UIIntents mMockUIIntents; - @Mock protected ContactPickerFragmentHost mockHost; - protected FakeDataModel mFakeDataModel; - private ActionTestHelpers.StubActionService mService; - - public ContactPickerFragmentTest() { - super(ContactPickerFragment.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - final Context context = getInstrumentation().getTargetContext(); - mService = new StubActionService(); - mFakeDataModel = new FakeDataModel(context) - .withContactPickerData(mMockContactPickerData) - .withActionService(mService); - FakeFactory.register(context) - .withDataModel(mFakeDataModel) - .withUIIntents(mMockUIIntents); - } - - /** - * Helper method to initialize the ContactPickerFragment and its data. - */ - private ContactPickerFragmentTest initFragment(final int initialMode) { - Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) - .thenReturn(true); - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final ContactPickerFragment fragment = getFragment(); - fragment.setHost(mockHost); - fragment.setContactPickingMode(initialMode, false); - - getActivity().setFragment(fragment); - Mockito.verify(mMockContactPickerData).init(fragment.getLoaderManager(), - fragment.mBinding); - } - }); - getInstrumentation().waitForIdleSync(); - return this; - } - - /** - * Bind the datamodel with all contacts cursor to populate the all contacts list in the - * fragment. - */ - private ContactPickerFragmentTest loadWithAllContactsCursor(final Cursor cursor) { - Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) - .thenReturn(true); - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - getFragment().onAllContactsCursorUpdated(cursor); - } - }); - getInstrumentation().waitForIdleSync(); - return this; - } - - /** - * Bind the datamodel with frequent contacts cursor to populate the contacts list in the - * fragment. - */ - private ContactPickerFragmentTest loadWithFrequentContactsCursor(final Cursor cursor) { - Mockito.when(mMockContactPickerData.isBound(Matchers.anyString())) - .thenReturn(true); - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - getFragment().onFrequentContactsCursorUpdated(cursor); - } - }); - getInstrumentation().waitForIdleSync(); - return this; - } - - /** - * Test the initial state of the fragment before loading data. - */ - public void testInitialState() { - initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT); - - // Make sure that the frequent contacts view is shown by default. - final ViewPager pager = (ViewPager) getFragment().getView().findViewById(R.id.pager); - final View currentPagedView = pager.getChildAt(pager.getCurrentItem()); - final View frequentContactsView = ((CustomHeaderViewPagerAdapter) pager.getAdapter()) - .getViewHolder(0).getView(null); - assertEquals(frequentContactsView, currentPagedView); - } - - /** - * Verifies that list view gets correctly populated given a cursor. - */ - public void testLoadAllContactsList() { - final Cursor cursor = TestDataFactory.getAllContactListCursor(); - initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) - .loadWithAllContactsCursor(cursor); - final ListView listView = (ListView) getFragment().getView() - .findViewById(R.id.all_contacts_list); - assertEquals(cursor.getCount(), listView.getCount()); - } - - /** - * Verifies that list view gets correctly populated given a cursor. - */ - public void testLoadFrequentContactsList() { - final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); - initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) - .loadWithFrequentContactsCursor(cursor); - final ListView listView = (ListView) getFragment().getView() - .findViewById(R.id.frequent_contacts_list); - assertEquals(cursor.getCount(), listView.getCount()); - } - - public void testPickInitialContact() { - final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); - initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT) - .loadWithFrequentContactsCursor(cursor); - final ListView listView = (ListView) getFragment().getView() - .findViewById(R.id.frequent_contacts_list); - // Click on the first contact to add it. - final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0); - clickButton(cliv); - final ContactRecipientAutoCompleteView chipsView = (ContactRecipientAutoCompleteView) - getFragment().getView() - .findViewById(R.id.recipient_text_view); - // Verify the contact is added to the chips view. - final List participants = - chipsView.getRecipientParticipantDataForConversationCreation(); - assertEquals(1, participants.size()); - assertEquals(cliv.mData.getDestination(), participants.get(0).getSendDestination()); - assertTrue(mService.getCalls().get(0).action instanceof GetOrCreateConversationAction); - } - - public void testLeaveChipsMode() { - final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); - initFragment(ContactPickerFragment.MODE_CHIPS_ONLY) - .loadWithFrequentContactsCursor(cursor); - // Click on the add more participants button - // TODO: Figure out a way to click on the add more participants button now that - // it's part of the menu. - // final ImageButton AddMoreParticipantsButton = (ImageButton) getFragment().getView() - // .findViewById(R.id.add_more_participants_button); - // clickButton(AddMoreParticipantsButton); - // Mockito.verify(mockHost).onInitiateAddMoreParticipants(); - } - - public void testPickMoreContacts() { - final Cursor cursor = TestDataFactory.getFrequentContactListCursor(); - initFragment(ContactPickerFragment.MODE_PICK_MORE_CONTACTS) - .loadWithFrequentContactsCursor(cursor); - final ListView listView = (ListView) getFragment().getView() - .findViewById(R.id.frequent_contacts_list); - // Click on the first contact to add it. - final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0); - clickButton(cliv); - // Verify that we don't attempt to create a conversation right away. - assertEquals(0, mService.getCalls().size()); - } -} diff --git a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java deleted file mode 100644 index 2dd2a89..0000000 --- a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.content.Context; -import android.media.MediaPlayer; -import android.test.suitebuilder.annotation.MediumTest; -import android.view.View; -import android.widget.EditText; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask; -import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback; -import com.android.messaging.datamodel.data.MessageData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.ViewTest; -import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.FakeMediaUtil; -import com.android.messaging.util.ImeUtil; - -import org.mockito.ArgumentMatcher; -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; -import java.util.Collections; - -@MediumTest -public class ComposeMessageViewTest extends ViewTest { - private Context mContext; - - @Mock protected DataModel mockDataModel; - @Mock protected DraftMessageData mockDraftMessageData; - @Mock protected BugleGservices mockBugleGservices; - @Mock protected ImeUtil mockImeUtil; - @Mock protected IComposeMessageViewHost mockIComposeMessageViewHost; - @Mock protected MediaPlayer mockMediaPlayer; - @Mock protected ConversationInputManager mockInputManager; - @Mock protected ConversationData mockConversationData; - - Binding mConversationBinding; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mContext = getInstrumentation().getTargetContext(); - FakeFactory.register(mContext) - .withDataModel(mockDataModel) - .withBugleGservices(mockBugleGservices) - .withMediaUtil(new FakeMediaUtil(mockMediaPlayer)); - - Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString()); - mConversationBinding = BindingBase.createBinding(this); - mConversationBinding.bind(mockConversationData); - } - - @Override - protected ComposeMessageView getView() { - final ComposeMessageView view = super.getView(); - view.setInputManager(mockInputManager); - view.setConversationDataModel(BindingBase.createBindingReference(mConversationBinding)); - return view; - } - - @Override - protected int getLayoutIdForView() { - return R.layout.compose_message_view; - } - - public void testSend() { - Mockito.when(mockDraftMessageData.getReadOnlyAttachments()) - .thenReturn(Collections.unmodifiableList(new ArrayList())); - Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(true); - Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(true); - final ComposeMessageView view = getView(); - - final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id", - "Sample Message"); - - Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText()); - Mockito.when(mockDraftMessageData.prepareMessageForSending( - Matchers.>any())) - .thenReturn(message); - Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false); - Mockito.doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - // Synchronously pass the draft check and callback. - ((CheckDraftTaskCallback)invocation.getArguments()[2]).onDraftChecked( - mockDraftMessageData, CheckDraftForSendTask.RESULT_PASSED); - return null; - } - }).when(mockDraftMessageData).checkDraftForAction(Mockito.anyBoolean(), Mockito.anyInt(), - Mockito.any(), - Mockito.>any()); - - view.bind(mockDraftMessageData, mockIComposeMessageViewHost); - - final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text); - final View sendButton = view.findViewById(R.id.send_message_button); - - view.requestDraftMessage(false); - - Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class), - Matchers.any(MessageData.class), Mockito.eq(false)); - - view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); - - assertEquals(message.getMessageText(), composeEditText.getText().toString()); - - sendButton.performClick(); - Mockito.verify(mockIComposeMessageViewHost).sendMessage( - Mockito.argThat(new ArgumentMatcher() { - @Override - public boolean matches(final Object o) { - assertEquals(message.getMessageText(), ((MessageData) o).getMessageText()); - return true; - } - })); - } - - public void testNotDefaultSms() { - Mockito.when(mockDraftMessageData.getReadOnlyAttachments()) - .thenReturn(Collections.unmodifiableList(new ArrayList())); - Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(false); - Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(false); - final ComposeMessageView view = getView(); - - final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id", - "Sample Message"); - - Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText()); - Mockito.when(mockDraftMessageData.prepareMessageForSending( - Matchers.>any())) - .thenReturn(message); - Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false); - - view.bind(mockDraftMessageData, mockIComposeMessageViewHost); - - final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text); - final View sendButton = view.findViewById(R.id.send_message_button); - - view.requestDraftMessage(false); - - Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class), - Matchers.any(MessageData.class), Mockito.eq(false)); - - view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED); - - assertEquals(message.getMessageText(), composeEditText.getText().toString()); - - sendButton.performClick(); - Mockito.verify(mockIComposeMessageViewHost).warnOfMissingActionConditions( - Matchers.any(Boolean.class), Matchers.any(Runnable.class)); - } -} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java deleted file mode 100644 index 7c6903d..0000000 --- a/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.test.suitebuilder.annotation.SmallTest; -import android.text.TextUtils; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.ui.contact.ContactPickerFragment; -import com.android.messaging.ui.conversation.ConversationActivityUiState; -import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost; - -import org.mockito.Mock; -import org.mockito.Mockito; - -@SmallTest -public class ConversationActivityUiStateTest extends BugleTestCase { - @Mock protected ConversationActivityUiStateHost mockListener; - - /** - * Test the Ui state where we start off with the contact picker to pick the first contact. - */ - public void testPickInitialContact() { - final ConversationActivityUiState uiState = new ConversationActivityUiState(null); - uiState.setHost(mockListener); - assertTrue(uiState.shouldShowContactPickerFragment()); - assertFalse(uiState.shouldShowConversationFragment()); - assertEquals(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT, - uiState.getDesiredContactPickingMode()); - uiState.onGetOrCreateConversation("conversation1"); - Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( - Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT), - Mockito.eq( - ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), - Mockito.anyBoolean()); - assertTrue(uiState.shouldShowContactPickerFragment()); - assertTrue(uiState.shouldShowConversationFragment()); - assertTrue(TextUtils.equals("conversation1", uiState.getConversationId())); - assertEquals(ContactPickerFragment.MODE_CHIPS_ONLY, - uiState.getDesiredContactPickingMode()); - } - - /** - * Test the Ui state where we have both the chips view and the conversation view and we - * start message compose. - */ - public void testHybridUiStateStartCompose() { - final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1"); - uiState.testSetUiState( - ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW); - uiState.setHost(mockListener); - - // Start message compose. - uiState.onStartMessageCompose(); - Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( - Mockito.eq( - ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), - Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY), - Mockito.anyBoolean()); - assertFalse(uiState.shouldShowContactPickerFragment()); - assertTrue(uiState.shouldShowConversationFragment()); - } - - /** - * Test the Ui state where we have both the chips view and the conversation view and we - * try to add a participant. - */ - public void testHybridUiStateAddParticipant() { - final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1"); - uiState.testSetUiState( - ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW); - uiState.setHost(mockListener); - - uiState.onAddMoreParticipants(); - Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( - Mockito.eq( - ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW), - Mockito.eq( - ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS), - Mockito.anyBoolean()); - assertTrue(uiState.shouldShowContactPickerFragment()); - assertFalse(uiState.shouldShowConversationFragment()); - assertEquals(ContactPickerFragment.MODE_PICK_MORE_CONTACTS, - uiState.getDesiredContactPickingMode()); - } - - /** - * Test the Ui state where we are trying to add more participants and commit. - */ - public void testCommitAddParticipant() { - final ConversationActivityUiState uiState = new ConversationActivityUiState(null); - uiState.testSetUiState( - ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS); - uiState.setHost(mockListener); - - uiState.onGetOrCreateConversation("conversation1"); - - // After adding more contacts, the terminal state is always conversation only (i.e. we - // don't go back to hybrid mode). - Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged( - Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS), - Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY), - Mockito.anyBoolean()); - assertFalse(uiState.shouldShowContactPickerFragment()); - assertTrue(uiState.shouldShowConversationFragment()); - } -} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java deleted file mode 100644 index c92fbf6..0000000 --- a/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.app.Activity; -import android.app.Fragment; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.test.suitebuilder.annotation.LargeTest; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.MemoryCacheManager; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.datamodel.media.MediaResourceManager; -import com.android.messaging.ui.FragmentTestCase; -import com.android.messaging.ui.PlainTextEditText; -import com.android.messaging.ui.TestActivity.FragmentEventListener; -import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost; -import com.android.messaging.ui.conversationlist.ConversationListFragment; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.ImeUtil; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - - -/** - * Unit tests for {@link ConversationListFragment}. - */ -@LargeTest -public class ConversationFragmentTest extends FragmentTestCase { - - @Mock protected DataModel mockDataModel; - @Mock protected ConversationData mockConversationData; - @Mock protected DraftMessageData mockDraftMessageData; - @Mock protected MediaResourceManager mockMediaResourceManager; - @Mock protected BugleGservices mockBugleGservices; - @Mock protected ConversationFragmentHost mockHost; - @Mock protected MemoryCacheManager mockMemoryCacheManager; - - private ImeUtil mSpiedImeUtil; - - private static final String CONVERSATION_ID = "cid"; - - - public ConversationFragmentTest() { - super(ConversationFragment.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - ImeUtil.clearInstance(); - mSpiedImeUtil = Mockito.spy(new ImeUtil()); - FakeFactory.register(this.getInstrumentation().getTargetContext()) - .withDataModel(mockDataModel) - .withBugleGservices(mockBugleGservices) - .withMemoryCacheManager(mockMemoryCacheManager); - } - - /** - * Helper that will do the 'binding' of ConversationFragmentTest with ConversationData and - * leave fragment in 'ready' state. - * @param cursor - */ - private void loadWith(final Cursor cursor) { - Mockito.when(mockDraftMessageData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.when(mockConversationData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.doReturn(mockDraftMessageData) - .when(mockDataModel) - .createDraftMessageData(Mockito.anyString()); - Mockito.when(mockDataModel.createConversationData( - Matchers.any(Activity.class), - Matchers.any(ConversationDataListener.class), - Matchers.anyString())) - .thenReturn(mockConversationData); - - // Create fragment synchronously to avoid need for volatile, synchronization etc. - final ConversationFragment fragment = getFragment(); - // Binding to model happens when attaching fragment to activity, so hook into test - // activity to do so. - getActivity().setFragmentEventListener(new FragmentEventListener() { - @Override - public void onAttachFragment(final Fragment attachedFragment) { - if (fragment == attachedFragment) { - fragment.setConversationInfo(getActivity(), CONVERSATION_ID, null); - } - } - }); - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - fragment.setHost(mockHost); - getActivity().setFragment(fragment); - Mockito.verify(mockDataModel).createConversationData( - getActivity(), fragment, CONVERSATION_ID); - Mockito.verify(mockConversationData).init(fragment.getLoaderManager(), - fragment.mBinding); - } - }); - // Wait for initial layout pass to work around crash in recycler view - getInstrumentation().waitForIdleSync(); - // Now load the cursor - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - fragment.onConversationMessagesCursorUpdated(mockConversationData, cursor, null, - false); - } - }); - getInstrumentation().waitForIdleSync(); - } - - /** - * Verifies that list view gets correctly populated given a cursor. - */ - public void testLoadListView() { - final Cursor cursor = TestDataFactory.getConversationMessageCursor(); - loadWith(cursor); - final RecyclerView listView = - (RecyclerView) getFragment().getView().findViewById(android.R.id.list); - assertEquals("bad cursor", cursor.getCount(), listView.getAdapter().getItemCount()); - assertEquals("bad cursor count", cursor.getCount(), listView.getChildCount()); - } - - public void testClickComposeMessageView() { - final Cursor cursor = TestDataFactory.getConversationMessageCursor(); - loadWith(cursor); - - final PlainTextEditText composeEditText = (PlainTextEditText) getFragment().getView() - .findViewById(R.id.compose_message_text); - setFocus(composeEditText, false); - Mockito.verify(mockHost, Mockito.never()).onStartComposeMessage(); - setFocus(composeEditText, true); - Mockito.verify(mockHost).onStartComposeMessage(); - } -} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java deleted file mode 100644 index f335785..0000000 --- a/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.conversation; - -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.os.Bundle; -import android.test.suitebuilder.annotation.SmallTest; -import android.widget.EditText; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.ConversationData; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.SubscriptionListData; -import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost; -import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink; -import com.android.messaging.ui.mediapicker.MediaPicker; -import com.android.messaging.util.BugleGservices; -import com.android.messaging.util.ImeUtil; -import com.android.messaging.util.ImeUtil.ImeStateHost; - -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.Spy; - -@SmallTest -public class ConversationInputManagerTest extends BugleTestCase { - @Spy protected ImeUtil spyImeUtil; - @Mock protected BugleGservices mockBugleGservices; - @Mock protected FragmentManager mockFragmentManager; - @Mock protected ConversationInputHost mockConversationInputHost; - @Mock protected ConversationInputSink mockConversationInputSink; - @Mock protected ImeStateHost mockImeStateHost; - @Mock protected ConversationData mockConversationData; - @Mock protected DraftMessageData mockDraftMessageData; - @Mock protected MediaPicker mockMediaPicker; - @Mock protected SubscriptionListData mockSubscriptionListData; - @Mock protected FragmentTransaction mockFragmentTransaction; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getTestContext()) - .withBugleGservices(mockBugleGservices); - spyImeUtil = Mockito.spy(new ImeUtil()); - ImeUtil.set(spyImeUtil); - } - - private ConversationInputManager initNewInputManager(final Bundle savedState) { - // Set up the mocks. - Mockito.when(mockConversationInputHost.getSimSelectorView()) - .thenReturn(new SimSelectorView(getTestContext(), null)); - Mockito.when(mockConversationInputHost.createMediaPicker()).thenReturn(mockMediaPicker); - Mockito.when(mockConversationInputSink.getComposeEditText()) - .thenReturn(new EditText(getTestContext())); - Mockito.doReturn(mockFragmentTransaction).when(mockFragmentTransaction).replace( - Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class), - Mockito.anyString()); - Mockito.when(mockFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG)) - .thenReturn(null); - Mockito.when(mockFragmentManager.beginTransaction()).thenReturn(mockFragmentTransaction); - Mockito.when(mockSubscriptionListData.hasData()).thenReturn(true); - Mockito.when(mockConversationData.getSubscriptionListData()) - .thenReturn(mockSubscriptionListData); - Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString()); - Mockito.doReturn(true).when(mockDraftMessageData).isBound(Mockito.anyString()); - - final Binding dataBinding = BindingBase.createBinding(this); - dataBinding.bind(mockConversationData); - final Binding draftBinding = BindingBase.createBinding(this); - draftBinding.bind(mockDraftMessageData); - final ConversationInputManager inputManager = new ConversationInputManager(getTestContext(), - mockConversationInputHost, mockConversationInputSink, mockImeStateHost, - mockFragmentManager, dataBinding, draftBinding, savedState); - return inputManager; - } - - public void testShowHideInputs() { - final ConversationInputManager inputManager = initNewInputManager(new Bundle()); - Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); - inputManager.showHideMediaPicker(true /* show */, true /* animate */); - Mockito.verify(mockFragmentTransaction).replace( - Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class), - Mockito.anyString()); - Mockito.verify(mockMediaPicker).open(Mockito.anyInt(), Mockito.eq(true /* animate */)); - - assertEquals(true, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - - Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); - inputManager.showHideMediaPicker(false /* show */, true /* animate */); - Mockito.verify(mockMediaPicker).dismiss(Mockito.eq(true /* animate */)); - - assertEquals(false, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - } - - public void testShowTwoInputsSequentially() { - // First show the media picker, then show the IME keyboard. - final ConversationInputManager inputManager = initNewInputManager(new Bundle()); - Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); - inputManager.showHideMediaPicker(true /* show */, true /* animate */); - - assertEquals(true, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - - Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); - inputManager.showHideImeKeyboard(true /* show */, true /* animate */); - - assertEquals(false, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(true, inputManager.isImeKeyboardVisible()); - } - - public void testOnKeyboardShow() { - final ConversationInputManager inputManager = initNewInputManager(new Bundle()); - Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); - inputManager.showHideMediaPicker(true /* show */, true /* animate */); - - assertEquals(true, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - - Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); - inputManager.testNotifyImeStateChanged(true /* imeOpen */); - - assertEquals(false, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(true, inputManager.isImeKeyboardVisible()); - } - - public void testRestoreState() { - final ConversationInputManager inputManager = initNewInputManager(new Bundle()); - Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); - inputManager.showHideMediaPicker(true /* show */, true /* animate */); - - assertEquals(true, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - - Bundle savedInstanceState = new Bundle(); - inputManager.onSaveInputState(savedInstanceState); - - // Now try to restore the state - final ConversationInputManager restoredInputManager = - initNewInputManager(savedInstanceState); - - // Make sure the state is preserved. - assertEquals(true, restoredInputManager.isMediaPickerVisible()); - assertEquals(false, restoredInputManager.isSimSelectorVisible()); - assertEquals(false, restoredInputManager.isImeKeyboardVisible()); - } - - public void testBackPress() { - final ConversationInputManager inputManager = initNewInputManager(new Bundle()); - Mockito.when(mockMediaPicker.isOpen()).thenReturn(true); - inputManager.showHideMediaPicker(true /* show */, true /* animate */); - - assertEquals(true, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - - Mockito.when(mockMediaPicker.isOpen()).thenReturn(false); - assertEquals(true, inputManager.onBackPressed()); - - assertEquals(false, inputManager.isMediaPickerVisible()); - assertEquals(false, inputManager.isSimSelectorVisible()); - assertEquals(false, inputManager.isImeKeyboardVisible()); - } -} diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java deleted file mode 100644 index 6b8b0c0..0000000 --- a/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversation; - -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.Suppress; -import android.view.View; -import android.widget.TextView; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.ViewTest; -import com.android.messaging.ui.conversation.ConversationMessageView; -import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost; -import com.android.messaging.util.Dates; - -import org.mockito.Mock; - -@MediumTest -public class ConversationMessageViewTest extends ViewTest { - @Mock ConversationMessageViewHost mockHost; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getInstrumentation().getTargetContext()); - } - - @Override - protected ConversationMessageView getView() { - final ConversationMessageView view = super.getView(); - view.setHost(mockHost); - return view; - } - - protected void verifyContent(final ConversationMessageView view, final String messageText, - final boolean showTimestamp, final String timestampText) { - - final TextView messageTextView = (TextView) view.findViewById(R.id.message_text); - final TextView statusTextView = (TextView) view.findViewById(R.id.message_status); - - assertNotNull(messageTextView); - assertEquals(messageText, messageTextView.getText()); - - if (showTimestamp) { - assertEquals(View.VISIBLE, statusTextView.getVisibility()); - assertEquals(timestampText, statusTextView.getText()); - } else { - assertEquals(View.GONE, statusTextView.getVisibility()); - } - } - - public void testBind() { - final ConversationMessageView view = getView(); - - final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); - cursor.moveToFirst(); - - view.bind(cursor); - verifyContent(view, TestDataFactory.getMessageText(cursor, 0), true, Dates - .getMessageTimeString((Long) cursor.getAt("received_timestamp", 0)).toString()); - } - - public void testBindTwice() { - final ConversationMessageView view = getView(); - - final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); - cursor.moveToFirst(); - view.bind(cursor); - - cursor.moveToNext(); - view.bind(cursor); - verifyContent(view, TestDataFactory.getMessageText(cursor, 1), true, Dates - .getMessageTimeString((Long) cursor.getAt("received_timestamp", 1)).toString()); - } - - public void testBindLast() { - final ConversationMessageView view = getView(); - - final FakeCursor cursor = TestDataFactory.getConversationMessageCursor(); - final int lastPos = cursor.getCount() - 1; - cursor.moveToPosition(lastPos); - - view.bind(cursor); - verifyContent(view, TestDataFactory.getMessageText(cursor, lastPos), true, Dates - .getMessageTimeString((Long) cursor.getAt("received_timestamp", lastPos)) - .toString()); - } - - @Override - protected int getLayoutIdForView() { - return R.layout.conversation_message_view; - } -} diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java deleted file mode 100644 index f9cc9e1..0000000 --- a/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.test.suitebuilder.annotation.LargeTest; -import android.view.View; -import android.widget.ImageView; -import android.widget.ListView; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.data.ConversationListData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.FragmentTestCase; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.ui.conversationlist.ConversationListFragment; -import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - - -/** - * Unit tests for {@link ConversationListFragment}. - */ -@LargeTest -public class ConversationListFragmentTest - extends FragmentTestCase { - - @Mock protected ConversationListData mMockConversationListData; - @Mock protected ConversationListFragmentHost mMockConversationHostListHost; - @Mock protected UIIntents mMockUIIntents; - protected FakeDataModel mFakeDataModel; - - public ConversationListFragmentTest() { - super(ConversationListFragment.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - final Context context = getInstrumentation().getTargetContext(); - mFakeDataModel = new FakeDataModel(context) - .withConversationListData(mMockConversationListData); - FakeFactory.register(context) - .withDataModel(mFakeDataModel) - .withUIIntents(mMockUIIntents); - } - - /** - * Helper that will do the 'binding' of ConversationListFragmentTest with ConversationListData - * and leave fragment in 'ready' state. - * @param cursor - */ - private void loadWith(final Cursor cursor) { - Mockito.when(mMockConversationListData.isBound(Matchers.anyString())) - .thenReturn(true); - - final ConversationListFragment fragment = getFragment(); - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - fragment.setHost(mMockConversationHostListHost); - getActivity().setFragment(fragment); - Mockito.verify(mMockConversationListData).init(fragment.getLoaderManager(), - fragment.mListBinding); - fragment.onConversationListCursorUpdated(mMockConversationListData, cursor); - } - }); - getInstrumentation().waitForIdleSync(); - } - - /** - * Verifies that list view gets correctly populated given a cursor. - */ - public void testLoadListView() { - final Cursor cursor = TestDataFactory.getConversationListCursor(); - loadWith(cursor); - final RecyclerView listView = - (RecyclerView) getFragment().getView().findViewById(android.R.id.list); - //assertEquals(cursor.getCount(), listView.getCount()); - assertEquals(cursor.getCount(), listView.getChildCount()); - } - - /** - * Verifies that 'empty list' promo is rendered with an empty cursor. - */ - public void testEmptyView() { - loadWith(TestDataFactory.getEmptyConversationListCursor()); - final RecyclerView listView = - (RecyclerView) getFragment().getView().findViewById(android.R.id.list); - final View emptyMessageView = - getFragment().getView().findViewById(R.id.no_conversations_view); - assertEquals(View.VISIBLE, emptyMessageView.getVisibility()); - assertEquals(0, listView.getChildCount()); - } - - /** - * Verifies that the button to start a new conversation works. - */ - public void testStartNewConversation() { - final Cursor cursor = TestDataFactory.getConversationListCursor(); - loadWith(cursor); - final ImageView startNewConversationButton = (ImageView) - getFragment().getView().findViewById(R.id.start_new_conversation_button); - - clickButton(startNewConversationButton); - Mockito.verify(mMockConversationHostListHost).onCreateConversationClick(); - } -} diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java deleted file mode 100644 index be054a8..0000000 --- a/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.conversationlist; - -import android.content.Context; -import android.test.suitebuilder.annotation.MediumTest; -import android.text.TextUtils; -import android.view.View; -import android.widget.TextView; - -import com.android.messaging.Factory; -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.data.ConversationListItemData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.UIIntentsImpl; -import com.android.messaging.ui.ViewTest; -import com.android.messaging.ui.conversationlist.ConversationListItemView; -import com.android.messaging.util.Dates; - -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.Mockito; - -@MediumTest -public class ConversationListItemViewTest extends ViewTest { - - @Mock private ConversationListItemView.HostInterface mockHost; - private FakeCursor mCursor; - - @Override - protected void setUp() throws Exception { - super.setUp(); - final Context context = getInstrumentation().getTargetContext(); - FakeFactory.register(context) - .withDataModel(new FakeDataModel(context)) - .withUIIntents(new UIIntentsImpl()); - mCursor = TestDataFactory.getConversationListCursor(); - } - - - protected void verifyLaunchedConversationForId(final String id, - final ConversationListItemView conversationView) { - // Must be a short click. - final ArgumentMatcher itemDataIdMatcher = - new ArgumentMatcher() { - @Override - public boolean matches(final Object arg) { - return TextUtils.equals(id, ((ConversationListItemData) arg).getConversationId()); - } - }; - Mockito.verify(mockHost).onConversationClicked( - Mockito.argThat(itemDataIdMatcher), Mockito.eq(false), - Mockito.eq(conversationView)); - } - - protected void verifyContent( - final ConversationListItemView view, final FakeCursor cursor, final int index) { - /* ConversationQueryColumns.NAME */ - final String conversationQueryColumnsName = "name"; - final String name = (String) cursor.getAt(conversationQueryColumnsName, index); - - /* ConversationQueryColumns.SNIPPET_TEXT */ - final String conversationQueryColumnsSnippetText = "snippet_text"; - final String snippet = (String) cursor.getAt(conversationQueryColumnsSnippetText, index); - - /* ConversationQueryColumns.SORT_TIMESTAMP */ - final String conversationQueryColumnsSortTimestamp = "sort_timestamp"; - final String timestamp = Dates.getConversationTimeString( - (Long) cursor.getAt(conversationQueryColumnsSortTimestamp, index)).toString(); - - final boolean unread = !isRead(cursor, index); - verifyContent(view, name, snippet, timestamp, unread); - } - - protected void verifyContent( - final ConversationListItemView view, - final String conversationName, - final String snippet, - final String timestamp, - final boolean unread) { - final TextView conversationNameView = - (TextView) view.findViewById(R.id.conversation_name); - final TextView snippetTextView = (TextView) view.findViewById(R.id.conversation_snippet); - final TextView timestampTextView = (TextView) view.findViewById( - R.id.conversation_timestamp); - final AsyncImageView imagePreviewView = - (AsyncImageView) view.findViewById(R.id.conversation_image_preview); - - final Context context = Factory.get().getApplicationContext(); - assertNotNull(conversationNameView); - assertEquals(conversationName, conversationNameView.getText()); - assertNotNull(snippetTextView); - if (unread) { - assertEquals(ConversationListItemView.UNREAD_SNIPPET_LINE_COUNT, - snippetTextView.getMaxLines()); - assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread), - snippetTextView.getCurrentTextColor()); - assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread), - conversationNameView.getCurrentTextColor()); - - } else { - assertEquals(ConversationListItemView.NO_UNREAD_SNIPPET_LINE_COUNT, - snippetTextView.getMaxLines()); - assertEquals(context.getResources().getColor(R.color.conversation_list_item_read), - snippetTextView.getCurrentTextColor()); - assertEquals(context.getResources().getColor(R.color.conversation_list_item_read), - conversationNameView.getCurrentTextColor()); - } - - assertEquals(View.VISIBLE, imagePreviewView.getVisibility()); - assertTrue(snippetTextView.getText().toString().contains(snippet)); - assertEquals(timestamp, timestampTextView.getText()); - } - - protected boolean isRead(final FakeCursor cursor, final int index) { - return 1 == ((Integer) cursor.getAt("read", index)).intValue(); - } - - public void testBindUnread() { - final ConversationListItemView view = getView(); - final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX; - mCursor.moveToPosition(messageIndex); - assertFalse(isRead(mCursor, messageIndex)); - view.bind(mCursor, mockHost); - verifyContent(view, mCursor, messageIndex); - } - - public void testBindRead() { - final ConversationListItemView view = getView(); - - final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX; - mCursor.moveToPosition(messageIndex); - assertTrue(isRead(mCursor, messageIndex)); - view.bind(mCursor, mockHost); - verifyContent(view, mCursor, messageIndex); - } - - public void testClickLaunchesConversation() { - final ConversationListItemView view = getView(); - final View swipeableContainer = view.findViewById(R.id.swipeableContainer); - mCursor.moveToFirst(); - view.bind(mCursor, mockHost); - swipeableContainer.performClick(); - verifyLaunchedConversationForId( - mCursor.getAt("_id" /* ConversationQueryColumns._ID */, 0).toString(), view); - } - - public void testBindTwice() { - final ConversationListItemView view = getView(); - - mCursor.moveToFirst(); - view.bind(mCursor, mockHost); - - mCursor.moveToNext(); - view.bind(mCursor, mockHost); - verifyContent(view, mCursor, mCursor.getPosition()); - } - - @Override - protected int getLayoutIdForView() { - return R.layout.conversation_list_item_view; - } -} diff --git a/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java deleted file mode 100644 index a38dac2..0000000 --- a/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.media.MediaPlayer; -import android.media.MediaRecorder.OnErrorListener; -import android.media.MediaRecorder.OnInfoListener; -import android.net.Uri; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.ui.ViewTest; -import com.android.messaging.util.FakeMediaUtil; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - -public class AudioRecordViewTest extends ViewTest { - - @Mock AudioRecordView.HostInterface mockHost; - @Mock LevelTrackingMediaRecorder mockRecorder; - @Mock MediaPlayer mockMediaPlayer; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeFactory.register(getInstrumentation().getContext()) - .withMediaUtil(new FakeMediaUtil(mockMediaPlayer)); - } - - private void verifyAudioSubmitted() { - Mockito.verify(mockHost).onAudioRecorded(Matchers.any(MessagePartData.class)); - } - - private AudioRecordView initView() { - final AudioRecordView view = getView(); - view.testSetMediaRecorder(mockRecorder); - view.setHostInterface(mockHost); - return view; - } - - public void testRecording() { - Mockito.when(mockRecorder.isRecording()).thenReturn(false); - Mockito.when(mockRecorder.startRecording(Matchers.any(), - Matchers.any(), Matchers.anyInt())).thenReturn(true); - Mockito.when(mockRecorder.stopRecording()).thenReturn(Uri.parse("content://someaudio/2")); - final AudioRecordView view = initView(); - view.onRecordButtonTouchDown(); - Mockito.verify(mockRecorder).startRecording(Matchers.any(), - Matchers.any(), Matchers.anyInt()); - Mockito.when(mockRecorder.isRecording()).thenReturn(true); - // Record for 1 second to make it meaningful. - sleepNoThrow(1000); - view.onRecordButtonTouchUp(); - // We added some buffer to the end of the audio recording, so sleep for sometime and - // verify audio is recorded. - sleepNoThrow(700); - Mockito.verify(mockRecorder).stopRecording(); - verifyAudioSubmitted(); - } - - private void sleepNoThrow(final long duration) { - try { - Thread.sleep(duration); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - protected int getLayoutIdForView() { - return R.layout.mediapicker_audio_chooser; - } -} diff --git a/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java b/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java deleted file mode 100644 index 951c694..0000000 --- a/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.os.AsyncTask; -import android.test.suitebuilder.annotation.SmallTest; -import com.android.messaging.BugleTestCase; -import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper; -import org.mockito.InOrder; -import org.mockito.Mockito; - -@SmallTest -public class CameraManagerTest extends BugleTestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - // Force each test to set up a camera wrapper to match their needs - CameraManager.setCameraWrapper(null); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - MockCameraFactory.cleanup(); - } - - public void testNoCameraDeviceGetInfo() { - CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper()); - assertEquals(false, CameraManager.get().hasAnyCamera()); - assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); - try { - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); - fail("selectCamera should have thrown"); - } catch (AssertionError e) { - } - } - - public void testFrontFacingOnlyGetInfo() { - CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT) - )); - assertEquals(true, CameraManager.get().hasAnyCamera()); - assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); - assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); - assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); - } - - public void testBackFacingOnlyGetInfo() { - CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) - )); - assertEquals(true, CameraManager.get().hasAnyCamera()); - assertEquals(false, CameraManager.get().hasFrontAndBackCamera()); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); - assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); - assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); - } - - public void testFrontAndBackGetInfo() { - CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT), - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) - )); - assertEquals(true, CameraManager.get().hasAnyCamera()); - assertEquals(true, CameraManager.get().hasFrontAndBackCamera()); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); - assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); - assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); - } - - public void testSwapCamera() { - CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper( - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT), - MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK) - )); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT); - assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing); - CameraManager.get().swapCamera(); - assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing); - } - - public void testOpenCamera() { - Camera backCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK); - Camera frontCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT); - CameraWrapper wrapper = MockCameraFactory.createCameraWrapper(frontCamera, backCamera); - CameraManager.setCameraWrapper(wrapper); - CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK); - CameraManager.get().openCamera(); - CameraManager.get().openCamera(); - CameraManager.get().openCamera(); - waitForPendingAsyncTasks(); - Mockito.verify(wrapper, Mockito.never()).open(0); - Mockito.verify(wrapper).open(1); - Mockito.verify(wrapper, Mockito.never()).release(frontCamera); - Mockito.verify(wrapper, Mockito.never()).release(backCamera); - CameraManager.get().swapCamera(); - waitForPendingAsyncTasks(); - Mockito.verify(wrapper).open(0); - Mockito.verify(wrapper).open(1); - Mockito.verify(wrapper, Mockito.never()).release(frontCamera); - Mockito.verify(wrapper).release(backCamera); - InOrder inOrder = Mockito.inOrder(wrapper); - inOrder.verify(wrapper).open(1); - inOrder.verify(wrapper).release(backCamera); - inOrder.verify(wrapper).open(0); - } - - private void waitForPendingAsyncTasks() { - try { - final Object lockObject = new Object(); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... voids) { - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - synchronized (lockObject) { - lockObject.notifyAll(); - } - } - }.execute(); - - synchronized (lockObject) { - lockObject.wait(500); - } - } catch (InterruptedException e) { - fail(); - } - } -} diff --git a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java deleted file mode 100644 index 83d8ac9..0000000 --- a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.provider.MediaStore.Images.Media; -import android.view.View; -import android.widget.CheckBox; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeCursor; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.TestDataFactory; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.ViewTest; -import com.android.messaging.util.UriUtil; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - -public class GalleryGridItemViewTest extends ViewTest { - - @Mock GalleryGridItemView.HostInterface mockHost; - - @Override - protected void setUp() throws Exception { - super.setUp(); - final Context context = getInstrumentation().getTargetContext(); - FakeFactory.register(context) - .withDataModel(new FakeDataModel(context)); - } - - protected void verifyClickedItem(final View view, final GalleryGridItemData data) { - Mockito.verify(mockHost).onItemClicked(view, data, false /* longClick */); - } - - protected void verifyContent( - final GalleryGridItemView view, - final String imageUrl, - final boolean showCheckbox, - final boolean isSelected) { - final AsyncImageView imageView = (AsyncImageView) view.findViewById(R.id.image); - final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox); - - assertNotNull(imageView); - assertTrue(imageView.mImageRequestBinding.isBound()); - assertTrue(imageView.mImageRequestBinding.getData().getKey().startsWith(imageUrl)); - assertNotNull(checkBox); - if (showCheckbox) { - assertEquals(View.VISIBLE, checkBox.getVisibility()); - assertEquals(isSelected, checkBox.isChecked()); - } else { - assertNotSame(View.VISIBLE, checkBox.getVisibility()); - } - } - - public void testBind() { - Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false); - Mockito.when(mockHost.isItemSelected(Matchers.any())) - .thenReturn(false); - final GalleryGridItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); - cursor.moveToFirst(); - final String path = (String) cursor.getAt(Media.DATA, 0); - view.bind(cursor, mockHost); - verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), - false, false); - } - - public void testBindMultiSelectUnSelected() { - Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); - Mockito.when(mockHost.isItemSelected(Matchers.any())) - .thenReturn(false); - final GalleryGridItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); - cursor.moveToFirst(); - final String path = (String) cursor.getAt(Media.DATA, 0); - view.bind(cursor, mockHost); - verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), - true, false); - } - - public void testBindMultiSelectSelected() { - Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); - Mockito.when(mockHost.isItemSelected(Matchers.any())) - .thenReturn(true); - final GalleryGridItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); - cursor.moveToFirst(); - final String path = (String) cursor.getAt(Media.DATA, 0); - view.bind(cursor, mockHost); - verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), - true, true); - } - - public void testClick() { - Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false); - Mockito.when(mockHost.isItemSelected(Matchers.any())) - .thenReturn(false); - final GalleryGridItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); - cursor.moveToFirst(); - view.bind(cursor, mockHost); - view.performClick(); - verifyClickedItem(view, view.mData); - } - - public void testBindTwice() { - Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true); - Mockito.when(mockHost.isItemSelected(Matchers.any())) - .thenReturn(false); - final GalleryGridItemView view = getView(); - final FakeCursor cursor = TestDataFactory.getGalleryGridCursor(); - - cursor.moveToFirst(); - view.bind(cursor, mockHost); - - cursor.moveToNext(); - final String path = (String) cursor.getAt(Media.DATA, 1); - view.bind(cursor, mockHost); - verifyContent(view, UriUtil.getUriForResourceFile(path).toString(), - true, false); - } - - @Override - protected int getLayoutIdForView() { - return R.layout.gallery_grid_item_view; - } -} diff --git a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java deleted file mode 100644 index 4a7040e..0000000 --- a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; - -import com.android.messaging.FakeFactory; -import com.android.messaging.R; -import com.android.messaging.datamodel.FakeDataModel; -import com.android.messaging.datamodel.binding.Binding; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.ui.FragmentTestCase; - -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; - -public class MediaPickerTest extends FragmentTestCase { - @Mock protected MediaPickerData mMockMediaPickerData; - @Mock protected DraftMessageData mMockDraftMessageData; - protected FakeDataModel mFakeDataModel; - - public MediaPickerTest() { - super(MediaPicker.class); - } - - @Override - protected MediaPicker getFragment() { - if (mFragment == null) { - mFragment = new MediaPicker(getInstrumentation().getTargetContext()); - } - return mFragment; - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - final Context context = getInstrumentation().getTargetContext(); - mFakeDataModel = new FakeDataModel(context) - .withMediaPickerData(mMockMediaPickerData); - FakeFactory.register(context) - .withDataModel(mFakeDataModel); - } - - /** - * Helper method to initialize the MediaPicker and its data. - */ - private void initFragment(final int supportedMediaTypes, final Integer[] expectedLoaderIds, - final boolean filterTabBeforeAttach) { - Mockito.when(mMockMediaPickerData.isBound(Matchers.anyString())) - .thenReturn(true); - Mockito.when(mMockDraftMessageData.isBound(Matchers.anyString())) - .thenReturn(true); - final Binding draftBinding = BindingBase.createBinding(this); - draftBinding.bind(mMockDraftMessageData); - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final MediaPicker fragment = getFragment(); - if (filterTabBeforeAttach) { - fragment.setSupportedMediaTypes(supportedMediaTypes); - getActivity().setFragment(fragment); - } else { - getActivity().setFragment(fragment); - fragment.setSupportedMediaTypes(supportedMediaTypes); - } - fragment.setDraftMessageDataModel(draftBinding); - Mockito.verify(mMockMediaPickerData, - Mockito.atLeastOnce()).init( - Matchers.eq(fragment.getLoaderManager())); - fragment.open(MediaPicker.MEDIA_TYPE_ALL, false); - } - }); - getInstrumentation().waitForIdleSync(); - } - - public void testDefaultTabs() { - Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0); - initFragment(MediaPicker.MEDIA_TYPE_ALL, new Integer[] { - MediaPickerData.GALLERY_IMAGE_LOADER }, - false); - final MediaPicker mediaPicker = getFragment(); - final View view = mediaPicker.getView(); - assertNotNull(view); - final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip); - assertEquals(tabStrip.getChildCount(), 3); - for (int i = 0; i < tabStrip.getChildCount(); i++) { - final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i); - assertEquals(View.VISIBLE, tabButton.getVisibility()); - assertEquals(i == 0, tabButton.isSelected()); - } - } - - public void testFilterTabsBeforeAttach() { - Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0); - initFragment(MediaPicker.MEDIA_TYPE_IMAGE, new Integer[] { - MediaPickerData.GALLERY_IMAGE_LOADER }, - true); - final MediaPicker mediaPicker = getFragment(); - final View view = mediaPicker.getView(); - assertNotNull(view); - final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip); - assertEquals(tabStrip.getChildCount(), 3); - for (int i = 0; i < tabStrip.getChildCount(); i++) { - final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i); - assertEquals(i == 0, tabButton.isSelected()); - } - } -} diff --git a/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java b/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java deleted file mode 100644 index 789a78f..0000000 --- a/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.ui.mediapicker; - -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; - -import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.HashMap; -import java.util.Map; - -class MockCameraFactory { - private static Map sCameraInfos = new HashMap(); - - public static Camera createCamera(int facing) { - Camera camera = Mockito.mock(Camera.class); - CameraInfo cameraInfo = new CameraInfo(); - cameraInfo.facing = facing; - sCameraInfos.put(camera, cameraInfo); - return camera; - } - - public static void getCameraInfo(Camera camera, CameraInfo outCameraInfo) { - CameraInfo cameraInfo = sCameraInfos.get(camera); - outCameraInfo.facing = cameraInfo.facing; - outCameraInfo.orientation = cameraInfo.orientation; - outCameraInfo.canDisableShutterSound = cameraInfo.canDisableShutterSound; - } - - public static CameraWrapper createCameraWrapper(final Camera... cameras) { - CameraWrapper wrapper = Mockito.mock(CameraWrapper.class); - Mockito.when(wrapper.getNumberOfCameras()).thenReturn(cameras.length); - Mockito.when(wrapper.open(Mockito.anyInt())).then(new Answer() { - @Override - public Camera answer(InvocationOnMock invocation) { - return cameras[(Integer) invocation.getArguments()[0]]; - } - }); - Mockito.doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - getCameraInfo( - cameras[(Integer) invocation.getArguments()[0]], - (CameraInfo) invocation.getArguments()[1] - ); - return null; - } - }).when(wrapper).getCameraInfo(Mockito.anyInt(), Mockito.any(CameraInfo.class)); - return wrapper; - } - - public static void cleanup() { - sCameraInfos.clear(); - } -} diff --git a/tests/src/com/android/messaging/util/BugleGservicesTest.java b/tests/src/com/android/messaging/util/BugleGservicesTest.java deleted file mode 100644 index 1a0a10e..0000000 --- a/tests/src/com/android/messaging/util/BugleGservicesTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; - -@SmallTest -public class BugleGservicesTest extends BugleTestCase { - - public void testGServiceGet() { - final BugleGservices bugleGservices = new FakeBugleGservices(); - - assertEquals(BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT, - bugleGservices.getString( - BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX, - BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT)); - } -} diff --git a/tests/src/com/android/messaging/util/ContactUtilTest.java b/tests/src/com/android/messaging/util/ContactUtilTest.java deleted file mode 100644 index 48a7ced..0000000 --- a/tests/src/com/android/messaging/util/ContactUtilTest.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.content.ContentProviderOperation; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.Contacts; -import android.test.suitebuilder.annotation.LargeTest; -import android.text.TextUtils; - -import com.android.messaging.BugleTestCase; -import com.android.messaging.FakeFactory; - -import org.junit.Assert; - -import java.util.ArrayList; - -/* - * Class for testing ContactUtil. - */ -@LargeTest -public class ContactUtilTest extends BugleTestCase { - private static final String TEST_NAME_PREFIX = "BugleTest:"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - // TODO: This test will actually mess with contacts on your phone. - // Ideally we would use a fake content provider to give us contact data... - FakeFactory.registerWithoutFakeContext(getTestContext()); - - // add test contacts. - addTestContact("John", "650-123-1233", "john@gmail.com", false); - addTestContact("Joe", "(650)123-1233", "joe@gmail.com", false); - addTestContact("Jim", "650 123 1233", "jim@gmail.com", false); - addTestContact("Samantha", "650-123-1235", "samantha@gmail.com", true); - addTestContact("Adrienne", "650-123-1236", "adrienne@gmail.com", true); - } - - @Override - protected void tearDown() throws Exception { - deleteTestContacts(); - super.tearDown(); - } - - /** - * Add a test contact based on contact name, phone and email. - */ - private void addTestContact( - final String name, final String phone, final String email, final boolean starred) - throws Exception { - final ArrayList ops = new ArrayList(); - - ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) - .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) - .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null) - .build()); - - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, - TEST_NAME_PREFIX + name).build()); - - if (phone != null) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) - .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE) - .build()); - } - - if (email != null) { - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue(ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) - .withValue(ContactsContract.CommonDataKinds.Email.TYPE, - ContactsContract.CommonDataKinds.Email.TYPE_WORK) - .build()); - } - - mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); - - // Star the whole contact if needed. - if (starred) { - final ContentValues values = new ContentValues(); - values.put(Contacts.STARRED, 1); - getContext().getContentResolver().update(Contacts.CONTENT_URI, values, - Contacts.DISPLAY_NAME + "= ?", new String[] { TEST_NAME_PREFIX + name }); - } - } - - /** - * Remove test contacts added during test setup. - */ - private void deleteTestContacts() { - final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, - Uri.encode(TEST_NAME_PREFIX)); - final Cursor cur = - mContext.getContentResolver().query(contactUri, null, null, null, null); - try { - if (cur.moveToFirst()) { - do { - final String lookupKey = cur.getString(cur.getColumnIndex(Contacts.LOOKUP_KEY)); - final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); - mContext.getContentResolver().delete(uri, null, null); - } while (cur.moveToNext()); - } - } catch (final Exception e) { - System.out.println(e.getStackTrace()); - } - } - - /** - * Verify ContactUtil.getPhone will return all phones, including the ones added for test. - */ - public void ingoredTestGetPhones() { - final Cursor cur = ContactUtil.getPhones(getContext()) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testGetPhones: Number of phones on the device:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); - } - - /** - * Verify ContactUtil.filterPhone will work on name based matches. - */ - public void ingoredTestFilterPhonesByName() { - final Cursor cur = ContactUtil.filterPhones(getContext(), TEST_NAME_PREFIX) - .performSynchronousQuery(); - - if (cur.getCount() != 5) { - Assert.fail("Cursor should have size of 5"); - return; - } - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); - } - - /** - * Verify ContactUtil.filterPhone will work on partial number matches. - */ - public void ingoredTestFilterPhonesByPartialNumber() { - final String[] filters = new String[] { "650123", "650-123", "(650)123", "650 123" }; - - for (final String filter : filters) { - final Cursor cur = ContactUtil.filterPhones(getContext(), filter) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByPartialNumber: Number of phones:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); - } - } - - /** - * Verify ContactUtil.filterPhone will work on full number matches. - */ - public void ingoredTestFilterPhonesByFullNumber() { - final String[] filters = new String[] { - "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" }; - - for (final String filter : filters) { - final Cursor cur = ContactUtil.filterPhones(getContext(), filter) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByFullNumber: Number of phones:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - } - } - - /** - * Verify ContactUtil.lookPhone will work on number including area code. - */ - public void ingoredTestLookupPhoneWithAreaCode() { - final String[] filters = new String[] { - "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" }; - - for (final String filter : filters) { - final Cursor cur = ContactUtil.lookupPhone(getContext(), filter) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithAreaCode: Number of phones:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - } - } - - /** - * Verify ContactUtil.lookPhone will work on number without area code. - */ - public void ingoredTestLookupPhoneWithoutAreaCode() { - final String[] filters = new String[] { - "1231233", "123-1233", "123 1233" }; - - for (final String filter : filters) { - final Cursor cur = ContactUtil.lookupPhone(getContext(), filter) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithoutAreaCode: Number of phones:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim"); - } - } - - public void ingoredTestGetFrequentPhones() { - final Cursor cur = ContactUtil.getFrequentContacts(getContext()) - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testGetFrequentPhones: Number of phones on the device:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha"); - verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne"); - } - - /** - * Verify ContactUtil.filterEmails will work on partial email. - */ - public void ingoredTestFilterEmails() { - final Cursor cur = ContactUtil.filterEmails(getContext(), "john@") - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testFilterEmails: Number of emails:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - } - - /** - * Verify ContactUtil.lookupEmail will work on full email. - */ - public void ingoredTestLookupEmail() { - final Cursor cur = ContactUtil.lookupEmail(getContext(), "john@gmail.com") - .performSynchronousQuery(); - - LogUtil.i(LogUtil.BUGLE_TAG, "testLookupEmail: Number of emails:" + - cur.getCount()); - - verifyCursorContains(cur, TEST_NAME_PREFIX + "John"); - } - - /** - * Utility method to check whether cursor contains a particular contact. - */ - private void verifyCursorContains(final Cursor cursor, final String nameToVerify) { - if (cursor.moveToFirst()) { - do { - final String name = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME); - if (TextUtils.equals(name, nameToVerify)) { - return; - } - } while (cursor.moveToNext()); - } - Assert.fail("Cursor should have " + nameToVerify); - } -} diff --git a/tests/src/com/android/messaging/util/FakeBugleGservices.java b/tests/src/com/android/messaging/util/FakeBugleGservices.java deleted file mode 100644 index f7d90da..0000000 --- a/tests/src/com/android/messaging/util/FakeBugleGservices.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -/** - * Fake implementation which just returns the default values. - */ -public class FakeBugleGservices extends BugleGservices { - public FakeBugleGservices() { - } - - @Override - public void registerForChanges(final Runnable r) { - } - - @Override - public long getLong(final String key, final long defaultValue) { - return defaultValue; - } - - @Override - public int getInt(final String key, final int defaultValue) { - return defaultValue; - } - - @Override - public boolean getBoolean(final String key, final boolean defaultValue) { - return defaultValue; - } - - @Override - public String getString(final String key, final String defaultValue) { - return defaultValue; - } - - @Override - public float getFloat(String key, float defaultValue) { - return defaultValue; - } - -} diff --git a/tests/src/com/android/messaging/util/FakeBuglePrefs.java b/tests/src/com/android/messaging/util/FakeBuglePrefs.java deleted file mode 100644 index b3429a6..0000000 --- a/tests/src/com/android/messaging/util/FakeBuglePrefs.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - - -/** - * Fake implementation which just returns the default values and ignores put operations. - */ -public class FakeBuglePrefs extends BuglePrefs { - public FakeBuglePrefs() { - } - - @Override - public int getInt(final String key, final int defaultValue) { - return defaultValue; - } - - @Override - public long getLong(final String key, final long defaultValue) { - return defaultValue; - } - - @Override - public boolean getBoolean(final String key, final boolean defaultValue) { - return defaultValue; - } - - @Override - public String getString(final String key, final String defaultValue) { - return defaultValue; - } - - @Override - public byte[] getBytes(String key) { - return null; - } - - @Override - public void putInt(final String key, final int value) { - } - - @Override - public void putLong(final String key, final long value) { - } - - @Override - public void putBoolean(final String key, final boolean value) { - } - - @Override - public void putString(final String key, final String value) { - } - - @Override - public void putBytes(String key, byte[] value) { - } - - @Override - public String getSharedPreferencesName() { - return "FakeBuglePrefs"; - } - - @Override - public void onUpgrade(int oldVersion, int newVersion) { - } - - @Override - public void remove(String key) { - } -} diff --git a/tests/src/com/android/messaging/util/FakeMediaUtil.java b/tests/src/com/android/messaging/util/FakeMediaUtil.java deleted file mode 100644 index e37ff0c..0000000 --- a/tests/src/com/android/messaging/util/FakeMediaUtil.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.content.Context; -import android.media.MediaPlayer; - -public class FakeMediaUtil extends MediaUtil { - private final MediaPlayer mMediaPlayer; - public FakeMediaUtil(final MediaPlayer mediaPlayer) { - mMediaPlayer = mediaPlayer; - } - - @Override - public void playSound(Context context, int resId, OnCompletionListener completionListener) { - if (completionListener != null) { - completionListener.onCompletion(); - } - } -} diff --git a/tests/src/com/android/messaging/util/YouTubeUtilTest.java b/tests/src/com/android/messaging/util/YouTubeUtilTest.java deleted file mode 100644 index 03c461a..0000000 --- a/tests/src/com/android/messaging/util/YouTubeUtilTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.messaging.BugleTestCase; - -/* - * Class for testing YouTubeUtil. - */ -@SmallTest -public class YouTubeUtilTest extends BugleTestCase { - public void testGetYoutubePreviewImageLink() { - final String videoId = "dQw4w9WgXcQ"; - final String videoThumbnailUrl = YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId - + YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_END; - - // Check known valid youtube links to videos - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/watch?v=" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("https://www.youtube.com/watch?v=" + videoId - + "&feature=youtu.be"), videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("www.youtube.com/watch?v=" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/embed/" + videoId), - videoThumbnailUrl); - assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/v/" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("https://youtube.googleapis.com/v/" - + videoId), videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/apiplayer?video_id=" - + videoId), videoThumbnailUrl); - // This is the type of links that are used as shares from YouTube and will be the most - // likely case that we see - assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://youtu.be/" + videoId), - videoThumbnailUrl); - - // Try links that shouldn't work - assertNull(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com")); - } -} \ No newline at end of file diff --git a/tools/buglesql b/tools/buglesql deleted file mode 100755 index c9ac8ef..0000000 --- a/tools/buglesql +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -usage='buglesql [-c] [-r] sql - -c display in columns with headers (default) - -r display as records' - -opts='-column -header' - -while test $# -gt 0 -do - case $1 in - -c) - opts='-column -header' - shift - ;; - -r) - opts='-line' - shift - ;; - *) - break; - esac -done - -if [ $# -lt 1 ]; then - echo "$usage" - exit 1 -fi - -adb shell su -c sqlite3 $opts data/data/com.android.messaging/databases/bugle_db "$1" diff --git a/tools/dumpapkversion.sh b/tools/dumpapkversion.sh deleted file mode 100755 index 48ff590..0000000 --- a/tools/dumpapkversion.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -for i in `find . -name \*.apk` -do - echo -n $i - echo -n " " - aapt dump badging $i | grep versionCode | sed -E 's/^.*versionCode/versionCode/' -done diff --git a/tools/messagegen/fillsms b/tools/messagegen/fillsms deleted file mode 100755 index 6903941..0000000 --- a/tools/messagegen/fillsms +++ /dev/null @@ -1,287 +0,0 @@ -#!/bin/bash -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -################################################################### -## Script that generates SMS and MMS messages and fills the Android -## telephony provider mmssms.db. This is used for testing SMS/MMS. -################################################################### - -AREA_CODE=605 - -TABLE_CANONICAL_ADDRESSES_START_ID=100 -TABLE_THREADS_START_ID=100 -TABLE_SMS_START_ID=1000 - -START_TIMESTAMP_IN_SECONDS=1357683093 # 1/8/2013 2:11:33 PM -TIMESTAMP_INC_IN_SECONDS=120 - -PART_DIR="/data/data/com.android.providers.telephony/app_parts" - -USAGE='fillsms [-f] [-x] <# of threads> <# of sms per thread> <# of mms per thread> - -f -- Only generates the SQL file, do not push to the device - -x -- Only execute a SQL file - -g -- For GB devices -Examples: - # Generate 2 threads each with 10 SMSes and 10 MMSes on device with phone - # number +16508619525. MMS messages use images listed in ./images, which list - # *.jpg and *.gif files in local directory. The SQL commands are in sql.txt - fillsms +16508619525 2 10 10 images sql.txt - - # Same as above but only creating the SQL command file without pushing to - # device - fillsms -f +16508619525 2 10 10 images sql.txt - - # Just push the sql.txt to device without generating new SQLs - fillsms -x +16508619525 2 10 10 images sql.txt -' - -SMIL=' ' - -MAX_WORDS_PER_MESSAGE=15 - -DICT=american-english - -# don't actually run the sql on device -opt_sql_only=0 -opt_exec_only=0 -opt_for_gb=0 - -while test $# -gt 0 -do - case $1 in - -f) - opt_sql_only=1 - shift - ;; - -x) - opt_exec_only=1 - shift - ;; - -g) - opt_for_gb=1 - shift - ;; - *) - break; - esac -done - - -if [ $opt_sql_only -eq "1" -a $opt_exec_only -eq "1" ]; then - echo "-f and -x can not coexist" - echo "$USAGE" - exit 1 -fi - -if [ $# -lt 6 ]; then - echo "$USAGE" - exit 1 -fi -device_phone=$1 -shift -num_of_threads=$1 -shift -sms_per_thread=$1 -shift -mms_per_thread=$1 -shift -image_list_file=$1 -shift -sql_file=$1 -shift - -dict_lines=`wc -l < $DICT` -image_files=`wc -l < $image_list_file` - -if [ $mms_per_thread -gt "0" ]; then - if [ ! -f $image_list_file ]; then - echo "No image files for creating MMS messages" - exit 1 - fi -fi - -echoerr () -{ - echo "$@" 1>&2; -} - -random_value () -{ - echo $(( $RANDOM % $1 + 1 )) -} - -dict_word () -{ - local v=$(random_value 30000) - sed $v"q;d" $DICT -} - -gen_message () -{ - local words=$(random_value $MAX_WORDS_PER_MESSAGE) - local message= - for k in `seq 1 $words`; - do - local word=$(dict_word) - message="$message $word" - done - echo $message | sed -e "s/'//g" -} - -random_image () -{ - local v=$(random_value $image_files) - sed $v"q;d" $image_list_file -} - -add_sql () -{ - echo $1 >> $sql_file -} - -adb_sql () -{ - echo $1 - adb shell sqlite3 data/data/com.android.providers.telephony/databases/mmssms.db "$1" -} - -###################################################################################### -###################################################################################### - -if [ $opt_exec_only -eq "0" ]; then - # clean up sql file - rm -f $sql_file - - # add sql to clean up database - add_sql "delete from pdu where _id>=$TABLE_SMS_START_ID;" - add_sql "delete from part where _id>=$TABLE_SMS_START_ID;" - add_sql "delete from addr where _id>=$TABLE_SMS_START_ID;" - add_sql "delete from sms where _id>=$TABLE_SMS_START_ID;" - add_sql "delete from threads where _id>=$TABLE_THREADS_START_ID;" - add_sql "delete from canonical_addresses where _id>=$TABLE_CANONICAL_ADDRESSES_START_ID;" - - for i in `seq 1 $num_of_threads`; - do - echo - echo "Creating thread $i ......" - echo - - # Get random phone number - value=$(random_value 1000) - middle=$(printf '%03d' $value) - value=$(random_value 10000) - last=$(printf '%04d' $value) - phone="+1$AREA_CODE$middle$last" - echo $phone - echo - - timestamp=$(( $START_TIMESTAMP_IN_SECONDS + 5 * $TIMESTAMP_INC_IN_SECONDS * $i )) - - # Generate threads - addr_id=$(( $TABLE_CANONICAL_ADDRESSES_START_ID + $i )) - add_sql "insert into canonical_addresses (_id,address) values ($addr_id,'$phone');" - - thread_id=$(( $TABLE_THREADS_START_ID + $i )) - add_sql "insert into threads (_id,date,message_count,recipient_ids,snippet,snippet_cs,read,type,error,has_attachment) values ($thread_id, $timestamp, $sms_per_thread, $addr_id, 'snippet', 0, 1, 0, 0, 0);" - - # Generate SMS - if [ $sms_per_thread -gt "0" ]; then - half_timestamp_inc=$(( 500 + ((($sms_per_thread + $mms_per_thread) * $TIMESTAMP_INC_IN_SECONDS) * 500 / $sms_per_thread) )) - for j in `seq 1 $sms_per_thread`; - do - message=$(gen_message) - date=$(( ( 1000 * $timestamp ) - $half_timestamp_inc * ( 2 * ($sms_per_thread - $j) + ( $i % 2 ) ) )) - message_id=$(( $TABLE_SMS_START_ID + $sms_per_thread * $i * 2 + (2 * $j) )) - message_type=$(( $j % 2 + 1 )) - add_sql "insert into sms (_id,thread_id,address,person,date,status,type,body,read,seen) values ($message_id, $thread_id, '$phone', '$phone', $date, -1, $message_type, '$message', 1, 1);" - done - fi - - # Generate MMS - if [ $mms_per_thread -gt "0" ]; then - half_timestamp_inc=$(( 1 + ((($sms_per_thread + $mms_per_thread) * $TIMESTAMP_INC_IN_SECONDS) / ( 2 * $mms_per_thread) ) )) - for j in `seq 1 $mms_per_thread`; - do - image_line=$(random_image) - image=`echo $image_line | awk '{ print $1 }'` - width=`echo $image_line | awk '{ print $2 }'` - height=`echo $image_line | awk '{ print $3 }'` - size=`echo $image_line | awk '{ print $4 }'` - date=$(( $timestamp - $half_timestamp_inc * ( 2 * ($mms_per_thread - $j) + ( ($i+1) % 2 ) ) )) - message_id=$(( $TABLE_SMS_START_ID + $sms_per_thread * $i * 2 + (2 * $j + 1) )) - message_type=$(( $j % 2 + 1 )) - if [ $message_type -eq '1' ]; then - m_type=132 - else - m_type=128 - fi - if [ $opt_for_gb -eq "0" ]; then - add_sql "insert into pdu (_id,thread_id,date,date_sent,msg_box,read,m_id,sub,sub_cs,ct_t,m_cls,m_type,v,m_size,pri,rr,tr_id,d_rpt,locked,seen,text_only) values ($message_id, $thread_id, $date, 0, $message_type, 1, 'hmma3p5s1a3m526@w.tmomail.net', 'no subject', 106, 'application/vnd.wap.multipart.related', 'personal', $m_type, 18, $size, 129, 129 , '$message_id', 129, 0, 1, 0);" - else - add_sql "insert into pdu (_id,thread_id,date,msg_box,read,m_id,sub,sub_cs,ct_t,m_cls,m_type,v,m_size,pri,rr,tr_id,d_rpt,locked,seen) values ($message_id, $thread_id, $date, $message_type, 1, 'hmma3p5s1a3m526@w.tmomail.net', 'no subject', 106, 'application/vnd.wap.multipart.related', 'personal', $m_type, 18, $size, 129, 129 , '$message_id', 129, 0, 1);" - fi - id_1=$(( $message_id )) - id_2=$(( $message_id + 1 )) - smil=$(printf "$SMIL" $height $width $height $width $image) - add_sql "insert into part (_id,mid,seq,ct,cid,cl,text) values ($id_1, $message_id, -1, 'application/smil', '', 'smil.xml', '$smil');" - image_no_suffix=${image%.*} - add_sql "insert into part (_id,mid,seq,ct,cid,cl,_data) values ($id_2, $message_id, 0, 'image/jpeg', '$image_no_suffix', '$image', '$PART_DIR/$image');" - if [ $message_type -eq '1' ]; then - add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_1, $message_id, '$phone', 137, 106);" - add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_2, $message_id, '$device_phone', 151, 106);" - else - add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_1, $message_id, 'insert-address-token', 137, 106);" - add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_2, $message_id, '$phone', 151, 106);" - fi - done - fi - done -fi - -# Push to device -if [ $opt_sql_only -eq "0" ]; then - # make sure we have access - adb root - - # Push all local jpgs or gifs to device, being lazy here. - if [ $mms_per_thread -gt "0" ]; then - for file in `ls *.jpg *.gif`; - do - echo "adb push $file $PART_DIR/$file" - adb push $file $PART_DIR/$file - done - fi - - echo "adb push $sql_file /data/fillsms" - adb push $sql_file /data/fillsms - echo - adb_sql ".read /data/fillsms" - echo - adb_sql "select count(*) from canonical_addresses where _id>=$TABLE_CANONICAL_ADDRESSES_START_ID;" - echo - adb_sql "select count(*) from threads where _id>=$TABLE_THREADS_START_ID;" - echo - if [ $sms_per_thread -gt "0" ]; then - adb_sql "select count(*) from sms where _id>=$TABLE_SMS_START_ID;" - echo - fi - if [ $mms_per_thread -gt "0" ]; then - adb_sql "select count(*) from pdu where _id>=$TABLE_SMS_START_ID;" - echo - adb_sql "select count(*) from part where _id>=$TABLE_SMS_START_ID;" - echo - adb_sql "select count(*) from addr where _id>=$TABLE_SMS_START_ID;" - echo - fi -fi diff --git a/tools/messagegen/listimages b/tools/messagegen/listimages deleted file mode 100755 index 9c50072..0000000 --- a/tools/messagegen/listimages +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -for file in `ls *.jpg *.gif`; -do - dimension=`identify -format "%w %h" $file` - size=`stat -c%s $file` - echo "$file $dimension $size" -done diff --git a/tools/mmssql b/tools/mmssql deleted file mode 100755 index 1643dbb..0000000 --- a/tools/mmssql +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -usage='mmssql [-c] [-r] sql - -c display in columns with headers (default) - -r display as records' - -opts='-column -header' - -while test $# -gt 0 -do - case $1 in - -c) - opts='-column -header' - shift - ;; - -r) - opts='-line' - shift - ;; - *) - break; - esac -done - -if [ $# -lt 1 ]; then - echo "$usage" - exit 1 -fi - -adb shell su -c sqlite3 $opts data/data/com.android.providers.telephony/databases/mmssms.db "$1" diff --git a/version.mk b/version.mk deleted file mode 100644 index 7407993..0000000 --- a/version.mk +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (C) 2015 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# The version code scheme for the package apk is: -# Mmbbbtad -# where -# M - major version (one or more digits) -# m - minor version (exactly 1 digit) -# bbb - manually specified build number (exactly 3 digits) -# t - build type (exactly 1 digit). Current valid values are: -# 0 : internal (dev build) -# 1 : prod build -# a - device architecture (exactly 1 digit). Current valid values are: -# 0 : non-native -# 1 : armv5te -# 3 : armv7-a -# 4 : arm64-v8a -# 5 : mips -# 6 : mips-64 -# 7 : x86 -# 8 : x86-64 -# d - asset density (exactly 1 digit). Current valid values are: -# 0 : all densities -# 2 : mdpi -# 4 : hdpi -# 6 : xhdpi -# 8 : xxhdpi -# 9 : xxxhdpi -# Mmbbb is specified manually. tad is automatically set during the build. -# -# For the client jar, the version code is agnostic to the target architecture and density: Mmbbbt00 -# -# NOTE: arch needs to be more significant than density because x86 devices support running ARM -# code in emulation mode, so all x86 versions must be higher than all ARM versions to ensure -# we deliver true x86 code to those devices. - -# Specify the following manually. Note that base_version_minor must be exactly 1 digit and -# base_version_build must be exactly 3 digits. -base_version_major := 1 -base_version_minor := 0 -base_version_build := 001 - -##################################################### -##################################################### -# Collect automatic version code parameters -ifneq "" "$(filter eng.%,$(BUILD_NUMBER))" - # This is an eng build - base_version_buildtype := 0 -else - # This is a build server build - base_version_buildtype := 1 -endif - -# Set the device architecture digit -ifeq "$(TARGET_ARCH)" "arm" - ifeq "$(TARGET_ARCH_VARIANT)" "armv5te" - base_version_arch := 1 - else ifeq "$(TARGET_ARCH_VARIANT)" "armv7-a" - base_version_arch := 3 - endif -else ifeq "$(TARGET_ARCH)" "arm64" - base_version_arch := 4 -else ifeq "$(TARGET_ARCH)" "mips" - base_version_arch := 5 -else ifeq "$(TARGET_ARCH)" "x86" - base_version_arch := 7 -else ifeq "$(TARGET_ARCH)" "x86_64" - base_version_arch := 8 -else - base_version_arch := 0 -endif - -ifeq "$(package_dpi)" "mdpi" - base_version_density := 2 -else ifeq "$(package_dpi)" "hdpi" - base_version_density := 4 -else ifeq "$(package_dpi)" "xhdpi" - base_version_density := 6 -else ifeq "$(package_dpi)" "xxhdpi" - base_version_density := 8 -else ifeq "$(package_dpi)" "xxxhdpi" - base_version_density := 9 -else - base_version_density := 0 -endif - -# Build the version code -version_code_package := $(base_version_major)$(base_version_minor)$(base_version_build)$(base_version_buildtype)$(base_version_arch)$(base_version_density) - -# The version name scheme for the package apk is: -# - For eng build (t=1): M.m.bbb eng.$(USER)-hh -# - For build server (t=0): M.m.bbb (nnnnnn-hh) -# where nnnnnn is the build number from the build server (no zero-padding) -# On eng builds, the BUILD_NUMBER has the user and timestamp inline -ifneq "" "$(filter eng.%,$(BUILD_NUMBER))" - git_hash := $(shell git --git-dir $(LOCAL_PATH)/.git log -n 1 --pretty=format:%h) - date_string := $(shell date +%m%d%y_%H%M%S) - version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build) (eng.$(USER).$(git_hash).$(date_string)-$(base_version_arch)$(base_version_density)) -else - version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build) ($(BUILD_NUMBER)-$(base_version_arch)$(base_version_density)) -endif - -# Cleanup the locals -base_version_major := -base_version_minor := -base_version_build := -base_version_buildtype := -base_version_arch := -base_version_density := -git_hash := -date_string :=