summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/util')
-rw-r--r--src/com/android/messaging/util/AccessibilityUtil.java170
-rw-r--r--src/com/android/messaging/util/Assert.java214
-rw-r--r--src/com/android/messaging/util/AvatarUriUtil.java320
-rw-r--r--src/com/android/messaging/util/BugleActivityUtil.java88
-rw-r--r--src/com/android/messaging/util/BugleApplicationPrefs.java45
-rw-r--r--src/com/android/messaging/util/BugleGservices.java72
-rw-r--r--src/com/android/messaging/util/BugleGservicesImpl.java68
-rw-r--r--src/com/android/messaging/util/BugleGservicesKeys.java298
-rw-r--r--src/com/android/messaging/util/BuglePrefs.java140
-rw-r--r--src/com/android/messaging/util/BuglePrefsImpl.java135
-rw-r--r--src/com/android/messaging/util/BuglePrefsKeys.java71
-rw-r--r--src/com/android/messaging/util/BugleSubscriptionPrefs.java95
-rw-r--r--src/com/android/messaging/util/BugleWidgetPrefs.java41
-rw-r--r--src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java157
-rw-r--r--src/com/android/messaging/util/CircularArray.java111
-rw-r--r--src/com/android/messaging/util/ConnectivityUtil.java247
-rw-r--r--src/com/android/messaging/util/ContactRecipientEntryUtils.java115
-rw-r--r--src/com/android/messaging/util/ContactUtil.java525
-rw-r--r--src/com/android/messaging/util/ContentType.java185
-rw-r--r--src/com/android/messaging/util/ConversationIdSet.java69
-rw-r--r--src/com/android/messaging/util/CubicBezierInterpolator.java120
-rw-r--r--src/com/android/messaging/util/Dates.java280
-rw-r--r--src/com/android/messaging/util/DebugUtils.java425
-rw-r--r--src/com/android/messaging/util/EmailAddress.java272
-rw-r--r--src/com/android/messaging/util/FallbackStrategies.java91
-rw-r--r--src/com/android/messaging/util/FileUtil.java140
-rw-r--r--src/com/android/messaging/util/GifTranscoder.java94
-rw-r--r--src/com/android/messaging/util/ImageUtils.java908
-rw-r--r--src/com/android/messaging/util/ImeUtil.java88
-rw-r--r--src/com/android/messaging/util/LogSaver.java293
-rw-r--r--src/com/android/messaging/util/LogUtil.java274
-rw-r--r--src/com/android/messaging/util/LoggingTimer.java70
-rw-r--r--src/com/android/messaging/util/LongSparseSet.java59
-rw-r--r--src/com/android/messaging/util/MaterialPalette.java27
-rw-r--r--src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java81
-rw-r--r--src/com/android/messaging/util/MediaUtil.java36
-rw-r--r--src/com/android/messaging/util/MediaUtilImpl.java66
-rw-r--r--src/com/android/messaging/util/NotificationPlayer.java363
-rw-r--r--src/com/android/messaging/util/OsUtil.java269
-rw-r--r--src/com/android/messaging/util/PendingIntentConstants.java40
-rw-r--r--src/com/android/messaging/util/PhoneUtils.java1011
-rw-r--r--src/com/android/messaging/util/RingtoneUtil.java53
-rw-r--r--src/com/android/messaging/util/SafeAsyncTask.java176
-rw-r--r--src/com/android/messaging/util/SwitchCompatUtils.java129
-rw-r--r--src/com/android/messaging/util/TextUtil.java73
-rw-r--r--src/com/android/messaging/util/ThreadUtil.java28
-rw-r--r--src/com/android/messaging/util/TintDrawableWrapper.java70
-rw-r--r--src/com/android/messaging/util/Trace.java115
-rw-r--r--src/com/android/messaging/util/Typefaces.java45
-rw-r--r--src/com/android/messaging/util/UiUtils.java438
-rw-r--r--src/com/android/messaging/util/UriUtil.java393
-rw-r--r--src/com/android/messaging/util/VersionUtil.java67
-rw-r--r--src/com/android/messaging/util/WakeLockHelper.java121
-rw-r--r--src/com/android/messaging/util/YouTubeUtil.java98
-rw-r--r--src/com/android/messaging/util/exif/ByteBufferInputStream.java48
-rw-r--r--src/com/android/messaging/util/exif/CountedDataInputStream.java140
-rw-r--r--src/com/android/messaging/util/exif/ExifData.java349
-rw-r--r--src/com/android/messaging/util/exif/ExifInterface.java2448
-rw-r--r--src/com/android/messaging/util/exif/ExifInvalidFormatException.java23
-rw-r--r--src/com/android/messaging/util/exif/ExifModifier.java196
-rw-r--r--src/com/android/messaging/util/exif/ExifOutputStream.java522
-rw-r--r--src/com/android/messaging/util/exif/ExifParser.java918
-rw-r--r--src/com/android/messaging/util/exif/ExifReader.java93
-rw-r--r--src/com/android/messaging/util/exif/ExifTag.java1008
-rw-r--r--src/com/android/messaging/util/exif/IfdData.java152
-rw-r--r--src/com/android/messaging/util/exif/IfdId.java31
-rw-r--r--src/com/android/messaging/util/exif/JpegHeader.java39
-rw-r--r--src/com/android/messaging/util/exif/OrderedDataOutputStream.java56
-rw-r--r--src/com/android/messaging/util/exif/Rational.java88
69 files changed, 0 insertions, 16090 deletions
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.
- * <p>Intended for use in code paths that should be run only for tests and never on
- * a real build.
- * <p>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=<avatarUri>&p=<avatarUri2>
- * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>
- * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>&p=<avatarUri4>
- *
- * 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=<fullName>
- *
- * 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<ParticipantData> 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<Uri> avatarUris = new ArrayList<Uri>(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<Uri> 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 <i>active</i> 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<String> 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<String> 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:
- * <number>(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 <E> The element type of this list.
- * @LibraryInternal
- */
-public class CircularArray<E> {
- 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<String> {
- private static final String JOIN_DELIMITER = "|";
- private static final String SPLIT_DELIMITER = "\\|";
-
- public ConversationIdSet() {
- super();
- }
-
- public ConversationIdSet(final Collection<String> 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<DebugAction> arrayAdapter = new ArrayAdapter<DebugAction>(
- 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<Void, Void, String[]> {
- 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.
- * <p>
- * 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.
- * <p>
- * 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. <code>"Name&lt;foo@example.org>"</code> 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. <code>"Name&lt;foo@example.org>"</code> 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.<p>
- * Basically, what would have been a nasty try-catch that's hard to separate and maintain:
- * <pre><code>
- * try {
- * // doSomething() that may fail.
- * } catch (Exception ex) {
- * try {
- * // fallback1() that may fail.
- * } catch (Exception ex2) {
- * try {
- * // fallback2() that may fail.
- * } catch (Exception ex3) {
- * // ...
- * }
- * }
- * }
- * </code></pre>
- * Now becomes:<br>
- * <pre><code>
- * FallbackStrategies
- * .startWith(something)
- * .thenTry(fallback1)
- * .thenTry(fallback2)
- * .execute();
- * </code></pre>
- */
-public class FallbackStrategies<Input, Output> {
- public interface Strategy<Input, Output> {
- Output execute(Input params) throws Exception;
- }
-
- private final List<Strategy<Input, Output>> mChainedStrategies;
-
- private FallbackStrategies(final Strategy<Input, Output> primaryStrategy) {
- mChainedStrategies = new ArrayList<Strategy<Input, Output>>();
- mChainedStrategies.add(primaryStrategy);
- }
-
- public static <Input, Output> FallbackStrategies<Input, Output> startWith(
- final Strategy<Input, Output> primaryStrategy) {
- return new FallbackStrategies<Input, Output>(primaryStrategy);
- }
-
- public FallbackStrategies<Input, Output> thenTry(final Strategy<Input, Output> 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<Input, Output> 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.
- * <p>
- * 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<LogRecord> 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<LogRecord>(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<Object> mSet = new LongSparseArray<Object>();
-
- 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<Command> mCmdQueue = new LinkedList<Command>();
-
- 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<String> using the given delimiter.
- * @param values
- * @param delimiter
- * @return
- */
- public static String joinFromSetWithDelimiter(
- final Set<String> 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<String, Integer> sPermissions = new Hashtable<String, Integer>();
-
- /**
- * 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<String> missingList = new ArrayList<String>();
- 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<SubscriptionInfo> 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<String, ArrayMap<String, String>> 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<String> 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<SubscriptionInfo> 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<String> getNormalizedSelfNumbers() {
- final HashSet<String> 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<SubscriptionInfo> getActiveSubscriptionInfoList() {
- final List<SubscriptionInfo> 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<String> getNormalizedSelfNumbers() {
- final HashSet<String> 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<String, String> getOrAddCountryMapInCacheLocked(String country) {
- if (country == null) {
- country = "";
- }
- ArrayMap<String, String> 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<String, String> 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<String, String> 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<SubscriptionInfo> 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:
- * <ol>
- * <li>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.
- * <li>Enforce creation on main thread as required by AsyncTask
- * <li>Enforce that the background task does not take longer than expected.
- * </ol>
- */
-public abstract class SafeAsyncTask<Params, Progress, Result>
- extends AsyncTask<Params, Progress, Result> {
- 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.
- *
- * <p>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<Params, Progress, Result> 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).<p>
- * To enable, set log.tag.Bugle_Trace (defined by {@link #TAG} to VERBOSE before
- * the process starts.<p>
- * 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. <p>
- * 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.
- *
- * <p class="note"> 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<SnackBarInteraction> 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<SnackBarInteraction> 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<String> SMS_MMS_SCHEMES = new HashSet<String>(
- Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO));
-
- public static final String SCHEME_BUGLE = "bugle";
- public static final HashSet<String> SUPPORTED_SCHEME = new HashSet<String>(
- 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<byte[]> mStripBytes = new ArrayList<byte[]>();
- 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<ExifTag> getAllTags() {
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
- 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<ExifTag> getAllTagsForIfd(int ifd) {
- IfdData d = mIfdDatas[ifd];
- if (d == null) {
- return null;
- }
- ExifTag[] tags = d.getAllTags();
- if (tags == null) {
- return null;
- }
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>(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<ExifTag> getAllTagsForTagId(short tag) {
- ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
- 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.
- * <p>
- * 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<Short> sOffsetTags = new HashSet<Short>();
- 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<Short> sBannedDefines = new HashSet<Short>(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:
- * <ul>
- * <li>TOP_LEFT is the normal orientation.</li>
- * <li>TOP_RIGHT is a left-right mirror.</li>
- * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
- * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
- * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
- * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
- * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
- * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
- * </ul>
- */
- 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.
- * <p>
- * e.g.
- * <p>
- * 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<ExifTag> 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<ExifTag> 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<ExifTag> 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<ExifTag> 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<ExifTag> 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<ExifTag> 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<ExifTag> 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}
- * <p>
- * 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<ExifTag> 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<Integer> l = new ArrayList<Integer>();
- 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<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
- 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.
- * <p>
- * Below is an example of writing EXIF data into a file
- *
- * <pre>
- * 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();
- * }
- * }
- * }
- * }
- * </pre>
- */
-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<ExifTag> 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<ExifTag> stripNullValueTags(ExifData data) {
- ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
- 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.
- * <p>
- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
- * parser.
- *
- * <pre>
- * 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.
- * }
- * </pre>
- */
-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<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
-
- 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<Integer, Object> 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.
- * <p>
- * 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.
- * <p>
- * 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<Integer, Object> 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
- * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
- * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition of this tag is not 1.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
- * <li>The length of the string is not equal to (component count -1) or
- * (component count) in the definition for this tag.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
- * or {@link #TYPE_RATIONAL}.</li>
- * <li>The value overflows.</li>
- * <li>The value.length does NOT match the component count in the definition
- * for this tag.</li>
- * </ul>
- *
- * @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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
- * or {@link #TYPE_RATIONAL}.</li>
- * <li>The value overflows.</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- *
- * @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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
- * {@link #TYPE_UNDEFINED} .</li>
- * <li>The length does NOT match the component count in the definition for
- * this tag.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
- * {@link #TYPE_UNDEFINED} .</li>
- * <li>The component count in the definition for this tag is not 1.</li>
- * </ul>
- */
- 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:
- * <ul>
- * <li>The corresponding setValue method for the class of the object passed
- * in would fail.</li>
- * <li>There is no obvious way to cast the object passed in into an EXIF tag
- * type.</li>
- * </ul>
- */
- 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<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
- 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;
- }
-}