summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/ApplicationPackageManager.java5
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java16
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/net/NetworkUtils.java44
-rw-r--r--core/java/android/os/INetworkManagementService.aidl12
-rw-r--r--core/java/android/provider/CallLog.java8
-rw-r--r--core/java/android/provider/Settings.java27
-rw-r--r--core/java/android/provider/VoicemailContract.java161
-rw-r--r--core/java/android/view/WindowManager.java3
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java17
-rw-r--r--core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java2
11 files changed, 276 insertions, 23 deletions
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f35e746..9f81670 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1662,7 +1662,7 @@ final class ApplicationPackageManager extends PackageManager {
int flags) {
try {
mPM.addCrossProfileIntentFilter(filter, mContext.getOpPackageName(),
- mContext.getUserId(), sourceUserId, targetUserId, flags);
+ sourceUserId, targetUserId, flags);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1674,8 +1674,7 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public void clearCrossProfileIntentFilters(int sourceUserId) {
try {
- mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName(),
- mContext.getUserId());
+ mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName());
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 245db06..a659acb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1766,7 +1766,7 @@ public class DevicePolicyManager {
public static final int ENCRYPTION_STATUS_INACTIVE = 1;
/**
- * Result code for {@link #setStorageEncryption} and {@link #getStorageEncryptionStatus}:
+ * Result code for {@link #getStorageEncryptionStatus}:
* indicating that encryption is not currently active, but is currently
* being activated. This is only reported by devices that support
* encryption of data and only when the storage is currently
@@ -1782,6 +1782,13 @@ public class DevicePolicyManager {
public static final int ENCRYPTION_STATUS_ACTIVE = 3;
/**
+ * Result code for {@link #getStorageEncryptionStatus}:
+ * indicating that encryption is active, but an encryption key has not
+ * been set by the user.
+ */
+ public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4;
+
+ /**
* Activity action: begin the process of encrypting data on the device. This activity should
* be launched after using {@link #setStorageEncryption} to request encryption be activated.
* After resuming from this activity, use {@link #getStorageEncryption}
@@ -1905,12 +1912,15 @@ public class DevicePolicyManager {
* storage system does not support encryption. If the
* result is {@link #ENCRYPTION_STATUS_INACTIVE}, use {@link
* #ACTION_START_ENCRYPTION} to begin the process of encrypting or decrypting the
- * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or
+ * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, the
+ * storage system has enabled encryption but no password is set so further action
+ * may be required. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or
* {@link #ENCRYPTION_STATUS_ACTIVE}, no further action is required.
*
* @return current status of encryption. The value will be one of
* {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE},
- * {@link #ENCRYPTION_STATUS_ACTIVATING}, or{@link #ENCRYPTION_STATUS_ACTIVE}.
+ * {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY},
+ * or {@link #ENCRYPTION_STATUS_ACTIVE}.
*/
public int getStorageEncryptionStatus() {
return getStorageEncryptionStatus(UserHandle.myUserId());
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b518498..3e5d362 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -261,9 +261,9 @@ interface IPackageManager {
void clearPackagePersistentPreferredActivities(String packageName, int userId);
void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage,
- int ownerUserId, int sourceUserId, int targetUserId, int flags);
+ int sourceUserId, int targetUserId, int flags);
- void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage, int ownerUserId);
+ void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d2a2997..8003afb 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -56,6 +56,30 @@ public class NetworkUtils {
/**
* Start the DHCP client daemon, in order to have it request addresses
+ * for the named interface. This returns {@code true} if the DHCPv4 daemon
+ * starts, {@code false} otherwise. This call blocks until such time as a
+ * result is available or the default discovery timeout has been reached.
+ * Callers should check {@link #getDhcpResults} to determine whether DHCP
+ * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcp(String interfaceName);
+
+ /**
+ * Initiate renewal on the DHCP client daemon for the named interface. This
+ * returns {@code true} if the DHCPv4 daemon has been notified, {@code false}
+ * otherwise. This call blocks until such time as a result is available or
+ * the default renew timeout has been reached. Callers should check
+ * {@link #getDhcpResults} to determine whether DHCP succeeded or failed,
+ * and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcpRenew(String interfaceName);
+
+ /**
+ * Start the DHCP client daemon, in order to have it request addresses
* for the named interface, and then configure the interface with those
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
@@ -64,17 +88,31 @@ public class NetworkUtils {
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
/**
- * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
+ * Initiate renewal on the DHCP client daemon. This call blocks until it obtains
* a result (either success or failure) from the daemon.
* @param interfaceName the name of the interface to configure
* @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
+
+ /**
+ * Fetch results from the DHCP client daemon. This call returns {@code true} if
+ * if there are results available to be read, {@code false} otherwise.
+ * @param interfaceName the name of the interface to configure
+ * @param dhcpResults if the request succeeds, this object is filled in with
+ * the IP address information.
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults);
/**
* Shut down the DHCP client daemon.
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f0660eb..f93550a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -178,6 +178,18 @@ interface INetworkManagementService
String[] getDnsForwarders();
/**
+ * Enables unidirectional packet forwarding from {@code fromIface} to
+ * {@code toIface}.
+ */
+ void startInterfaceForwarding(String fromIface, String toIface);
+
+ /**
+ * Disables unidirectional packet forwarding from {@code fromIface} to
+ * {@code toIface}.
+ */
+ void stopInterfaceForwarding(String fromIface, String toIface);
+
+ /**
* Enables Network Address Translation between two interfaces.
* The address and netmask of the external interface is used for
* the NAT'ed network.
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 7d57233..6517f35 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -454,6 +454,7 @@ public class CallLog {
long start, int duration, Long dataUsage, boolean addForAllUsers) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
+ boolean isHidden = false;
TelecomManager tm = null;
try {
@@ -468,6 +469,12 @@ public class CallLog {
if (address != null) {
accountAddress = address.getSchemeSpecificPart();
}
+ } else {
+ // We could not find the account through telecom. For call log entries that
+ // are added with a phone account which is not registered, we automatically
+ // mark them as hidden. They are unhidden once the account is registered.
+ Log.i(LOG_TAG, "Marking call log entry as hidden.");
+ isHidden = true;
}
}
@@ -513,6 +520,7 @@ public class CallLog {
values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
+ values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0));
values.put(NEW, Integer.valueOf(1));
if (callType == MISSED_TYPE) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cc84932..4c452aa 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7146,6 +7146,33 @@ public final class Settings {
public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
/**
+ * Whether WFC is enabled
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+ /**
+ * WFC Mode.
+ * <p>
+ * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+ /**
+ * Whether WFC roaming is enabled
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+ /**
* Global override to disable VoLTE (independent of user setting)
* <p>
* Type: int (1 for disable VoLTE, 0 to use user configuration)
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index d71ad03..0da4fd5 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -19,10 +19,18 @@ package android.provider;
import android.Manifest;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
+import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.Voicemail;
+
+import java.util.List;
/**
* The contract between the voicemail provider and applications. Contains
@@ -199,13 +207,100 @@ public class VoicemailContract {
*/
public static final String _DATA = "_data";
+ // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
+ // that was encoded into call log databases.
+
+ /**
+ * The component name of the account in string form.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
+
+ /**
+ * The identifier of a account that is unique to a specified component.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_ID = "subscription_id";
+
+ /**
+ * Flag used to indicate that local, unsynced changes are present.
+ * Currently, this is used to indicate that the voicemail was read or deleted.
+ * The value will be 1 if dirty is true, 0 if false.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DIRTY = "dirty";
+
+ /**
+ * Flag used to indicate that the voicemail was deleted but not synced to the server.
+ * A deleted row should be ignored.
+ * The value will be 1 if deleted is true, 0 if false.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
+
/**
* A convenience method to build voicemail URI specific to a source package by appending
* {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
*/
public static Uri buildSourceUri(String packageName) {
return Voicemails.CONTENT_URI.buildUpon()
- .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build();
+ .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName)
+ .build();
+ }
+
+ /**
+ * Inserts a new voicemail into the voicemail content provider.
+ *
+ * @param context The context of the app doing the inserting
+ * @param voicemail Data to be inserted
+ * @return {@link Uri} of the newly inserted {@link Voicemail}
+ */
+ public static Uri insert(Context context, Voicemail voicemail) {
+ ContentResolver contentResolver = context.getContentResolver();
+ ContentValues contentValues = getContentValues(voicemail);
+ return contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ }
+
+ /**
+ * Inserts a list of voicemails into the voicemail content provider.
+ *
+ * @param context The context of the app doing the inserting
+ * @param voicemails Data to be inserted
+ * @return the number of voicemails inserted
+ */
+ public static int insert(Context context, List<Voicemail> voicemails) {
+ ContentResolver contentResolver = context.getContentResolver();
+ int count = voicemails.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues contentValues = getContentValues(voicemails.get(i));
+ contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ }
+ return count;
+ }
+
+ /**
+ * Clears all voicemails accessible to this voicemail content provider for the calling
+ * package. By default, a package only has permission to delete voicemails it inserted.
+ *
+ * @return the number of voicemails deleted
+ */
+ public static int deleteAll(Context context) {
+ return context.getContentResolver().delete(
+ buildSourceUri(context.getPackageName()), "", new String[0]);
+ }
+
+ /**
+ * Maps structured {@link Voicemail} to {@link ContentValues} in content provider.
+ */
+ private static ContentValues getContentValues(Voicemail voicemail) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Voicemails.DATE, String.valueOf(voicemail.getTimestampMillis()));
+ contentValues.put(Voicemails.NUMBER, voicemail.getNumber());
+ contentValues.put(Voicemails.DURATION, String.valueOf(voicemail.getDuration()));
+ contentValues.put(Voicemails.SOURCE_PACKAGE, voicemail.getSourcePackage());
+ contentValues.put(Voicemails.SOURCE_DATA, voicemail.getSourceData());
+ contentValues.put(Voicemails.IS_READ, voicemail.isRead() ? 1 : 0);
+ return contentValues;
}
}
@@ -222,10 +317,27 @@ public class VoicemailContract {
private Status() {
}
/**
- * The package name of the voicemail source. There can only be a one entry per source.
+ * The package name of the voicemail source. There can only be a one entry per account
+ * per source.
* <P>Type: TEXT</P>
*/
public static final String SOURCE_PACKAGE = SOURCE_PACKAGE_FIELD;
+
+ // Note: Multiple entries may exist for a single source if they are differentiated by the
+ // PHONE_ACCOUNT_* fields.
+
+ /**
+ * The component name of the account in string form.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
+
+ /**
+ * The identifier of a account that is unique to a specified component.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_ID = "phone_account_id";
+
/**
* The URI to call to invoke source specific voicemail settings screen. On a user request
* to setup voicemail an intent with action VIEW with this URI will be fired by the system.
@@ -318,5 +430,50 @@ public class VoicemailContract {
return Status.CONTENT_URI.buildUpon()
.appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build();
}
+
+ /**
+ * A helper method to set the status of a voicemail source.
+ *
+ * @param context The context from the package calling the method. This will be the source.
+ * @param accountHandle The handle for the account the source is associated with.
+ * @param configurationState See {@link Status#CONFIGURATION_STATE}
+ * @param dataChannelState See {@link Status#DATA_CHANNEL_STATE}
+ * @param notificationChannelState See {@link Status#NOTIFICATION_CHANNEL_STATE}
+ */
+ public static void setStatus(Context context, PhoneAccountHandle accountHandle,
+ int configurationState, int dataChannelState, int notificationChannelState) {
+ ContentResolver contentResolver = context.getContentResolver();
+ Uri statusUri = buildSourceUri(context.getPackageName());
+ ContentValues values = new ContentValues();
+ values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME,
+ accountHandle.getComponentName().toString());
+ values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId());
+ values.put(Status.CONFIGURATION_STATE, configurationState);
+ values.put(Status.DATA_CHANNEL_STATE, dataChannelState);
+ values.put(Status.NOTIFICATION_CHANNEL_STATE, notificationChannelState);
+
+ if (isStatusPresent(contentResolver, statusUri)) {
+ contentResolver.update(statusUri, values, null, null);
+ } else {
+ contentResolver.insert(statusUri, values);
+ }
+ }
+
+ /**
+ * Determines if a voicemail source exists in the status table.
+ *
+ * @param contentResolver A content resolver constructed from the appropriate context.
+ * @param statusUri The content uri for the source.
+ * @return {@code true} if a status entry for this source exists
+ */
+ private static boolean isStatusPresent(ContentResolver contentResolver, Uri statusUri) {
+ Cursor cursor = null;
+ try {
+ cursor = contentResolver.query(statusUri, null, null, null, null);
+ return cursor != null && cursor.getCount() != 0;
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 905d6d7..66dae7b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1989,7 +1989,8 @@ public interface WindowManager extends ViewManager {
if (userActivityTimeout >= 0) {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
}
- if (!surfaceInsets.equals(Insets.NONE) || hasManualSurfaceInsets) {
+ if (surfaceInsets.left != 0 || surfaceInsets.top != 0 || surfaceInsets.right != 0 ||
+ surfaceInsets.bottom != 0 || hasManualSurfaceInsets) {
sb.append(" surfaceInsets=").append(surfaceInsets);
if (hasManualSurfaceInsets) {
sb.append(" (manual)");
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index fb0ffb0..e32a3a2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -84,7 +84,6 @@ public class LocalTransport extends BackupTransport {
private File mRestoreSetDir;
private File mRestoreSetIncrementalDir;
private File mRestoreSetFullDir;
- private long mRestoreToken;
// Additional bookkeeping for full backup
private String mFullTargetPackage;
@@ -93,20 +92,22 @@ public class LocalTransport extends BackupTransport {
private BufferedOutputStream mFullBackupOutputStream;
private byte[] mFullBackupBuffer;
- private File mFullRestoreSetDir;
- private HashSet<String> mFullRestorePackages;
private FileInputStream mCurFullRestoreStream;
private FileOutputStream mFullRestoreSocketStream;
private byte[] mFullRestoreBuffer;
- public LocalTransport(Context context) {
- mContext = context;
+ private void makeDataDirs() {
mCurrentSetDir.mkdirs();
- mCurrentSetFullDir.mkdir();
- mCurrentSetIncrementalDir.mkdir();
if (!SELinux.restorecon(mCurrentSetDir)) {
Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
}
+ mCurrentSetFullDir.mkdir();
+ mCurrentSetIncrementalDir.mkdir();
+ }
+
+ public LocalTransport(Context context) {
+ mContext = context;
+ makeDataDirs();
}
@Override
@@ -151,6 +152,7 @@ public class LocalTransport extends BackupTransport {
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
deleteContents(mCurrentSetDir);
+ makeDataDirs();
return TRANSPORT_OK;
}
@@ -425,7 +427,6 @@ public class LocalTransport extends BackupTransport {
+ " matching packages");
mRestorePackages = packages;
mRestorePackage = -1;
- mRestoreToken = token;
mRestoreSetDir = new File(mDataDir, Long.toString(token));
mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
index 06838c9..526e2ae 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -45,7 +45,7 @@ public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeI
private static float[] createLUT(TimeInterpolator interpolator, long duration) {
long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
- int numAnimFrames = (int) Math.ceil(duration / animIntervalMs);
+ int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
float values[] = new float[numAnimFrames];
float lastFrame = numAnimFrames - 1;
for (int i = 0; i < numAnimFrames; i++) {