diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cf6f003d5..b60c73569 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,15 +1,15 @@ - - - - + + + + + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true"> \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java index 9f5dbf63b..422d8b13d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java +++ b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java @@ -1,20 +1,18 @@ package bruhcollective.itaysonlab.libvkx.client; -import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; -import static ru.vtosters.lite.utils.Preferences.isValidSignature; - import android.annotation.SuppressLint; import android.content.Context; import android.os.RemoteException; - +import bruhcollective.itaysonlab.libvkx.ILibVkxService; import com.vk.dto.music.MusicTrack; import com.vk.music.common.MusicPlaybackLaunchContext; import java.util.ArrayList; import java.util.List; -import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; +import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import static ru.vtosters.lite.utils.Preferences.isValidSignature; public class LibVKXClient { @SuppressLint("StaticFieldLeak") @@ -78,7 +76,7 @@ public class LibVKXClient { } } - public static void lambdaplay(List list, MusicTrack musicTrack, ILibVkxService iLibVkxService){ + public static void lambdaplay(List list, MusicTrack musicTrack, ILibVkxService iLibVkxService) { lambdaplay(list, musicTrack, iLibVkxService, null); } diff --git a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java index 11b862856..79f15ff4d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java +++ b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java @@ -1,7 +1,5 @@ package bruhcollective.itaysonlab.libvkx.client; -import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -10,10 +8,11 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.IBinder; import android.os.RemoteException; +import bruhcollective.itaysonlab.libvkx.ILibVkxService; import java.util.List; -import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; public class LibVKXClientImpl { private ILibVkxService serviceInstance; diff --git a/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java b/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java index 2d0bbb359..75d0ab028 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java +++ b/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java @@ -5,24 +5,21 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; - import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; - import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber; import com.vtosters.lite.R; +import ru.vtosters.lite.utils.AndroidUtils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import ru.vtosters.lite.utils.AndroidUtils; - /** * Created by Aefyr on 20.05.2018. */ public class NotificationsHelper { -// private static final int NOTIFICATION_ID_BASE = 3921337; + // private static final int NOTIFICATION_ID_BASE = 3921337; private static final String NOTIFICATION_CHANNEL_ID = "tgss_chan_v5"; private static final int MAX_UPDATE_NOTIFICATIONS_PER_SECOND = 3; private static final int MIN_TIME_BETWEEN_UPDATE_NOTIFICATIONS = 1000 / MAX_UPDATE_NOTIFICATIONS_PER_SECOND; diff --git a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java index 4b35f7f6d..dcb73ccf2 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java +++ b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java @@ -3,7 +3,6 @@ package com.aefyr.tsg.g2; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java b/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java index 8decf3eac..2972d4f6f 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java +++ b/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java @@ -8,9 +8,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.AsyncTask; import android.util.Log; - import com.aefyr.tsg.g2.TelegramStickersPack; - import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java b/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java index dd1cc967b..08b59c8f0 100644 --- a/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java +++ b/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java @@ -5,12 +5,11 @@ import android.graphics.BitmapFactory; import android.os.Handler; import android.os.Looper; import android.util.Log; - import com.aefyr.tsg.g2.stickersgrabber.util.Flag; import com.aefyr.tsg.g2.stickersgrabber.util.GoalCounter; - import org.json.JSONArray; import org.json.JSONObject; +import ru.vtosters.lite.net.*; import java.io.File; import java.io.FileOutputStream; @@ -27,12 +26,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import ru.vtosters.lite.net.NetCall; -import ru.vtosters.lite.net.NetCallback; -import ru.vtosters.lite.net.NetClient; -import ru.vtosters.lite.net.NetRequest; -import ru.vtosters.lite.net.NetResponse; - /** * Created by Aefyr on 11.05.2018. */ diff --git a/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java b/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java index 02d2b62ae..cdd3afa48 100755 --- a/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java +++ b/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java @@ -2,7 +2,6 @@ package com.arthenica.ffmpegkit; import android.os.Build; import android.util.Log; - import com.arthenica.smartexception.java.Exceptions; import java.util.Collections; diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java index d6423103e..df22f8e98 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java @@ -35,243 +35,293 @@ import java.util.Map.Entry; */ public class ArscBlamer { - /** Maps package key pool indices to blamed resources. */ - private final Map[]> keyToBlame = new HashMap<>(); + /** + * Maps package key pool indices to blamed resources. + */ + private final Map[]> keyToBlame = new HashMap<>(); - /** Maps types to blamed resources. */ - private final Map[]> typeToBlame = new HashMap<>(); + /** + * Maps types to blamed resources. + */ + private final Map[]> typeToBlame = new HashMap<>(); - /** Maps package to blamed resources. */ - private final Multimap packageToBlame = HashMultimap.create(); + /** + * Maps package to blamed resources. + */ + private final Multimap packageToBlame = HashMultimap.create(); - /** Maps string indices to blamed resources. */ - private final List[] stringToBlame; + /** + * Maps string indices to blamed resources. + */ + private final List[] stringToBlame; - /** Maps type chunk entries to blamed resources. */ - private final Multimap typeEntryToBlame = HashMultimap.create(); + /** + * Maps type chunk entries to blamed resources. + */ + private final Multimap typeEntryToBlame = HashMultimap.create(); - /** Maps resources to the type chunk entries they reference. */ - private Multimap resourceEntries; + /** + * Maps resources to the type chunk entries they reference. + */ + private Multimap resourceEntries; - /** Maps resources which have no base config to the type chunk entries they reference. */ - private Multimap baselessKeys; + /** + * Maps resources which have no base config to the type chunk entries they reference. + */ + private Multimap baselessKeys; - /** Contains all of the type chunks in {@link #resourceTable}. */ - private List typeChunks; + /** + * Contains all of the type chunks in {@link #resourceTable}. + */ + private List typeChunks; - /** This is the {@link ResourceTableChunk} inside of the resources.arsc file in the APK. */ - private final ResourceTableChunk resourceTable; + /** + * This is the {@link ResourceTableChunk} inside of the resources.arsc file in the APK. + */ + private final ResourceTableChunk resourceTable; - /** - * Creates a new {@link ArscBlamer}. - * - * @param resourceTable The resources.arsc resource table to blame. - */ - public ArscBlamer(ResourceTableChunk resourceTable) { - this.resourceTable = resourceTable; - this.stringToBlame = createEntryListArray(resourceTable.getStringPool().getStringCount()); - } - - /** Generates blame mappings. */ - public void blame() { - Multimap entries = getResourceEntries(); - for (Entry> entry : entries.asMap().entrySet()) { - ResourceEntry resourceEntry = entry.getKey(); - PackageChunk packageChunk = Preconditions.checkNotNull( - resourceTable.getPackage(resourceEntry.packageName())); - int keyCount = packageChunk.getKeyStringPool().getStringCount(); - int typeCount = packageChunk.getTypeStringPool().getStringCount(); - for (TypeChunk.Entry chunkEntry : entry.getValue()) { - blameKeyOrType(keyToBlame, packageChunk, chunkEntry.keyIndex(), resourceEntry, keyCount); - blameKeyOrType(typeToBlame, packageChunk, chunkEntry.parent().getId() - 1, resourceEntry, - typeCount); - blameFromTypeChunkEntry(chunkEntry); - } - blamePackage(packageChunk, resourceEntry); + /** + * Creates a new {@link ArscBlamer}. + * + * @param resourceTable The resources.arsc resource table to blame. + */ + public ArscBlamer(ResourceTableChunk resourceTable) { + this.resourceTable = resourceTable; + this.stringToBlame = createEntryListArray(resourceTable.getStringPool().getStringCount()); } - Multimaps.invertFrom(entries, typeEntryToBlame); - for (TypeChunk.Entry entry : typeEntryToBlame.keySet()) { - blameFromTypeChunkEntry(entry); - } - } - private void blameKeyOrType(Map[]> keyOrType, - PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) { - if (!keyOrType.containsKey(packageChunk)) { - keyOrType.put(packageChunk, createEntryListArray(entryCount)); - } - keyOrType.get(packageChunk)[keyIndex].add(entry); - } - - private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) { - packageToBlame.put(packageChunk, entry); - } - - private void blameFromTypeChunkEntry(TypeChunk.Entry chunkEntry) { - for (BinaryResourceValue value : getAllResourceValues(chunkEntry)) { - for (ResourceEntry entry : typeEntryToBlame.get(chunkEntry)) { - switch (value.type()) { - case STRING: - blameString(value.data(), entry); - break; - default: - break; + /** + * Generates blame mappings. + */ + public void blame() { + Multimap entries = getResourceEntries(); + for (Entry> entry : entries.asMap().entrySet()) { + ResourceEntry resourceEntry = entry.getKey(); + PackageChunk packageChunk = Preconditions.checkNotNull( + resourceTable.getPackage(resourceEntry.packageName())); + int keyCount = packageChunk.getKeyStringPool().getStringCount(); + int typeCount = packageChunk.getTypeStringPool().getStringCount(); + for (TypeChunk.Entry chunkEntry : entry.getValue()) { + blameKeyOrType(keyToBlame, packageChunk, chunkEntry.keyIndex(), resourceEntry, keyCount); + blameKeyOrType(typeToBlame, packageChunk, chunkEntry.parent().getId() - 1, resourceEntry, + typeCount); + blameFromTypeChunkEntry(chunkEntry); + } + blamePackage(packageChunk, resourceEntry); + } + Multimaps.invertFrom(entries, typeEntryToBlame); + for (TypeChunk.Entry entry : typeEntryToBlame.keySet()) { + blameFromTypeChunkEntry(entry); } - } - } - } - - /** Returns all {@link BinaryResourceValue} for a single {@code entry}. */ - private Collection getAllResourceValues(TypeChunk.Entry entry) { - Set values = new HashSet(); - BinaryResourceValue binaryResourceValue = entry.value(); - if (binaryResourceValue != null) { - values.add(binaryResourceValue); - } - for (BinaryResourceValue value : entry.values().values()) { - values.add(value); - } - return values; - } - - private void blameString(int stringIndex, ResourceEntry entry) { - stringToBlame[stringIndex].add(entry); - } - - /** Must first call {@link #blame}. */ - public Map[]> getKeyToBlamedResources() { - return Collections.unmodifiableMap(keyToBlame); - } - - /** Must first call {@link #blame}. */ - public Map[]> getTypeToBlamedResources() { - return Collections.unmodifiableMap(typeToBlame); - } - - /** Must first call {@link #blame}. */ - public Multimap getPackageToBlamedResources() { - return Multimaps.unmodifiableMultimap(packageToBlame); - } - - /** Must first call {@link #blame}. */ - public List[] getStringToBlamedResources() { - return stringToBlame; - } - - /** Must first call {@link #blame}. */ - public Multimap getTypeEntryToBlamedResources() { - return Multimaps.unmodifiableMultimap(typeEntryToBlame); - } - - /** Returns a multimap of keys for which there is no default resource. */ - public Multimap getBaselessKeys() { - if (baselessKeys != null) { - return baselessKeys; - } - Multimap result = HashMultimap.create(); - for (Entry> entry - : getResourceEntries().asMap().entrySet()) { - Collection chunkEntries = entry.getValue(); - if (!hasBaseConfiguration(chunkEntries)) { - result.putAll(entry.getKey(), chunkEntries); - } - } - baselessKeys = result; - return result; - } - - /** Returns a multimap of resource entries to the chunk entries they reference in this APK. */ - public Multimap getResourceEntries() { - if (resourceEntries != null) { - return resourceEntries; - } - Multimap result = HashMultimap.create(); - for (TypeChunk typeChunk : getTypeChunks()) { - for (TypeChunk.Entry entry : typeChunk.getEntries().values()) { - result.put(ResourceEntry.create(entry), entry); - } - } - resourceEntries = result; - return result; - } - - /** Returns all {@link TypeChunk} in resources.arsc. */ - public List getTypeChunks() { - if (typeChunks != null) { - return typeChunks; - } - List result = new ArrayList<>(); - for (PackageChunk packageChunk : resourceTable.getPackages()) { - for (TypeChunk typeChunk : packageChunk.getTypeChunks()) { - result.add(typeChunk); - } - } - typeChunks = result; - return result; - } - - private boolean hasBaseConfiguration(Collection entries) { - for (TypeChunk.Entry entry : entries) { - if (entry.parent().getConfiguration().isDefault()) { - return true; - } - } - return false; - } - - private static List[] createEntryListArray(int size) { - ArrayListResourceEntry[] result = new ArrayListResourceEntry[size]; - for (int i = 0; i < size; ++i) { - result[i] = new ArrayListResourceEntry(); - } - return result; - } - - /** Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}. */ - private static class ArrayListResourceEntry extends ArrayList { - - private ArrayListResourceEntry() { - super(2); // ~90-95% of these lists end up with only 1 or 2 elements. - } - } - - /** Describes a single resource entry. */ - public static class ResourceEntry { - private final String packageName; - private final String typeName; - private final String entryName; - - static ResourceEntry create(TypeChunk.Entry entry) { - PackageChunk packageChunk = Preconditions.checkNotNull(entry.parent().getPackageChunk()); - String packageName = packageChunk.getPackageName(); - String typeName = entry.typeName(); - String entryName = entry.key(); - return new ResourceEntry(packageName, typeName, entryName); } - private ResourceEntry(String packageName, String typeName, String entryName) { - this.packageName = packageName; - this.typeName = typeName; - this.entryName = entryName; + private void blameKeyOrType(Map[]> keyOrType, + PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) { + if (!keyOrType.containsKey(packageChunk)) { + keyOrType.put(packageChunk, createEntryListArray(entryCount)); + } + keyOrType.get(packageChunk)[keyIndex].add(entry); } - public String packageName() { return packageName; } - public String typeName() { return typeName; } - public String entryName() { return entryName; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ResourceEntry that = (ResourceEntry)o; - return Objects.equals(packageName, that.packageName) && - Objects.equals(typeName, that.typeName) && - Objects.equals(entryName, that.entryName); + private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) { + packageToBlame.put(packageChunk, entry); } - @Override - public int hashCode() { - return Objects.hash(packageName, typeName, entryName); + private void blameFromTypeChunkEntry(TypeChunk.Entry chunkEntry) { + for (BinaryResourceValue value : getAllResourceValues(chunkEntry)) { + for (ResourceEntry entry : typeEntryToBlame.get(chunkEntry)) { + switch (value.type()) { + case STRING: + blameString(value.data(), entry); + break; + default: + break; + } + } + } + } + + /** + * Returns all {@link BinaryResourceValue} for a single {@code entry}. + */ + private Collection getAllResourceValues(TypeChunk.Entry entry) { + Set values = new HashSet(); + BinaryResourceValue binaryResourceValue = entry.value(); + if (binaryResourceValue != null) { + values.add(binaryResourceValue); + } + for (BinaryResourceValue value : entry.values().values()) { + values.add(value); + } + return values; + } + + private void blameString(int stringIndex, ResourceEntry entry) { + stringToBlame[stringIndex].add(entry); + } + + /** + * Must first call {@link #blame}. + */ + public Map[]> getKeyToBlamedResources() { + return Collections.unmodifiableMap(keyToBlame); + } + + /** + * Must first call {@link #blame}. + */ + public Map[]> getTypeToBlamedResources() { + return Collections.unmodifiableMap(typeToBlame); + } + + /** + * Must first call {@link #blame}. + */ + public Multimap getPackageToBlamedResources() { + return Multimaps.unmodifiableMultimap(packageToBlame); + } + + /** + * Must first call {@link #blame}. + */ + public List[] getStringToBlamedResources() { + return stringToBlame; + } + + /** + * Must first call {@link #blame}. + */ + public Multimap getTypeEntryToBlamedResources() { + return Multimaps.unmodifiableMultimap(typeEntryToBlame); + } + + /** + * Returns a multimap of keys for which there is no default resource. + */ + public Multimap getBaselessKeys() { + if (baselessKeys != null) { + return baselessKeys; + } + Multimap result = HashMultimap.create(); + for (Entry> entry + : getResourceEntries().asMap().entrySet()) { + Collection chunkEntries = entry.getValue(); + if (!hasBaseConfiguration(chunkEntries)) { + result.putAll(entry.getKey(), chunkEntries); + } + } + baselessKeys = result; + return result; + } + + /** + * Returns a multimap of resource entries to the chunk entries they reference in this APK. + */ + public Multimap getResourceEntries() { + if (resourceEntries != null) { + return resourceEntries; + } + Multimap result = HashMultimap.create(); + for (TypeChunk typeChunk : getTypeChunks()) { + for (TypeChunk.Entry entry : typeChunk.getEntries().values()) { + result.put(ResourceEntry.create(entry), entry); + } + } + resourceEntries = result; + return result; + } + + /** + * Returns all {@link TypeChunk} in resources.arsc. + */ + public List getTypeChunks() { + if (typeChunks != null) { + return typeChunks; + } + List result = new ArrayList<>(); + for (PackageChunk packageChunk : resourceTable.getPackages()) { + for (TypeChunk typeChunk : packageChunk.getTypeChunks()) { + result.add(typeChunk); + } + } + typeChunks = result; + return result; + } + + private boolean hasBaseConfiguration(Collection entries) { + for (TypeChunk.Entry entry : entries) { + if (entry.parent().getConfiguration().isDefault()) { + return true; + } + } + return false; + } + + private static List[] createEntryListArray(int size) { + ArrayListResourceEntry[] result = new ArrayListResourceEntry[size]; + for (int i = 0; i < size; ++i) { + result[i] = new ArrayListResourceEntry(); + } + return result; + } + + /** + * Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}. + */ + private static class ArrayListResourceEntry extends ArrayList { + + private ArrayListResourceEntry() { + super(2); // ~90-95% of these lists end up with only 1 or 2 elements. + } + } + + /** + * Describes a single resource entry. + */ + public static class ResourceEntry { + private final String packageName; + private final String typeName; + private final String entryName; + + static ResourceEntry create(TypeChunk.Entry entry) { + PackageChunk packageChunk = Preconditions.checkNotNull(entry.parent().getPackageChunk()); + String packageName = packageChunk.getPackageName(); + String typeName = entry.typeName(); + String entryName = entry.key(); + return new ResourceEntry(packageName, typeName, entryName); + } + + private ResourceEntry(String packageName, String typeName, String entryName) { + this.packageName = packageName; + this.typeName = typeName; + this.entryName = entryName; + } + + public String packageName() { + return packageName; + } + + public String typeName() { + return typeName; + } + + public String entryName() { + return entryName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResourceEntry that = (ResourceEntry) o; + return Objects.equals(packageName, that.packageName) && + Objects.equals(typeName, that.typeName) && + Objects.equals(entryName, that.entryName); + } + + @Override + public int hashCode() { + return Objects.hash(packageName, typeName, entryName); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java index 96f98784f..2da6a2d4f 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java @@ -27,48 +27,54 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** Given an arsc file, maps the contents of the file. */ +/** + * Given an arsc file, maps the contents of the file. + */ public final class BinaryResourceFile implements SerializableResource { - /** The chunks contained in this resource file. */ - private final List chunks = new ArrayList<>(); + /** + * The chunks contained in this resource file. + */ + private final List chunks = new ArrayList<>(); - public BinaryResourceFile(byte[] buf) { - ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); - while (buffer.remaining() > 0) { - chunks.add(Chunk.newInstance(buffer)); + public BinaryResourceFile(byte[] buf) { + ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + while (buffer.remaining() > 0) { + chunks.add(Chunk.newInstance(buffer)); + } } - } - /** - * Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile} - * representing the contents of the stream. - * - * @param is The input stream to read from. - * @return BinaryResourceFile represented by the @{link InputStream}. - * @throws IOException - */ - public static BinaryResourceFile fromInputStream(InputStream is) throws IOException { - byte[] buf = ByteStreams.toByteArray(is); - return new BinaryResourceFile(buf); - } + /** + * Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile} + * representing the contents of the stream. + * + * @param is The input stream to read from. + * @return BinaryResourceFile represented by the @{link InputStream}. + * @throws IOException + */ + public static BinaryResourceFile fromInputStream(InputStream is) throws IOException { + byte[] buf = ByteStreams.toByteArray(is); + return new BinaryResourceFile(buf); + } - /** Returns the chunks in this resource file. */ - public List getChunks() { - return Collections.unmodifiableList(chunks); - } + /** + * Returns the chunks in this resource file. + */ + public List getChunks() { + return Collections.unmodifiableList(chunks); + } - @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); - } + @Override + public byte[] toByteArray() throws IOException { + return toByteArray(false); + } - @Override - public byte[] toByteArray(boolean shrink) throws IOException { - ByteArrayDataOutput output = ByteStreams.newDataOutput(); - for (Chunk chunk : chunks) { - output.write(chunk.toByteArray(shrink)); + @Override + public byte[] toByteArray(boolean shrink) throws IOException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + for (Chunk chunk : chunks) { + output.write(chunk.toByteArray(shrink)); + } + return output.toByteArray(); } - return output.toByteArray(); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java index a3b80ab77..32a61cf60 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java @@ -27,73 +27,97 @@ import java.util.Objects; */ public class BinaryResourceIdentifier { - /** The {@link PackageChunk} id mask for a packed resource id of the form 0xpptteeee. */ - private static final int PACKAGE_ID_MASK = 0xFF000000; - private static final int PACKAGE_ID_SHIFT = 24; + /** + * The {@link PackageChunk} id mask for a packed resource id of the form 0xpptteeee. + */ + private static final int PACKAGE_ID_MASK = 0xFF000000; + private static final int PACKAGE_ID_SHIFT = 24; - /** The {@link TypeChunk} id mask for a packed resource id of the form 0xpptteeee. */ - private static final int TYPE_ID_MASK = 0x00FF0000; - private static final int TYPE_ID_SHIFT = 16; + /** + * The {@link TypeChunk} id mask for a packed resource id of the form 0xpptteeee. + */ + private static final int TYPE_ID_MASK = 0x00FF0000; + private static final int TYPE_ID_SHIFT = 16; - /** The {@link TypeChunk.Entry} id mask for a packed resource id of the form 0xpptteeee. */ - private static final int ENTRY_ID_MASK = 0xFFFF; - private static final int ENTRY_ID_SHIFT = 0; + /** + * The {@link TypeChunk.Entry} id mask for a packed resource id of the form 0xpptteeee. + */ + private static final int ENTRY_ID_MASK = 0xFFFF; + private static final int ENTRY_ID_SHIFT = 0; - private final int packageId; - private final int typeId; - private final int entryId; + private final int packageId; + private final int typeId; + private final int entryId; - /** Returns a {@link BinaryResourceIdentifier} from a {@code resourceId} of the form 0xpptteeee. */ - public static BinaryResourceIdentifier create(int resourceId) { - int packageId = (resourceId & PACKAGE_ID_MASK) >>> PACKAGE_ID_SHIFT; - int typeId = (resourceId & TYPE_ID_MASK) >>> TYPE_ID_SHIFT; - int entryId = (resourceId & ENTRY_ID_MASK) >>> ENTRY_ID_SHIFT; - return create(packageId, typeId, entryId); - } + /** + * Returns a {@link BinaryResourceIdentifier} from a {@code resourceId} of the form 0xpptteeee. + */ + public static BinaryResourceIdentifier create(int resourceId) { + int packageId = (resourceId & PACKAGE_ID_MASK) >>> PACKAGE_ID_SHIFT; + int typeId = (resourceId & TYPE_ID_MASK) >>> TYPE_ID_SHIFT; + int entryId = (resourceId & ENTRY_ID_MASK) >>> ENTRY_ID_SHIFT; + return create(packageId, typeId, entryId); + } - /** Returns a {@link BinaryResourceIdentifier} with the given identifiers. */ - public static BinaryResourceIdentifier create(int packageId, int typeId, int entryId) { - Preconditions.checkState((packageId & 0xFF) == packageId, "packageId must be <= 0xFF."); - Preconditions.checkState((typeId & 0xFF) == typeId, "typeId must be <= 0xFF."); - Preconditions.checkState((entryId & 0xFFFF) == entryId, "entryId must be <= 0xFFFF."); - return new BinaryResourceIdentifier(packageId, typeId, entryId); - } + /** + * Returns a {@link BinaryResourceIdentifier} with the given identifiers. + */ + public static BinaryResourceIdentifier create(int packageId, int typeId, int entryId) { + Preconditions.checkState((packageId & 0xFF) == packageId, "packageId must be <= 0xFF."); + Preconditions.checkState((typeId & 0xFF) == typeId, "typeId must be <= 0xFF."); + Preconditions.checkState((entryId & 0xFFFF) == entryId, "entryId must be <= 0xFFFF."); + return new BinaryResourceIdentifier(packageId, typeId, entryId); + } - private BinaryResourceIdentifier(int packageId, int typeId, int entryId) { - this.packageId = packageId; - this.typeId = typeId; - this.entryId = entryId; - } + private BinaryResourceIdentifier(int packageId, int typeId, int entryId) { + this.packageId = packageId; + this.typeId = typeId; + this.entryId = entryId; + } - /** The (1-based) id of the {@link PackageChunk} containing this resource. */ - public int packageId() { return packageId; } + /** + * The (1-based) id of the {@link PackageChunk} containing this resource. + */ + public int packageId() { + return packageId; + } - /** The (1-based) id of the {@link TypeChunk} containing this resource. */ - public int typeId() { return typeId; } + /** + * The (1-based) id of the {@link TypeChunk} containing this resource. + */ + public int typeId() { + return typeId; + } - /** The (0-based) index of the entry in a {@link TypeChunk} containing this resource. */ - public int entryId() { return entryId; } + /** + * The (0-based) index of the entry in a {@link TypeChunk} containing this resource. + */ + public int entryId() { + return entryId; + } - public int resId() { return packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId; } + public int resId() { + return packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BinaryResourceIdentifier that = (BinaryResourceIdentifier)o; - return packageId == that.packageId && - typeId == that.typeId && - entryId == that.entryId; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BinaryResourceIdentifier that = (BinaryResourceIdentifier) o; + return packageId == that.packageId && + typeId == that.typeId && + entryId == that.entryId; + } - @Override - public int hashCode() { - return Objects.hash(packageId, typeId, entryId); - } + @Override + public int hashCode() { + return Objects.hash(packageId, typeId, entryId); + } - @Override - public String toString() { - int v = packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId; - return String.format("0x%1$08x", v); - } + @Override + public String toString() { + int v = packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId; + return String.format("0x%1$08x", v); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java index 4da4e2f51..3fe721a60 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java @@ -26,139 +26,144 @@ import java.nio.charset.Charset; import static java.nio.charset.StandardCharsets.UTF_16LE; import static java.nio.charset.StandardCharsets.UTF_8; -/** Provides utilities to decode/encode a String packed in an arsc resource file. */ +/** + * Provides utilities to decode/encode a String packed in an arsc resource file. + */ public final class BinaryResourceString { - /** Type of {@link BinaryResourceString} to encode / decode. */ - public enum Type { - UTF8(UTF_8), - UTF16(UTF_16LE); + /** + * Type of {@link BinaryResourceString} to encode / decode. + */ + public enum Type { + UTF8(UTF_8), + UTF16(UTF_16LE); - private final Charset charset; + private final Charset charset; - Type(Charset charset) { - this.charset = charset; + Type(Charset charset) { + this.charset = charset; + } + + public Charset charset() { + return charset; + } } - public Charset charset() { - return charset; - } - } + private BinaryResourceString() { + } // Private constructor - private BinaryResourceString() {} // Private constructor - - /** - * Given a buffer and an offset into the buffer, returns a String. The {@code offset} is the - * 0-based byte offset from the start of the buffer where the string resides. This should be the - * location in memory where the string's character count, followed by its byte count, and then - * followed by the actual string is located. - * - *

Here's an example UTF-8-encoded string of ab©: - *

-   * 03 04 61 62 C2 A9 00
-   * ^ Offset should be here
-   * 
- * - * @param buffer The buffer containing the string to decode. - * @param offset Offset into the buffer where the string resides. - * @param type The encoding type that the {@link BinaryResourceString} is encoded in. - * @return The decoded string. - */ - public static String decodeString(ByteBuffer buffer, int offset, Type type) { - int length; - int characterCount = decodeLength(buffer, offset, type); - offset += computeLengthOffset(characterCount, type); - // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length. - // UTF-16 strings, however, only have 1 length: the number of characters. - if (type == Type.UTF8) { - length = decodeLength(buffer, offset, type); - offset += computeLengthOffset(length, type); - } else { - length = characterCount * 2; + /** + * Given a buffer and an offset into the buffer, returns a String. The {@code offset} is the + * 0-based byte offset from the start of the buffer where the string resides. This should be the + * location in memory where the string's character count, followed by its byte count, and then + * followed by the actual string is located. + * + *

Here's an example UTF-8-encoded string of ab©: + *

+     * 03 04 61 62 C2 A9 00
+     * ^ Offset should be here
+     * 
+ * + * @param buffer The buffer containing the string to decode. + * @param offset Offset into the buffer where the string resides. + * @param type The encoding type that the {@link BinaryResourceString} is encoded in. + * @return The decoded string. + */ + public static String decodeString(ByteBuffer buffer, int offset, Type type) { + int length; + int characterCount = decodeLength(buffer, offset, type); + offset += computeLengthOffset(characterCount, type); + // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length. + // UTF-16 strings, however, only have 1 length: the number of characters. + if (type == Type.UTF8) { + length = decodeLength(buffer, offset, type); + offset += computeLengthOffset(length, type); + } else { + length = characterCount * 2; + } + return new String(buffer.array(), offset, length, type.charset()); } - return new String(buffer.array(), offset, length, type.charset()); - } - /** - * Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string. - * Strings are prefixed by 2 values. The first is the number of characters in the string. - * The second is the encoding length (number of bytes in the string). - * - *

Here's an example UTF-8-encoded string of ab©: - *

03 04 61 62 C2 A9 00
- * - * @param str The string to be encoded. - * @param type The encoding type that the {@link BinaryResourceString} should be encoded in. - * @return The encoded string. - */ - public static byte[] encodeString(String str, Type type) { - byte[] bytes = str.getBytes(type.charset()); - // The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator. - ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5); - encodeLength(output, str.length(), type); - if (type == Type.UTF8) { // Only UTF-8 strings have the encoding length. - encodeLength(output, bytes.length, type); + /** + * Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string. + * Strings are prefixed by 2 values. The first is the number of characters in the string. + * The second is the encoding length (number of bytes in the string). + * + *

Here's an example UTF-8-encoded string of ab©: + *

03 04 61 62 C2 A9 00
+ * + * @param str The string to be encoded. + * @param type The encoding type that the {@link BinaryResourceString} should be encoded in. + * @return The encoded string. + */ + public static byte[] encodeString(String str, Type type) { + byte[] bytes = str.getBytes(type.charset()); + // The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator. + ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5); + encodeLength(output, str.length(), type); + if (type == Type.UTF8) { // Only UTF-8 strings have the encoding length. + encodeLength(output, bytes.length, type); + } + output.write(bytes); + // NULL-terminate the string + if (type == Type.UTF8) { + output.write(0); + } else { + output.writeShort(0); + } + return output.toByteArray(); } - output.write(bytes); - // NULL-terminate the string - if (type == Type.UTF8) { - output.write(0); - } else { - output.writeShort(0); - } - return output.toByteArray(); - } - private static void encodeLength(ByteArrayDataOutput output, int length, Type type) { - if (length < 0) { - output.write(0); - return; + private static void encodeLength(ByteArrayDataOutput output, int length, Type type) { + if (length < 0) { + output.write(0); + return; + } + if (type == Type.UTF8) { + if (length > 0x7F) { + output.write(((length & 0x7F00) >> 8) | 0x80); + } + output.write(length & 0xFF); + } else { // UTF-16 + // TODO(acornwall): Replace output with a little-endian output. + if (length > 0x7FFF) { + int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000; + output.write(highBytes & 0xFF); + output.write((highBytes & 0xFF00) >> 8); + } + int lowBytes = length & 0xFFFF; + output.write(lowBytes & 0xFF); + output.write((lowBytes & 0xFF00) >> 8); + } } - if (type == Type.UTF8) { - if (length > 0x7F) { - output.write(((length & 0x7F00) >> 8) | 0x80); - } - output.write(length & 0xFF); - } else { // UTF-16 - // TODO(acornwall): Replace output with a little-endian output. - if (length > 0x7FFF) { - int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000; - output.write(highBytes & 0xFF); - output.write((highBytes & 0xFF00) >> 8); - } - int lowBytes = length & 0xFFFF; - output.write(lowBytes & 0xFF); - output.write((lowBytes & 0xFF00) >> 8); + + private static int computeLengthOffset(int length, Type type) { + return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1); } - } - private static int computeLengthOffset(int length, Type type) { - return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1); - } - - private static int decodeLength(ByteBuffer buffer, int offset, Type type) { - return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset); - } - - private static int decodeLengthUTF8(ByteBuffer buffer, int offset) { - // UTF-8 strings use a clever variant of the 7-bit integer for packing the string length. - // If the first byte is >= 0x80, then a second byte follows. For these values, the length - // is WORD-length in big-endian & 0x7FFF. - int length = UnsignedBytes.toInt(buffer.get(offset)); - if ((length & 0x80) != 0) { - length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1)); + private static int decodeLength(ByteBuffer buffer, int offset, Type type) { + return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset); } - return length; - } - private static int decodeLengthUTF16(ByteBuffer buffer, int offset) { - // UTF-16 strings use a clever variant of the 7-bit integer for packing the string length. - // If the first word is >= 0x8000, then a second word follows. For these values, the length - // is DWORD-length in big-endian & 0x7FFFFFFF. - int length = (buffer.getShort(offset) & 0xFFFF); - if ((length & 0x8000) != 0) { - length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF); + private static int decodeLengthUTF8(ByteBuffer buffer, int offset) { + // UTF-8 strings use a clever variant of the 7-bit integer for packing the string length. + // If the first byte is >= 0x80, then a second byte follows. For these values, the length + // is WORD-length in big-endian & 0x7FFF. + int length = UnsignedBytes.toInt(buffer.get(offset)); + if ((length & 0x80) != 0) { + length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1)); + } + return length; + } + + private static int decodeLengthUTF16(ByteBuffer buffer, int offset) { + // UTF-16 strings use a clever variant of the 7-bit integer for packing the string length. + // If the first word is >= 0x8000, then a second word follows. For these values, the length + // is DWORD-length in big-endian & 0x7FFFFFFF. + int length = (buffer.getShort(offset) & 0xFFFF); + if ((length & 0x8000) != 0) { + length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF); + } + return length; } - return length; - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java index b66a53157..52c40b491 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java @@ -26,126 +26,176 @@ import java.nio.ByteOrder; import java.util.Map; import java.util.Objects; -/** Represents a single typed resource value. */ +/** + * Represents a single typed resource value. + */ public class BinaryResourceValue implements SerializableResource { - /** Resource type codes. */ - public enum Type { - /** {@code data} is either 0 (undefined) or 1 (empty). */ - NULL(0x00), - /** {@code data} holds a {@link ResourceTableChunk} entry reference. */ - REFERENCE(0x01), - /** {@code data} holds an attribute resource identifier. */ - ATTRIBUTE(0x02), - /** {@code data} holds an index into the containing resource table's string pool. */ - STRING(0x03), - /** {@code data} holds a single-precision floating point number. */ - FLOAT(0x04), - /** {@code data} holds a complex number encoding a dimension value, such as "100in". */ - DIMENSION(0x05), - /** {@code data} holds a complex number encoding a fraction of a container. */ - FRACTION(0x06), - /** {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. */ - DYNAMIC_REFERENCE(0x07), - /** {@code data} holds a dynamic attribute resource identifier. */ - DYNAMIC_ATTRIBUTE(0x08), - /** {@code data} is a raw integer value of the form n..n. */ - INT_DEC(0x10), - /** {@code data} is a raw integer value of the form 0xn..n. */ - INT_HEX(0x11), - /** {@code data} is either 0 (false) or 1 (true). */ - INT_BOOLEAN(0x12), - /** {@code data} is a raw integer value of the form #aarrggbb. */ - INT_COLOR_ARGB8(0x1c), - /** {@code data} is a raw integer value of the form #rrggbb. */ - INT_COLOR_RGB8(0x1d), - /** {@code data} is a raw integer value of the form #argb. */ - INT_COLOR_ARGB4(0x1e), - /** {@code data} is a raw integer value of the form #rgb. */ - INT_COLOR_RGB4(0x1f); + /** + * Resource type codes. + */ + public enum Type { + /** + * {@code data} is either 0 (undefined) or 1 (empty). + */ + NULL(0x00), + /** + * {@code data} holds a {@link ResourceTableChunk} entry reference. + */ + REFERENCE(0x01), + /** + * {@code data} holds an attribute resource identifier. + */ + ATTRIBUTE(0x02), + /** + * {@code data} holds an index into the containing resource table's string pool. + */ + STRING(0x03), + /** + * {@code data} holds a single-precision floating point number. + */ + FLOAT(0x04), + /** + * {@code data} holds a complex number encoding a dimension value, such as "100in". + */ + DIMENSION(0x05), + /** + * {@code data} holds a complex number encoding a fraction of a container. + */ + FRACTION(0x06), + /** + * {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. + */ + DYNAMIC_REFERENCE(0x07), + /** + * {@code data} holds a dynamic attribute resource identifier. + */ + DYNAMIC_ATTRIBUTE(0x08), + /** + * {@code data} is a raw integer value of the form n..n. + */ + INT_DEC(0x10), + /** + * {@code data} is a raw integer value of the form 0xn..n. + */ + INT_HEX(0x11), + /** + * {@code data} is either 0 (false) or 1 (true). + */ + INT_BOOLEAN(0x12), + /** + * {@code data} is a raw integer value of the form #aarrggbb. + */ + INT_COLOR_ARGB8(0x1c), + /** + * {@code data} is a raw integer value of the form #rrggbb. + */ + INT_COLOR_RGB8(0x1d), + /** + * {@code data} is a raw integer value of the form #argb. + */ + INT_COLOR_ARGB4(0x1e), + /** + * {@code data} is a raw integer value of the form #rgb. + */ + INT_COLOR_RGB4(0x1f); - private final byte code; + private final byte code; - private static final Map FROM_BYTE; + private static final Map FROM_BYTE; - static { - Builder builder = ImmutableMap.builder(); - for (Type type : values()) { - builder.put(type.code(), type); - } - FROM_BYTE = builder.build(); + static { + Builder builder = ImmutableMap.builder(); + for (Type type : values()) { + builder.put(type.code(), type); + } + FROM_BYTE = builder.build(); + } + + Type(int code) { + this.code = UnsignedBytes.checkedCast(code); + } + + public byte code() { + return code; + } + + public static Type fromCode(byte code) { + return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code); + } } - Type(int code) { - this.code = UnsignedBytes.checkedCast(code); + /** + * The serialized size in bytes of a {@link BinaryResourceValue}. + */ + public static final int SIZE = 8; + + private final int size; + private final Type type; + private final int data; + + public static BinaryResourceValue create(ByteBuffer buffer) { + int size = (buffer.getShort() & 0xFFFF); + buffer.get(); // Unused + Type type = Type.fromCode(buffer.get()); + int data = buffer.getInt(); + return new BinaryResourceValue(size, type, data); } - public byte code() { - return code; + public BinaryResourceValue(int size, Type type, int data) { + this.size = size; + this.type = type; + this.data = data; } - public static Type fromCode(byte code) { - return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code); + /** + * The length in bytes of this value. + */ + public int size() { + return size; } - } - /** The serialized size in bytes of a {@link BinaryResourceValue}. */ - public static final int SIZE = 8; + /** + * The raw data type of this value. + */ + public Type type() { + return type; + } - private final int size; - private final Type type; - private final int data; + /** + * The actual 4-byte value; interpretation of the value depends on {@code dataType}. + */ + public int data() { + return data; + } - public static BinaryResourceValue create(ByteBuffer buffer) { - int size = (buffer.getShort() & 0xFFFF); - buffer.get(); // Unused - Type type = Type.fromCode(buffer.get()); - int data = buffer.getInt(); - return new BinaryResourceValue(size, type, data); - } + @Override + public byte[] toByteArray() { + return toByteArray(false); + } - public BinaryResourceValue(int size, Type type, int data) { - this.size = size; - this.type = type; - this.data = data; - } + @Override + public byte[] toByteArray(boolean shrink) { + ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort((short) size()); + buffer.put((byte) 0); // Unused + buffer.put(type().code()); + buffer.putInt(data()); + return buffer.array(); + } - /** The length in bytes of this value. */ - public int size() { return size; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BinaryResourceValue that = (BinaryResourceValue) o; + return size == that.size && + data == that.data && + type == that.type; + } - /** The raw data type of this value. */ - public Type type() { return type; } - - /** The actual 4-byte value; interpretation of the value depends on {@code dataType}. */ - public int data() { return data; } - - @Override - public byte[] toByteArray() { - return toByteArray(false); - } - - @Override - public byte[] toByteArray(boolean shrink) { - ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); - buffer.putShort((short) size()); - buffer.put((byte) 0); // Unused - buffer.put(type().code()); - buffer.putInt(data()); - return buffer.array(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BinaryResourceValue that = (BinaryResourceValue)o; - return size == that.size && - data == that.data && - type == that.type; - } - - @Override - public int hashCode() { - return Objects.hash(size, type, data); - } + @Override + public int hashCode() { + return Objects.hash(size, type, data); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java index 0d2661486..665b8cd06 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java @@ -30,272 +30,296 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Map; -/** Represents a generic chunk. */ +/** + * Represents a generic chunk. + */ public abstract class Chunk implements SerializableResource { - /** Types of chunks that can exist. */ - public enum Type { - NULL(0x0000), - STRING_POOL(0x0001), - TABLE(0x0002), - XML(0x0003), - XML_START_NAMESPACE(0x0100), - XML_END_NAMESPACE(0x0101), - XML_START_ELEMENT(0x0102), - XML_END_ELEMENT(0x0103), - XML_CDATA(0x0104), - XML_RESOURCE_MAP(0x0180), - TABLE_PACKAGE(0x0200), - TABLE_TYPE(0x0201), - TABLE_TYPE_SPEC(0x0202), - TABLE_LIBRARY(0x0203); + /** + * Types of chunks that can exist. + */ + public enum Type { + NULL(0x0000), + STRING_POOL(0x0001), + TABLE(0x0002), + XML(0x0003), + XML_START_NAMESPACE(0x0100), + XML_END_NAMESPACE(0x0101), + XML_START_ELEMENT(0x0102), + XML_END_ELEMENT(0x0103), + XML_CDATA(0x0104), + XML_RESOURCE_MAP(0x0180), + TABLE_PACKAGE(0x0200), + TABLE_TYPE(0x0201), + TABLE_TYPE_SPEC(0x0202), + TABLE_LIBRARY(0x0203); - private final short code; + private final short code; - private static final Map FROM_SHORT; + private static final Map FROM_SHORT; - static { - Builder builder = ImmutableMap.builder(); - for (Type type : values()) { - builder.put(type.code(), type); - } - FROM_SHORT = builder.build(); + static { + Builder builder = ImmutableMap.builder(); + for (Type type : values()) { + builder.put(type.code(), type); + } + FROM_SHORT = builder.build(); + } + + Type(int code) { + this.code = Shorts.checkedCast(code); + } + + public short code() { + return code; + } + + public static Type fromCode(short code) { + return Preconditions.checkNotNull(FROM_SHORT.get(code), "Unknown chunk type: %s", code); + } } - Type(int code) { - this.code = Shorts.checkedCast(code); + /** + * The byte boundary to pad chunks on. + */ + public static final int PAD_BOUNDARY = 4; + + /** + * The number of bytes in every chunk that describes chunk type, header size, and chunk size. + */ + public static final int METADATA_SIZE = 8; + + /** + * The offset in bytes, from the start of the chunk, where the chunk size can be found. + */ + private static final int CHUNK_SIZE_OFFSET = 4; + + /** + * The parent to this chunk, if any. + */ + @Nullable + private final Chunk parent; + + /** + * Size of the chunk header in bytes. + */ + protected final int headerSize; + + /** + * headerSize + dataSize. The total size of this chunk. + */ + protected final int chunkSize; + + /** + * Offset of this chunk from the start of the file. + */ + protected final int offset; + + protected Chunk(ByteBuffer buffer, @Nullable Chunk parent) { + this.parent = parent; + offset = buffer.position() - 2; + headerSize = (buffer.getShort() & 0xFFFF); + chunkSize = buffer.getInt(); } - public short code() { - return code; + /** + * Finishes initialization of a chunk. This should be called immediately after the constructor. + * This is separate from the constructor so that the header of a chunk can be fully initialized + * before the payload of that chunk is initialized for chunks that require such behavior. + * + * @param buffer The buffer that the payload will be initialized from. + */ + protected void init(ByteBuffer buffer) { } - public static Type fromCode(short code) { - return Preconditions.checkNotNull(FROM_SHORT.get(code), "Unknown chunk type: %s", code); - } - } - - /** The byte boundary to pad chunks on. */ - public static final int PAD_BOUNDARY = 4; - - /** The number of bytes in every chunk that describes chunk type, header size, and chunk size. */ - public static final int METADATA_SIZE = 8; - - /** The offset in bytes, from the start of the chunk, where the chunk size can be found. */ - private static final int CHUNK_SIZE_OFFSET = 4; - - /** The parent to this chunk, if any. */ - @Nullable - private final Chunk parent; - - /** Size of the chunk header in bytes. */ - protected final int headerSize; - - /** headerSize + dataSize. The total size of this chunk. */ - protected final int chunkSize; - - /** Offset of this chunk from the start of the file. */ - protected final int offset; - - protected Chunk(ByteBuffer buffer, @Nullable Chunk parent) { - this.parent = parent; - offset = buffer.position() - 2; - headerSize = (buffer.getShort() & 0xFFFF); - chunkSize = buffer.getInt(); - } - - /** - * Finishes initialization of a chunk. This should be called immediately after the constructor. - * This is separate from the constructor so that the header of a chunk can be fully initialized - * before the payload of that chunk is initialized for chunks that require such behavior. - * - * @param buffer The buffer that the payload will be initialized from. - */ - protected void init(ByteBuffer buffer) {} - - /** - * Returns the parent to this chunk, if any. A parent is a chunk whose payload contains this - * chunk. If there's no parent, null is returned. - */ - @Nullable - public Chunk getParent() { - return parent; - } - - protected abstract Type getType(); - - /** Returns the size of this chunk's header. */ - public final int getHeaderSize() { - return headerSize; - } - - /** - * Returns the size of this chunk when it was first read from a buffer. A chunk's size can deviate - * from this value when its data is modified (e.g. adding an entry, changing a string). - * - *

A chunk's current size can be determined from the length of the byte array returned from - * {@link #toByteArray}. - */ - public final int getOriginalChunkSize() { - return chunkSize; - } - - /** - * Reposition the buffer after this chunk. Use this at the end of a Chunk constructor. - * @param buffer The buffer to be repositioned. - */ - private final void seekToEndOfChunk(ByteBuffer buffer) { - buffer.position(offset + chunkSize); - } - - /** - * Writes the type and header size. We don't know how big this chunk will be (it could be - * different since the last time we checked), so this needs to be passed in. - * - * @param output The buffer that will be written to. - * @param chunkSize The total size of this chunk in bytes, including the header. - */ - protected final void writeHeader(ByteBuffer output, int chunkSize) { - int start = output.position(); - output.putShort(getType().code()); - output.putShort((short) headerSize); - output.putInt(chunkSize); - writeHeader(output); - int headerBytes = output.position() - start; - Preconditions.checkState(headerBytes == getHeaderSize(), - "Written header is wrong size. Got %s, want %s", headerBytes, getHeaderSize()); - } - - /** - * Writes the remaining header (after the type, {@code headerSize}, and {@code chunkSize}). - * - * @param output The buffer that the header will be written to. - */ - protected void writeHeader(ByteBuffer output) {} - - /** - * Writes the chunk payload. The payload is data in a chunk which is not in - * the first {@code headerSize} bytes of the chunk. - * - * @param output The stream that the payload will be written to. - * @param header The already-written header. This can be modified to fix payload offsets. - * @param shrink True if this payload should be optimized for size. - * @throws IOException Thrown if {@code output} could not be written to (out of memory). - */ - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException {} - - /** - * Pads {@code output} until {@code currentLength} is on a 4-byte boundary. - * - * @param output The {@link DataOutput} that will be padded. - * @param currentLength The current length, in bytes, of {@code output} - * @return The new length of {@code output} - * @throws IOException Thrown if {@code output} could not be written to. - */ - protected int writePad(DataOutput output, int currentLength) throws IOException { - while (currentLength % PAD_BOUNDARY != 0) { - output.write(0); - ++currentLength; - } - return currentLength; - } - - @Override - public final byte[] toByteArray() throws IOException { - return toByteArray(false); - } - - /** - * Converts this chunk into an array of bytes representation. Normally you will not need to - * override this method unless your header changes based on the contents / size of the payload. - */ - @Override - public final byte[] toByteArray(boolean shrink) throws IOException { - ByteBuffer header = ByteBuffer.allocate(getHeaderSize()).order(ByteOrder.LITTLE_ENDIAN); - writeHeader(header, 0); // The chunk size isn't known yet. This will be filled in later. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { - writePayload(payload, header, shrink); + /** + * Returns the parent to this chunk, if any. A parent is a chunk whose payload contains this + * chunk. If there's no parent, null is returned. + */ + @Nullable + public Chunk getParent() { + return parent; } - byte[] payloadBytes = baos.toByteArray(); - int chunkSize = getHeaderSize() + payloadBytes.length; - header.putInt(CHUNK_SIZE_OFFSET, chunkSize); + protected abstract Type getType(); - // Combine results - ByteBuffer result = ByteBuffer.allocate(chunkSize).order(ByteOrder.LITTLE_ENDIAN); - result.put(header.array()); - result.put(payloadBytes); - return result.array(); - } + /** + * Returns the size of this chunk's header. + */ + public final int getHeaderSize() { + return headerSize; + } - /** - * Creates a new chunk whose contents start at {@code buffer}'s current position. - * - * @param buffer A buffer positioned at the start of a chunk. - * @return new chunk - */ - public static Chunk newInstance(ByteBuffer buffer) { - return newInstance(buffer, null); - } + /** + * Returns the size of this chunk when it was first read from a buffer. A chunk's size can deviate + * from this value when its data is modified (e.g. adding an entry, changing a string). + * + *

A chunk's current size can be determined from the length of the byte array returned from + * {@link #toByteArray}. + */ + public final int getOriginalChunkSize() { + return chunkSize; + } - /** - * Creates a new chunk whose contents start at {@code buffer}'s current position. - * - * @param buffer A buffer positioned at the start of a chunk. - * @param parent The parent to this chunk (or null if there's no parent). - * @return new chunk - */ - public static Chunk newInstance(ByteBuffer buffer, @Nullable Chunk parent) { - Chunk result; - Type type = Type.fromCode(buffer.getShort()); - switch (type) { - case STRING_POOL: - result = new StringPoolChunk(buffer, parent); - break; - case TABLE: - result = new ResourceTableChunk(buffer, parent); - break; - case XML: - result = new XmlChunk(buffer, parent); - break; - case XML_START_NAMESPACE: - result = new XmlNamespaceStartChunk(buffer, parent); - break; - case XML_END_NAMESPACE: - result = new XmlNamespaceEndChunk(buffer, parent); - break; - case XML_START_ELEMENT: - result = new XmlStartElementChunk(buffer, parent); - break; - case XML_END_ELEMENT: - result = new XmlEndElementChunk(buffer, parent); - break; - case XML_CDATA: - result = new XmlCdataChunk(buffer, parent); - break; - case XML_RESOURCE_MAP: - result = new XmlResourceMapChunk(buffer, parent); - break; - case TABLE_PACKAGE: - result = new PackageChunk(buffer, parent); - break; - case TABLE_TYPE: - result = new TypeChunk(buffer, parent); - break; - case TABLE_TYPE_SPEC: - result = new TypeSpecChunk(buffer, parent); - break; - case TABLE_LIBRARY: - result = new LibraryChunk(buffer, parent); - break; - default: - result = new UnknownChunk(buffer, parent); + /** + * Reposition the buffer after this chunk. Use this at the end of a Chunk constructor. + * + * @param buffer The buffer to be repositioned. + */ + private final void seekToEndOfChunk(ByteBuffer buffer) { + buffer.position(offset + chunkSize); + } + + /** + * Writes the type and header size. We don't know how big this chunk will be (it could be + * different since the last time we checked), so this needs to be passed in. + * + * @param output The buffer that will be written to. + * @param chunkSize The total size of this chunk in bytes, including the header. + */ + protected final void writeHeader(ByteBuffer output, int chunkSize) { + int start = output.position(); + output.putShort(getType().code()); + output.putShort((short) headerSize); + output.putInt(chunkSize); + writeHeader(output); + int headerBytes = output.position() - start; + Preconditions.checkState(headerBytes == getHeaderSize(), + "Written header is wrong size. Got %s, want %s", headerBytes, getHeaderSize()); + } + + /** + * Writes the remaining header (after the type, {@code headerSize}, and {@code chunkSize}). + * + * @param output The buffer that the header will be written to. + */ + protected void writeHeader(ByteBuffer output) { + } + + /** + * Writes the chunk payload. The payload is data in a chunk which is not in + * the first {@code headerSize} bytes of the chunk. + * + * @param output The stream that the payload will be written to. + * @param header The already-written header. This can be modified to fix payload offsets. + * @param shrink True if this payload should be optimized for size. + * @throws IOException Thrown if {@code output} could not be written to (out of memory). + */ + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + } + + /** + * Pads {@code output} until {@code currentLength} is on a 4-byte boundary. + * + * @param output The {@link DataOutput} that will be padded. + * @param currentLength The current length, in bytes, of {@code output} + * @return The new length of {@code output} + * @throws IOException Thrown if {@code output} could not be written to. + */ + protected int writePad(DataOutput output, int currentLength) throws IOException { + while (currentLength % PAD_BOUNDARY != 0) { + output.write(0); + ++currentLength; + } + return currentLength; + } + + @Override + public final byte[] toByteArray() throws IOException { + return toByteArray(false); + } + + /** + * Converts this chunk into an array of bytes representation. Normally you will not need to + * override this method unless your header changes based on the contents / size of the payload. + */ + @Override + public final byte[] toByteArray(boolean shrink) throws IOException { + ByteBuffer header = ByteBuffer.allocate(getHeaderSize()).order(ByteOrder.LITTLE_ENDIAN); + writeHeader(header, 0); // The chunk size isn't known yet. This will be filled in later. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { + writePayload(payload, header, shrink); + } + + byte[] payloadBytes = baos.toByteArray(); + int chunkSize = getHeaderSize() + payloadBytes.length; + header.putInt(CHUNK_SIZE_OFFSET, chunkSize); + + // Combine results + ByteBuffer result = ByteBuffer.allocate(chunkSize).order(ByteOrder.LITTLE_ENDIAN); + result.put(header.array()); + result.put(payloadBytes); + return result.array(); + } + + /** + * Creates a new chunk whose contents start at {@code buffer}'s current position. + * + * @param buffer A buffer positioned at the start of a chunk. + * @return new chunk + */ + public static Chunk newInstance(ByteBuffer buffer) { + return newInstance(buffer, null); + } + + /** + * Creates a new chunk whose contents start at {@code buffer}'s current position. + * + * @param buffer A buffer positioned at the start of a chunk. + * @param parent The parent to this chunk (or null if there's no parent). + * @return new chunk + */ + public static Chunk newInstance(ByteBuffer buffer, @Nullable Chunk parent) { + Chunk result; + Type type = Type.fromCode(buffer.getShort()); + switch (type) { + case STRING_POOL: + result = new StringPoolChunk(buffer, parent); + break; + case TABLE: + result = new ResourceTableChunk(buffer, parent); + break; + case XML: + result = new XmlChunk(buffer, parent); + break; + case XML_START_NAMESPACE: + result = new XmlNamespaceStartChunk(buffer, parent); + break; + case XML_END_NAMESPACE: + result = new XmlNamespaceEndChunk(buffer, parent); + break; + case XML_START_ELEMENT: + result = new XmlStartElementChunk(buffer, parent); + break; + case XML_END_ELEMENT: + result = new XmlEndElementChunk(buffer, parent); + break; + case XML_CDATA: + result = new XmlCdataChunk(buffer, parent); + break; + case XML_RESOURCE_MAP: + result = new XmlResourceMapChunk(buffer, parent); + break; + case TABLE_PACKAGE: + result = new PackageChunk(buffer, parent); + break; + case TABLE_TYPE: + result = new TypeChunk(buffer, parent); + break; + case TABLE_TYPE_SPEC: + result = new TypeSpecChunk(buffer, parent); + break; + case TABLE_LIBRARY: + result = new LibraryChunk(buffer, parent); + break; + default: + result = new UnknownChunk(buffer, parent); + } + result.init(buffer); + result.seekToEndOfChunk(buffer); + return result; } - result.init(buffer); - result.seekToEndOfChunk(buffer); - return result; - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java index 11ba3a41f..8a311a2b7 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java @@ -24,50 +24,52 @@ import java.nio.ByteBuffer; import java.util.LinkedHashMap; import java.util.Map; -/** Represents a chunk whose payload is a list of sub-chunks. */ +/** + * Represents a chunk whose payload is a list of sub-chunks. + */ public abstract class ChunkWithChunks extends Chunk { - private final Map chunks = new LinkedHashMap<>(); + private final Map chunks = new LinkedHashMap<>(); - protected ChunkWithChunks(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - chunks.clear(); - int start = this.offset + getHeaderSize(); - int offset = start; - int end = this.offset + getOriginalChunkSize(); - int position = buffer.position(); - buffer.position(start); - - while (offset < end) { - Chunk chunk = Chunk.newInstance(buffer, this); - chunks.put(offset, chunk); - offset += chunk.getOriginalChunkSize(); + protected ChunkWithChunks(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); } - buffer.position(position); - } + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + chunks.clear(); + int start = this.offset + getHeaderSize(); + int offset = start; + int end = this.offset + getOriginalChunkSize(); + int position = buffer.position(); + buffer.position(start); - /** - * Retrieves the @{code chunks} contained in this chunk. - * - * @return map of buffer offset -> chunk contained in this chunk. - */ - public final Map getChunks() { - return chunks; - } + while (offset < end) { + Chunk chunk = Chunk.newInstance(buffer, this); + chunks.put(offset, chunk); + offset += chunk.getOriginalChunkSize(); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - for (Chunk chunk : getChunks().values()) { - byte[] chunkBytes = chunk.toByteArray(shrink); - output.write(chunkBytes); - writePad(output, chunkBytes.length); + buffer.position(position); + } + + /** + * Retrieves the @{code chunks} contained in this chunk. + * + * @return map of buffer offset -> chunk contained in this chunk. + */ + public final Map getChunks() { + return chunks; + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + for (Chunk chunk : getChunks().values()) { + byte[] chunkBytes = chunk.toByteArray(shrink); + output.write(chunkBytes); + writePad(output, chunkBytes.length); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java index 38236de8b..4a31546f4 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -32,105 +33,121 @@ import java.util.Objects; */ public final class LibraryChunk extends Chunk { - /** The number of resources of this type at creation time. */ - private final int entryCount; + /** + * The number of resources of this type at creation time. + */ + private final int entryCount; - /** The libraries used in this chunk (package id + name). */ - private final List entries = new ArrayList<>(); + /** + * The libraries used in this chunk (package id + name). + */ + private final List entries = new ArrayList<>(); - protected LibraryChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - entryCount = buffer.getInt(); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - entries.addAll(enumerateEntries(buffer)); - } - - private List enumerateEntries(ByteBuffer buffer) { - List result = new ArrayList<>(entryCount); - int offset = this.offset + getHeaderSize(); - int endOffset = offset + Entry.SIZE * entryCount; - - while (offset < endOffset) { - result.add(Entry.create(buffer, offset)); - offset += Entry.SIZE; - } - return result; - } - - @Override - protected Type getType() { - return Type.TABLE_LIBRARY; - } - - @Override - protected void writeHeader(ByteBuffer output) { - super.writeHeader(output); - output.putInt(entries.size()); - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - for (Entry entry : entries) { - output.write(entry.toByteArray(shrink)); - } - } - - /** A shared library package-id to package name entry. */ - protected static class Entry implements SerializableResource { - - /** Library entries only contain a package ID (4 bytes) and a package name. */ - private static final int SIZE = 4 + PackageUtils.PACKAGE_NAME_SIZE; - - private final int packageId; - private final String packageName; - - static Entry create(ByteBuffer buffer, int offset) { - int packageId = buffer.getInt(offset); - String packageName = PackageUtils.readPackageName(buffer, offset + 4); - return new Entry(packageId, packageName); - } - - private Entry(int packageId, String packageName) { - this.packageId = packageId; - this.packageName = packageName; - } - - /** The id assigned to the shared library at build time. */ - public int packageId() { return packageId; } - - /** The package name of the shared library. */ - public String packageName() { return packageName; } - - @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); + protected LibraryChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + entryCount = buffer.getInt(); } @Override - public byte[] toByteArray(boolean shrink) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(packageId()); - PackageUtils.writePackageName(buffer, packageName()); - return buffer.array(); + protected void init(ByteBuffer buffer) { + super.init(buffer); + entries.addAll(enumerateEntries(buffer)); + } + + private List enumerateEntries(ByteBuffer buffer) { + List result = new ArrayList<>(entryCount); + int offset = this.offset + getHeaderSize(); + int endOffset = offset + Entry.SIZE * entryCount; + + while (offset < endOffset) { + result.add(Entry.create(buffer, offset)); + offset += Entry.SIZE; + } + return result; } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Entry entry = (Entry)o; - return packageId == entry.packageId && - Objects.equals(packageName, entry.packageName); + protected Type getType() { + return Type.TABLE_LIBRARY; } @Override - public int hashCode() { - return Objects.hash(packageId, packageName); + protected void writeHeader(ByteBuffer output) { + super.writeHeader(output); + output.putInt(entries.size()); + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + for (Entry entry : entries) { + output.write(entry.toByteArray(shrink)); + } + } + + /** + * A shared library package-id to package name entry. + */ + protected static class Entry implements SerializableResource { + + /** + * Library entries only contain a package ID (4 bytes) and a package name. + */ + private static final int SIZE = 4 + PackageUtils.PACKAGE_NAME_SIZE; + + private final int packageId; + private final String packageName; + + static Entry create(ByteBuffer buffer, int offset) { + int packageId = buffer.getInt(offset); + String packageName = PackageUtils.readPackageName(buffer, offset + 4); + return new Entry(packageId, packageName); + } + + private Entry(int packageId, String packageName) { + this.packageId = packageId; + this.packageName = packageName; + } + + /** + * The id assigned to the shared library at build time. + */ + public int packageId() { + return packageId; + } + + /** + * The package name of the shared library. + */ + public String packageName() { + return packageName; + } + + @Override + public byte[] toByteArray() throws IOException { + return toByteArray(false); + } + + @Override + public byte[] toByteArray(boolean shrink) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(packageId()); + PackageUtils.writePackageName(buffer, packageName()); + return buffer.array(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry entry = (Entry) o; + return packageId == entry.packageId && + Objects.equals(packageName, entry.packageName); + } + + @Override + public int hashCode() { + return Objects.hash(packageId, packageName); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java index cc8c2e32f..9ede60266 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -29,211 +29,251 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -/** A package chunk is a collection of resource data types within a package. */ +/** + * A package chunk is a collection of resource data types within a package. + */ public final class PackageChunk extends ChunkWithChunks { - /** Offset in bytes, from the start of the chunk, where {@code typeStringsOffset} can be found. */ - private static final int TYPE_OFFSET_OFFSET = 268; + /** + * Offset in bytes, from the start of the chunk, where {@code typeStringsOffset} can be found. + */ + private static final int TYPE_OFFSET_OFFSET = 268; - /** Offset in bytes, from the start of the chunk, where {@code keyStringsOffset} can be found. */ - private static final int KEY_OFFSET_OFFSET = 276; + /** + * Offset in bytes, from the start of the chunk, where {@code keyStringsOffset} can be found. + */ + private static final int KEY_OFFSET_OFFSET = 276; - /** The package id if this is a base package, or 0 if not a base package. */ - private int id; + /** + * The package id if this is a base package, or 0 if not a base package. + */ + private int id; - /** The name of the package. */ - private String packageName; + /** + * The name of the package. + */ + private String packageName; - /** The offset (from {@code offset}) in the original buffer where type strings start. */ - private final int typeStringsOffset; + /** + * The offset (from {@code offset}) in the original buffer where type strings start. + */ + private final int typeStringsOffset; - /** The index into the type string pool of the last public type. */ - private final int lastPublicType; + /** + * The index into the type string pool of the last public type. + */ + private final int lastPublicType; - /** An offset to the string pool that contains the key strings for this package. */ - private final int keyStringsOffset; + /** + * An offset to the string pool that contains the key strings for this package. + */ + private final int keyStringsOffset; - /** The index into the key string pool of the last public key. */ - private final int lastPublicKey; + /** + * The index into the key string pool of the last public key. + */ + private final int lastPublicKey; - /** An offset to the type ID(s). This is undocumented in the original code. */ - private final int typeIdOffset; + /** + * An offset to the type ID(s). This is undocumented in the original code. + */ + private final int typeIdOffset; - /** Contains a mapping of a type index to its {@link TypeSpecChunk}. */ - private final Map typeSpecs = new HashMap<>(); + /** + * Contains a mapping of a type index to its {@link TypeSpecChunk}. + */ + private final Map typeSpecs = new HashMap<>(); - /** Contains a mapping of a type index to all of the {@link TypeChunk} with that index. */ - private final Multimap types = ArrayListMultimap.create(); + /** + * Contains a mapping of a type index to all of the {@link TypeChunk} with that index. + */ + private final Multimap types = ArrayListMultimap.create(); - /** May contain a library chunk for mapping dynamic references to resolved references. */ - private Optional libraryChunk = Optional.empty(); + /** + * May contain a library chunk for mapping dynamic references to resolved references. + */ + private Optional libraryChunk = Optional.empty(); - protected PackageChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - id = buffer.getInt(); - packageName = PackageUtils.readPackageName(buffer, buffer.position()); - typeStringsOffset = buffer.getInt(); - lastPublicType = buffer.getInt(); - keyStringsOffset = buffer.getInt(); - lastPublicKey = buffer.getInt(); - typeIdOffset = buffer.getInt(); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - for (Chunk chunk : getChunks().values()) { - if (chunk instanceof TypeChunk) { - TypeChunk typeChunk = (TypeChunk) chunk; - types.put(typeChunk.getId(), typeChunk); - } else if (chunk instanceof TypeSpecChunk) { - TypeSpecChunk typeSpecChunk = (TypeSpecChunk) chunk; - typeSpecs.put(typeSpecChunk.getId(), typeSpecChunk); - } else if (chunk instanceof LibraryChunk) { - if (libraryChunk.isPresent()) { - throw new IllegalStateException( - "Multiple library chunks present in package chunk."); - } - libraryChunk = Optional.of((LibraryChunk) chunk); - } else if (!(chunk instanceof StringPoolChunk)) { - throw new IllegalStateException( - String.format("PackageChunk contains an unexpected chunk: %s", chunk.getClass())); - } + protected PackageChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + id = buffer.getInt(); + packageName = PackageUtils.readPackageName(buffer, buffer.position()); + typeStringsOffset = buffer.getInt(); + lastPublicType = buffer.getInt(); + keyStringsOffset = buffer.getInt(); + lastPublicKey = buffer.getInt(); + typeIdOffset = buffer.getInt(); } - } - /** Returns the package id if this is a base package, or 0 if not a base package. */ - public int getId() { - return id; - } - - /** Sets the package id */ - public void setId(int id) { - this.id = id; - } - - /** - * Returns the string pool that contains the names of the resources in this package. - */ - public StringPoolChunk getKeyStringPool() { - Chunk chunk = Preconditions.checkNotNull(getChunks().get(keyStringsOffset + offset)); - Preconditions.checkState(chunk instanceof StringPoolChunk, "Key string pool not found."); - return (StringPoolChunk) chunk; - } - - /** - * Get the type string for a specific id, e.g., (e.g. string, attr, id). - * - * @param id The id to get the type for. - * @return The type string. - */ - public String getTypeString(int id) { - StringPoolChunk typePool = getTypeStringPool(); - Preconditions.checkNotNull(typePool, "Package has no type pool."); - Preconditions.checkState(typePool.getStyleCount() >= id, "No type for id: " + id); - return typePool.getString(id - 1); // - 1 here to convert to 0-based index - } - - /** - * Returns the string pool that contains the type strings for this package, such as "layout", - * "string", "color". - */ - public StringPoolChunk getTypeStringPool() { - Chunk chunk = Preconditions.checkNotNull(getChunks().get(typeStringsOffset + offset)); - Preconditions.checkState(chunk instanceof StringPoolChunk, "Type string pool not found."); - return (StringPoolChunk) chunk; - } - - /** Returns all {@link TypeChunk} in this package. */ - public Collection getTypeChunks() { - return types.values(); - } - - /** - * For a given type id, returns the {@link TypeChunk} objects that match that id. The type id is - * the 1-based index of the type in the type string pool (returned by {@link #getTypeStringPool}). - * - * @param id The 1-based type id to return {@link TypeChunk} objects for. - * @return The matching {@link TypeChunk} objects, or an empty collection if there are none. - */ - public Collection getTypeChunks(int id) { - return types.get(id); - } - - /** - * For a given type, returns the {@link TypeChunk} objects that match that type - * (e.g. "attr", "id", "string", ...). - * - * @param type The type to return {@link TypeChunk} objects for. - * @return The matching {@link TypeChunk} objects, or an empty collection if there are none. - */ - public Collection getTypeChunks(String type) { - StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool()); - return getTypeChunks(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based - } - - /** Returns all {@link TypeSpecChunk} in this package. */ - public Collection getTypeSpecChunks() { - return typeSpecs.values(); - } - - /** For a given (1-based) type id, returns the {@link TypeSpecChunk} matching it. */ - public TypeSpecChunk getTypeSpecChunk(int id) { - return Preconditions.checkNotNull(typeSpecs.get(id)); - } - - /** - * For a given {@code type}, returns the {@link TypeSpecChunk} that matches it - * (e.g. "attr", "id", "string", ...). - */ - public TypeSpecChunk getTypeSpecChunk(String type) { - StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool()); - return getTypeSpecChunk(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based - } - - /** Returns the name of this package. */ - public String getPackageName() { - return packageName; - } - - /** Set the package name */ - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - @Override - protected Type getType() { - return Type.TABLE_PACKAGE; - } - - @Override - protected void writeHeader(ByteBuffer output) { - output.putInt(id); - PackageUtils.writePackageName(output, packageName); - output.putInt(0); // typeStringsOffset. This value can't be computed here. - output.putInt(lastPublicType); - output.putInt(0); // keyStringsOffset. This value can't be computed here. - output.putInt(lastPublicKey); - output.putInt(typeIdOffset); - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - int typeOffset = typeStringsOffset; - int keyOffset = keyStringsOffset; - int payloadOffset = 0; - for (Chunk chunk : getChunks().values()) { - if (chunk == getTypeStringPool()) { - typeOffset = payloadOffset + getHeaderSize(); - } else if (chunk == getKeyStringPool()) { - keyOffset = payloadOffset + getHeaderSize(); - } - byte[] chunkBytes = chunk.toByteArray(shrink); - output.write(chunkBytes); - payloadOffset = writePad(output, chunkBytes.length); + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + for (Chunk chunk : getChunks().values()) { + if (chunk instanceof TypeChunk) { + TypeChunk typeChunk = (TypeChunk) chunk; + types.put(typeChunk.getId(), typeChunk); + } else if (chunk instanceof TypeSpecChunk) { + TypeSpecChunk typeSpecChunk = (TypeSpecChunk) chunk; + typeSpecs.put(typeSpecChunk.getId(), typeSpecChunk); + } else if (chunk instanceof LibraryChunk) { + if (libraryChunk.isPresent()) { + throw new IllegalStateException( + "Multiple library chunks present in package chunk."); + } + libraryChunk = Optional.of((LibraryChunk) chunk); + } else if (!(chunk instanceof StringPoolChunk)) { + throw new IllegalStateException( + String.format("PackageChunk contains an unexpected chunk: %s", chunk.getClass())); + } + } + } + + /** + * Returns the package id if this is a base package, or 0 if not a base package. + */ + public int getId() { + return id; + } + + /** + * Sets the package id + */ + public void setId(int id) { + this.id = id; + } + + /** + * Returns the string pool that contains the names of the resources in this package. + */ + public StringPoolChunk getKeyStringPool() { + Chunk chunk = Preconditions.checkNotNull(getChunks().get(keyStringsOffset + offset)); + Preconditions.checkState(chunk instanceof StringPoolChunk, "Key string pool not found."); + return (StringPoolChunk) chunk; + } + + /** + * Get the type string for a specific id, e.g., (e.g. string, attr, id). + * + * @param id The id to get the type for. + * @return The type string. + */ + public String getTypeString(int id) { + StringPoolChunk typePool = getTypeStringPool(); + Preconditions.checkNotNull(typePool, "Package has no type pool."); + Preconditions.checkState(typePool.getStyleCount() >= id, "No type for id: " + id); + return typePool.getString(id - 1); // - 1 here to convert to 0-based index + } + + /** + * Returns the string pool that contains the type strings for this package, such as "layout", + * "string", "color". + */ + public StringPoolChunk getTypeStringPool() { + Chunk chunk = Preconditions.checkNotNull(getChunks().get(typeStringsOffset + offset)); + Preconditions.checkState(chunk instanceof StringPoolChunk, "Type string pool not found."); + return (StringPoolChunk) chunk; + } + + /** + * Returns all {@link TypeChunk} in this package. + */ + public Collection getTypeChunks() { + return types.values(); + } + + /** + * For a given type id, returns the {@link TypeChunk} objects that match that id. The type id is + * the 1-based index of the type in the type string pool (returned by {@link #getTypeStringPool}). + * + * @param id The 1-based type id to return {@link TypeChunk} objects for. + * @return The matching {@link TypeChunk} objects, or an empty collection if there are none. + */ + public Collection getTypeChunks(int id) { + return types.get(id); + } + + /** + * For a given type, returns the {@link TypeChunk} objects that match that type + * (e.g. "attr", "id", "string", ...). + * + * @param type The type to return {@link TypeChunk} objects for. + * @return The matching {@link TypeChunk} objects, or an empty collection if there are none. + */ + public Collection getTypeChunks(String type) { + StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool()); + return getTypeChunks(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based + } + + /** + * Returns all {@link TypeSpecChunk} in this package. + */ + public Collection getTypeSpecChunks() { + return typeSpecs.values(); + } + + /** + * For a given (1-based) type id, returns the {@link TypeSpecChunk} matching it. + */ + public TypeSpecChunk getTypeSpecChunk(int id) { + return Preconditions.checkNotNull(typeSpecs.get(id)); + } + + /** + * For a given {@code type}, returns the {@link TypeSpecChunk} that matches it + * (e.g. "attr", "id", "string", ...). + */ + public TypeSpecChunk getTypeSpecChunk(String type) { + StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool()); + return getTypeSpecChunk(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based + } + + /** + * Returns the name of this package. + */ + public String getPackageName() { + return packageName; + } + + /** + * Set the package name + */ + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + @Override + protected Type getType() { + return Type.TABLE_PACKAGE; + } + + @Override + protected void writeHeader(ByteBuffer output) { + output.putInt(id); + PackageUtils.writePackageName(output, packageName); + output.putInt(0); // typeStringsOffset. This value can't be computed here. + output.putInt(lastPublicType); + output.putInt(0); // keyStringsOffset. This value can't be computed here. + output.putInt(lastPublicKey); + output.putInt(typeIdOffset); + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + int typeOffset = typeStringsOffset; + int keyOffset = keyStringsOffset; + int payloadOffset = 0; + for (Chunk chunk : getChunks().values()) { + if (chunk == getTypeStringPool()) { + typeOffset = payloadOffset + getHeaderSize(); + } else if (chunk == getKeyStringPool()) { + keyOffset = payloadOffset + getHeaderSize(); + } + byte[] chunkBytes = chunk.toByteArray(shrink); + output.write(chunkBytes); + payloadOffset = writePad(output, chunkBytes.length); + } + header.putInt(TYPE_OFFSET_OFFSET, typeOffset); + header.putInt(KEY_OFFSET_OFFSET, keyOffset); } - header.putInt(TYPE_OFFSET_OFFSET, typeOffset); - header.putInt(KEY_OFFSET_OFFSET, keyOffset); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java index 5077ca6f0..6c5215762 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java @@ -20,48 +20,53 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -/** Provides utility methods for package names. */ +/** + * Provides utility methods for package names. + */ public final class PackageUtils { - public static final int PACKAGE_NAME_SIZE = 256; + public static final int PACKAGE_NAME_SIZE = 256; - private PackageUtils() {} // Prevent instantiation + private PackageUtils() { + } // Prevent instantiation - /** - * Reads the package name from the buffer and repositions the buffer to point directly after - * the package name. - * @param buffer The buffer containing the package name. - * @param offset The offset in the buffer to read from. - * @return The package name. - */ - public static String readPackageName(ByteBuffer buffer, int offset) { - byte[] data = buffer.array(); - int length = 0; - // Look for the null terminator for the string instead of using the entire buffer. - // It's UTF-16 so check 2 bytes at a time to see if its double 0. - for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) { - if (data[i] == 0 && data[i + 1] == 0) { - length = i - offset; - break; - } + /** + * Reads the package name from the buffer and repositions the buffer to point directly after + * the package name. + * + * @param buffer The buffer containing the package name. + * @param offset The offset in the buffer to read from. + * @return The package name. + */ + public static String readPackageName(ByteBuffer buffer, int offset) { + byte[] data = buffer.array(); + int length = 0; + // Look for the null terminator for the string instead of using the entire buffer. + // It's UTF-16 so check 2 bytes at a time to see if its double 0. + for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) { + if (data[i] == 0 && data[i + 1] == 0) { + length = i - offset; + break; + } + } + Charset utf16 = StandardCharsets.UTF_16LE; + String str = new String(data, offset, length, utf16); + buffer.position(offset + PACKAGE_NAME_SIZE); + return str; } - Charset utf16 = StandardCharsets.UTF_16LE; - String str = new String(data, offset, length, utf16); - buffer.position(offset + PACKAGE_NAME_SIZE); - return str; - } - /** - * Writes the provided package name to the buffer in UTF-16. - * @param buffer The buffer that will be written to. - * @param packageName The package name that will be written to the buffer. - */ - public static void writePackageName(ByteBuffer buffer, String packageName) { - byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE); - buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE)); - if (nameBytes.length < PACKAGE_NAME_SIZE) { - // pad out the remaining space with an empty array. - buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]); + /** + * Writes the provided package name to the buffer in UTF-16. + * + * @param buffer The buffer that will be written to. + * @param packageName The package name that will be written to the buffer. + */ + public static void writePackageName(ByteBuffer buffer, String packageName) { + byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE); + buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE)); + if (nameBytes.length < PACKAGE_NAME_SIZE) { + // pad out the remaining space with an empty array. + buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java index 5891f0567..e7d3895ef 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java @@ -26,255 +26,276 @@ import java.util.Map.Entry; /** * Calculates extra information about an {@link ResourceEntry}, such as the total * APK size the entry is responsible for. - * + *

* This class is not thread-safe. */ public class ResourceEntryStatsCollector { - /** The size in bytes of an offset in a chunk. */ - private static final int OFFSET_SIZE = 4; + /** + * The size in bytes of an offset in a chunk. + */ + private static final int OFFSET_SIZE = 4; - /** The size in bytes of overhead for styles, if present, in {@link StringPoolChunk}. */ - private static final int STYLE_OVERHEAD = 8; + /** + * The size in bytes of overhead for styles, if present, in {@link StringPoolChunk}. + */ + private static final int STYLE_OVERHEAD = 8; - /** - * The number of bytes, in addition to the header, that the {@link PackageChunk} has in overhead - * excluding the chunks it contains. - */ - private static final int PACKAGE_CHUNK_OVERHEAD = 8; + /** + * The number of bytes, in addition to the header, that the {@link PackageChunk} has in overhead + * excluding the chunks it contains. + */ + private static final int PACKAGE_CHUNK_OVERHEAD = 8; - private final Map stats = new HashMap<>(); + private final Map stats = new HashMap<>(); - private final ArscBlamer blamer; + private final ArscBlamer blamer; - private final ResourceTableChunk resourceTable; + private final ResourceTableChunk resourceTable; - /** - * Creates a new {@link ResourceEntryStatsCollector}. - * - * @param blamer The blamer that maps resource entries to what they use. - * @param resourceTable The resource table that {@code blamer} is blamed on. - */ - public ResourceEntryStatsCollector(ArscBlamer blamer, ResourceTableChunk resourceTable) { - this.resourceTable = resourceTable; - this.blamer = blamer; - } - - public void compute() throws IOException { - Preconditions.checkState(stats.isEmpty(), "Must only call #compute once."); - blamer.blame(); - computeStringPoolSizes(); - computePackageSizes(); - } - - /** Returns entries for which there are computed stats. Must first call {@link #compute}. */ - public Map getStats() { - Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first."); - return Collections.unmodifiableMap(stats); - } - - /** Returns computed stats for a given entry. Must first call {@link #compute}. */ - public ResourceStatistics getStats(ResourceEntry entry) { - Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first."); - return stats.containsKey(entry) ? stats.get(entry) : ResourceStatistics.EMPTY; - } - - private void computeStringPoolSizes() throws IOException { - computePoolSizes(resourceTable.getStringPool(), blamer.getStringToBlamedResources()); - } - - private void computePackageSizes() throws IOException { - computeTypePoolSizes(); - computeKeyPoolSizes(); - computeTypeSpecSizes(); - computeTypeChunkSizes(); - computePackageChunkSizes(); - } - - private void computeTypePoolSizes() throws IOException { - for (Entry[]> entry - : blamer.getTypeToBlamedResources().entrySet()) { - computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue()); - } - } - - private void computeKeyPoolSizes() throws IOException { - for (Entry[]> entry - : blamer.getKeyToBlamedResources().entrySet()) { - computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue()); - } - } - - private void computeTypeSpecSizes() { - for (Entry[]> entry - : blamer.getTypeToBlamedResources().entrySet()) { - computeTypeSpecSizes(entry.getKey(), entry.getValue()); - } - } - - private void computeTypeChunkSizes() { - for (Entry> entry - : blamer.getTypeEntryToBlamedResources().asMap().entrySet()) { - TypeChunk.Entry chunkEntry = entry.getKey(); - TypeChunk typeChunk = chunkEntry.parent(); - int size = chunkEntry.size() + OFFSET_SIZE; - int count = typeChunk.getEntries().size(); - int nullEntries = typeChunk.getTotalEntryCount() - typeChunk.getEntries().size(); - int overhead = typeChunk.getHeaderSize() + nullEntries * OFFSET_SIZE; - addSizes(entry.getValue(), overhead, size, count); - } - } - - private void computePackageChunkSizes() { - for (Entry> entry - : blamer.getPackageToBlamedResources().asMap().entrySet()) { - int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD; - addSizes(entry.getValue(), overhead, 0, 1); - } - } - - private void computePoolSizes(StringPoolChunk stringPool, - List[] usages) throws IOException { - int overhead = stringPool.getHeaderSize(); - if (stringPool.getStyleCount() > 0) { - overhead += STYLE_OVERHEAD; + /** + * Creates a new {@link ResourceEntryStatsCollector}. + * + * @param blamer The blamer that maps resource entries to what they use. + * @param resourceTable The resource table that {@code blamer} is blamed on. + */ + public ResourceEntryStatsCollector(ArscBlamer blamer, ResourceTableChunk resourceTable) { + this.resourceTable = resourceTable; + this.blamer = blamer; } - // We have to iterate over the indices of the string pool, because it is possible that there are - // indices which have *no* associated resource entry (i.e. references from XML files without an - // entry in R). - int count = 0; - for (int i = 0; i < usages.length; ++i) { - if (usages[i].isEmpty()) { - overhead += computeStringAndStyleSize(stringPool, i); - } else { - ++count; - } + public void compute() throws IOException { + Preconditions.checkState(stats.isEmpty(), "Must only call #compute once."); + blamer.blame(); + computeStringPoolSizes(); + computePackageSizes(); } - // Now that we know the number of actual entries, we can compute the size. - for (int i = 0; i < usages.length; ++i) { - if (usages[i].isEmpty()) { - continue; - } - int size = computeStringAndStyleSize(stringPool, i); - addSizes(usages[i], overhead, size, count); - } - } - - private void computeTypeSpecSizes(PackageChunk packageChunk, - List[] usages) { - for (int i = 0; i < usages.length; ++i) { - // The 1 here is to convert back to a 1-based index. - TypeSpecChunk typeSpec = packageChunk.getTypeSpecChunk(i + 1); - // TypeSpecChunk entries share everything equally. - addSizes(usages[i], typeSpec.getOriginalChunkSize(), 0, 1); - } - } - - /** - * Given an {@code index} into a {@code stringPool}, return string's total size in bytes plus its - * style, if any. - * - * @param stringPool The string pool containing the {@code index}. - * @param index The (0-based) index of the string and (optional) style. - * @throws IOException Thrown if the style's length could not be computed. - */ - private int computeStringAndStyleSize(StringPoolChunk stringPool, int index) - throws IOException { - return computeStringSize(stringPool, index) + computeStyleSize(stringPool, index); - } - - /** Given an {@code index} into a {@code stringPool}, return string's total size in bytes. */ - private int computeStringSize(StringPoolChunk stringPool, int index) { - String string = stringPool.getString(index); - int result = BinaryResourceString.encodeString(string, stringPool.getStringType()).length; - result += OFFSET_SIZE; - return result; - } - - /** - * Given an {@code index} into a {@code stringPool}, return style's total size in bytes or 0 if - * there's no style at that index. - * - * @throws IOException Thrown if the style's length could not be computed. - */ - private int computeStyleSize(StringPoolChunk stringPool, int index) throws IOException { - if (index >= stringPool.getStyleCount()) { // No style at index - return 0; - } - return stringPool.getStyle(index).toByteArray().length + OFFSET_SIZE; - } - - /** - * Adds to the {@link #stats} of {@code entries} that reference a value in a chunk the bytes it's - * responsible for. This should only be called once per chunk-value pair. - * - * @param entries The resource entries referencing a single value in a chunk. - * @param overhead The number of bytes of overhead of a chunk. Typically the header size. - * @param size The size in bytes of a value in a chunk that {@code entries} reference. - * @param count The total number of values in the chunk. - */ - private void addSizes(Collection entries, int overhead, int size, int count) { - int usageCount = entries.size(); - for (ResourceEntry resourceEntry : entries) { - // TODO(acornwall): Replace with Java 8's #getOrDefault when possible. - if (!stats.containsKey(resourceEntry)) { - stats.put(resourceEntry, new ResourceStatistics()); - } - ResourceStatistics resourceStats = stats.get(resourceEntry); - if (usageCount == 1) { - resourceStats.addPrivateSize(size); - } else { - resourceStats.addSharedSize(size); - } - // Special case: If the chunk only has one relevant value, removing this entry will remove the - // entire chunk. - if (usageCount == 1 && count == 1) { - resourceStats.addPrivateSize(overhead); - } - resourceStats.addProportionalSize(size, usageCount); - resourceStats.addProportionalSize(overhead, usageCount * count); - } - } - - /** Stats for an individual {@link ResourceEntry}. */ - public static class ResourceStatistics { - - /** The empty, immutable instance of ResourceStatistics which contains 0 for all values. */ - public static final ResourceStatistics EMPTY = new ResourceStatistics(); - - private int privateSize = 0; - private int sharedSize = 0; - private double proportionalSize = 0; - - private ResourceStatistics() {} - - /** The number of bytes that would be freed if this resource was removed. */ - public int getPrivateSize() { - return privateSize; + /** + * Returns entries for which there are computed stats. Must first call {@link #compute}. + */ + public Map getStats() { + Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first."); + return Collections.unmodifiableMap(stats); } - /** The number of bytes taken up by this resource that are also shared with other resources. */ - public int getSharedSize() { - return sharedSize; + /** + * Returns computed stats for a given entry. Must first call {@link #compute}. + */ + public ResourceStatistics getStats(ResourceEntry entry) { + Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first."); + return stats.containsKey(entry) ? stats.get(entry) : ResourceStatistics.EMPTY; } - /** The total size this resource is responsible for. */ - public double getProportionalSize() { - return proportionalSize; + private void computeStringPoolSizes() throws IOException { + computePoolSizes(resourceTable.getStringPool(), blamer.getStringToBlamedResources()); } - private void addPrivateSize(int privateSize) { - this.privateSize += privateSize; + private void computePackageSizes() throws IOException { + computeTypePoolSizes(); + computeKeyPoolSizes(); + computeTypeSpecSizes(); + computeTypeChunkSizes(); + computePackageChunkSizes(); } - private void addSharedSize(int sharedSize) { - this.sharedSize += sharedSize; + private void computeTypePoolSizes() throws IOException { + for (Entry[]> entry + : blamer.getTypeToBlamedResources().entrySet()) { + computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue()); + } } - private void addProportionalSize(int numerator, int denominator) { - this.proportionalSize += 1.0 * numerator / denominator; + private void computeKeyPoolSizes() throws IOException { + for (Entry[]> entry + : blamer.getKeyToBlamedResources().entrySet()) { + computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue()); + } + } + + private void computeTypeSpecSizes() { + for (Entry[]> entry + : blamer.getTypeToBlamedResources().entrySet()) { + computeTypeSpecSizes(entry.getKey(), entry.getValue()); + } + } + + private void computeTypeChunkSizes() { + for (Entry> entry + : blamer.getTypeEntryToBlamedResources().asMap().entrySet()) { + TypeChunk.Entry chunkEntry = entry.getKey(); + TypeChunk typeChunk = chunkEntry.parent(); + int size = chunkEntry.size() + OFFSET_SIZE; + int count = typeChunk.getEntries().size(); + int nullEntries = typeChunk.getTotalEntryCount() - typeChunk.getEntries().size(); + int overhead = typeChunk.getHeaderSize() + nullEntries * OFFSET_SIZE; + addSizes(entry.getValue(), overhead, size, count); + } + } + + private void computePackageChunkSizes() { + for (Entry> entry + : blamer.getPackageToBlamedResources().asMap().entrySet()) { + int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD; + addSizes(entry.getValue(), overhead, 0, 1); + } + } + + private void computePoolSizes(StringPoolChunk stringPool, + List[] usages) throws IOException { + int overhead = stringPool.getHeaderSize(); + if (stringPool.getStyleCount() > 0) { + overhead += STYLE_OVERHEAD; + } + + // We have to iterate over the indices of the string pool, because it is possible that there are + // indices which have *no* associated resource entry (i.e. references from XML files without an + // entry in R). + int count = 0; + for (int i = 0; i < usages.length; ++i) { + if (usages[i].isEmpty()) { + overhead += computeStringAndStyleSize(stringPool, i); + } else { + ++count; + } + } + + // Now that we know the number of actual entries, we can compute the size. + for (int i = 0; i < usages.length; ++i) { + if (usages[i].isEmpty()) { + continue; + } + int size = computeStringAndStyleSize(stringPool, i); + addSizes(usages[i], overhead, size, count); + } + } + + private void computeTypeSpecSizes(PackageChunk packageChunk, + List[] usages) { + for (int i = 0; i < usages.length; ++i) { + // The 1 here is to convert back to a 1-based index. + TypeSpecChunk typeSpec = packageChunk.getTypeSpecChunk(i + 1); + // TypeSpecChunk entries share everything equally. + addSizes(usages[i], typeSpec.getOriginalChunkSize(), 0, 1); + } + } + + /** + * Given an {@code index} into a {@code stringPool}, return string's total size in bytes plus its + * style, if any. + * + * @param stringPool The string pool containing the {@code index}. + * @param index The (0-based) index of the string and (optional) style. + * @throws IOException Thrown if the style's length could not be computed. + */ + private int computeStringAndStyleSize(StringPoolChunk stringPool, int index) + throws IOException { + return computeStringSize(stringPool, index) + computeStyleSize(stringPool, index); + } + + /** + * Given an {@code index} into a {@code stringPool}, return string's total size in bytes. + */ + private int computeStringSize(StringPoolChunk stringPool, int index) { + String string = stringPool.getString(index); + int result = BinaryResourceString.encodeString(string, stringPool.getStringType()).length; + result += OFFSET_SIZE; + return result; + } + + /** + * Given an {@code index} into a {@code stringPool}, return style's total size in bytes or 0 if + * there's no style at that index. + * + * @throws IOException Thrown if the style's length could not be computed. + */ + private int computeStyleSize(StringPoolChunk stringPool, int index) throws IOException { + if (index >= stringPool.getStyleCount()) { // No style at index + return 0; + } + return stringPool.getStyle(index).toByteArray().length + OFFSET_SIZE; + } + + /** + * Adds to the {@link #stats} of {@code entries} that reference a value in a chunk the bytes it's + * responsible for. This should only be called once per chunk-value pair. + * + * @param entries The resource entries referencing a single value in a chunk. + * @param overhead The number of bytes of overhead of a chunk. Typically the header size. + * @param size The size in bytes of a value in a chunk that {@code entries} reference. + * @param count The total number of values in the chunk. + */ + private void addSizes(Collection entries, int overhead, int size, int count) { + int usageCount = entries.size(); + for (ResourceEntry resourceEntry : entries) { + // TODO(acornwall): Replace with Java 8's #getOrDefault when possible. + if (!stats.containsKey(resourceEntry)) { + stats.put(resourceEntry, new ResourceStatistics()); + } + ResourceStatistics resourceStats = stats.get(resourceEntry); + if (usageCount == 1) { + resourceStats.addPrivateSize(size); + } else { + resourceStats.addSharedSize(size); + } + // Special case: If the chunk only has one relevant value, removing this entry will remove the + // entire chunk. + if (usageCount == 1 && count == 1) { + resourceStats.addPrivateSize(overhead); + } + resourceStats.addProportionalSize(size, usageCount); + resourceStats.addProportionalSize(overhead, usageCount * count); + } + } + + /** + * Stats for an individual {@link ResourceEntry}. + */ + public static class ResourceStatistics { + + /** + * The empty, immutable instance of ResourceStatistics which contains 0 for all values. + */ + public static final ResourceStatistics EMPTY = new ResourceStatistics(); + + private int privateSize = 0; + private int sharedSize = 0; + private double proportionalSize = 0; + + private ResourceStatistics() { + } + + /** + * The number of bytes that would be freed if this resource was removed. + */ + public int getPrivateSize() { + return privateSize; + } + + /** + * The number of bytes taken up by this resource that are also shared with other resources. + */ + public int getSharedSize() { + return sharedSize; + } + + /** + * The total size this resource is responsible for. + */ + public double getProportionalSize() { + return proportionalSize; + } + + private void addPrivateSize(int privateSize) { + this.privateSize += privateSize; + } + + private void addSharedSize(int sharedSize) { + this.sharedSize += sharedSize; + } + + private void addProportionalSize(int numerator, int denominator) { + this.proportionalSize += 1.0 * numerator / denominator; + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java index 8752b9054..b6dffd660 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java @@ -16,9 +16,9 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; -import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; @@ -36,57 +36,67 @@ import java.util.Map; */ public final class ResourceTableChunk extends ChunkWithChunks { - /** A string pool containing all string resource values in the entire resource table. */ - private StringPoolChunk stringPool; + /** + * A string pool containing all string resource values in the entire resource table. + */ + private StringPoolChunk stringPool; - /** The packages contained in this resource table. */ - private final Map packages = new HashMap<>(); + /** + * The packages contained in this resource table. + */ + private final Map packages = new HashMap<>(); - protected ResourceTableChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - // packageCount. We ignore this, because we already know how many chunks we have. - Preconditions.checkState(buffer.getInt() >= 1, "ResourceTableChunk package count was < 1."); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - packages.clear(); - for (Chunk chunk : getChunks().values()) { - if (chunk instanceof PackageChunk) { - PackageChunk packageChunk = (PackageChunk) chunk; - packages.put(packageChunk.getPackageName(), packageChunk); - } else if (chunk instanceof StringPoolChunk) { - stringPool = (StringPoolChunk) chunk; - } + protected ResourceTableChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + // packageCount. We ignore this, because we already know how many chunks we have. + Preconditions.checkState(buffer.getInt() >= 1, "ResourceTableChunk package count was < 1."); } - Preconditions.checkNotNull(stringPool, "ResourceTableChunk must have a string pool."); - } - /** Returns the string pool containing all string resource values in the resource table. */ - public StringPoolChunk getStringPool() { - return stringPool; - } + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + packages.clear(); + for (Chunk chunk : getChunks().values()) { + if (chunk instanceof PackageChunk) { + PackageChunk packageChunk = (PackageChunk) chunk; + packages.put(packageChunk.getPackageName(), packageChunk); + } else if (chunk instanceof StringPoolChunk) { + stringPool = (StringPoolChunk) chunk; + } + } + Preconditions.checkNotNull(stringPool, "ResourceTableChunk must have a string pool."); + } - /** Returns the package with the given {@code packageName}. Else, returns null. */ - @Nullable - public PackageChunk getPackage(String packageName) { - return packages.get(packageName); - } + /** + * Returns the string pool containing all string resource values in the resource table. + */ + public StringPoolChunk getStringPool() { + return stringPool; + } - /** Returns the packages contained in this resource table. */ - public Collection getPackages() { - return Collections.unmodifiableCollection(packages.values()); - } + /** + * Returns the package with the given {@code packageName}. Else, returns null. + */ + @Nullable + public PackageChunk getPackage(String packageName) { + return packages.get(packageName); + } - @Override - protected Type getType() { - return Type.TABLE; - } + /** + * Returns the packages contained in this resource table. + */ + public Collection getPackages() { + return Collections.unmodifiableCollection(packages.values()); + } - @Override - protected void writeHeader(ByteBuffer output) { - super.writeHeader(output); - output.putInt(packages.size()); - } + @Override + protected Type getType() { + return Type.TABLE; + } + + @Override + protected void writeHeader(ByteBuffer output) { + super.writeHeader(output); + output.putInt(packages.size()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java index b9c62a1d5..d099defe4 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java @@ -22,20 +22,22 @@ import java.io.IOException; * A resource, typically a @{link Chunk}, that can be converted to an array of bytes. */ public interface SerializableResource { - - /** - * Converts this resource into an array of bytes representation. - * @return An array of bytes representing this resource. - * @throws IOException - */ - byte[] toByteArray() throws IOException; - /** - * Converts this resource into an array of bytes representation. - * @param shrink True if, when converting to a byte array, this resource can modify the returned - * bytes in an effort to reduce the size. - * @return An array of bytes representing this resource. - * @throws IOException - */ - byte[] toByteArray(boolean shrink) throws IOException; + /** + * Converts this resource into an array of bytes representation. + * + * @return An array of bytes representing this resource. + * @throws IOException + */ + byte[] toByteArray() throws IOException; + + /** + * Converts this resource into an array of bytes representation. + * + * @param shrink True if, when converting to a byte array, this resource can modify the returned + * bytes in an effort to reduce the size. + * @return An array of bytes representing this resource. + * @throws IOException + */ + byte[] toByteArray(boolean shrink) throws IOException; } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java index c0c73efaa..28b7f5907 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.io.LittleEndianDataOutputStream; -import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.IOException; @@ -28,396 +28,416 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.*; -/** Represents a string pool structure. */ +/** + * Represents a string pool structure. + */ public final class StringPoolChunk extends Chunk { - // These are the defined flags for the "flags" field of ResourceStringPoolHeader - private static final int SORTED_FLAG = 1 << 0; - private static final int UTF8_FLAG = 1 << 8; + // These are the defined flags for the "flags" field of ResourceStringPoolHeader + private static final int SORTED_FLAG = 1 << 0; + private static final int UTF8_FLAG = 1 << 8; - /** The offset from the start of the header that the stylesStart field is at. */ - private static final int STYLE_START_OFFSET = 24; + /** + * The offset from the start of the header that the stylesStart field is at. + */ + private static final int STYLE_START_OFFSET = 24; - /** Flags. */ - private final int flags; + /** + * Flags. + */ + private final int flags; - /** Index from header of the string data. */ - private final int stringsStart; + /** + * Index from header of the string data. + */ + private final int stringsStart; - /** Index from header of the style data. */ - private final int stylesStart; + /** + * Index from header of the style data. + */ + private final int stylesStart; - /** - * Number of strings in the original buffer. This is not necessarily the number of strings in - * {@code strings}. - */ - private final int stringCount; + /** + * Number of strings in the original buffer. This is not necessarily the number of strings in + * {@code strings}. + */ + private final int stringCount; - /** - * Number of styles in the original buffer. This is not necessarily the number of styles in - * {@code styles}. - */ - private final int styleCount; + /** + * Number of styles in the original buffer. This is not necessarily the number of styles in + * {@code styles}. + */ + private final int styleCount; - /** - * The strings ordered as they appear in the arsc file. e.g. strings.get(1234) gets the 1235th - * string in the arsc file. - */ - private final List strings = new ArrayList<>(); + /** + * The strings ordered as they appear in the arsc file. e.g. strings.get(1234) gets the 1235th + * string in the arsc file. + */ + private final List strings = new ArrayList<>(); - /** - * These styles have a 1:1 relationship with the strings. For example, styles.get(3) refers to - * the string at location strings.get(3). There are never more styles than strings (though there - * may be less). Inside of that are all of the styles referenced by that string. - */ - private final List styles = new ArrayList<>(); + /** + * These styles have a 1:1 relationship with the strings. For example, styles.get(3) refers to + * the string at location strings.get(3). There are never more styles than strings (though there + * may be less). Inside of that are all of the styles referenced by that string. + */ + private final List styles = new ArrayList<>(); - /** - * True if the original {@link StringPoolChunk} shows signs of being deduped. Specifically, this - * is set to true if there exists a string whose offset is <= the previous offset. This is used to - * preserve the deduping of strings for pools that have been deduped. - */ - private boolean isOriginalDeduped = false; + /** + * True if the original {@link StringPoolChunk} shows signs of being deduped. Specifically, this + * is set to true if there exists a string whose offset is <= the previous offset. This is used to + * preserve the deduping of strings for pools that have been deduped. + */ + private boolean isOriginalDeduped = false; - protected StringPoolChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - stringCount = buffer.getInt(); - styleCount = buffer.getInt(); - flags = buffer.getInt(); - stringsStart = buffer.getInt(); - stylesStart = buffer.getInt(); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - strings.addAll(readStrings(buffer, offset + stringsStart, stringCount)); - styles.addAll(readStyles(buffer, offset + stylesStart, styleCount)); - } - - /** - * Returns the 0-based index of the first occurrence of the given string, or -1 if the string is - * not in the pool. This runs in O(n) time. - * - * @param string The string to check the pool for. - * @return Index of the string, or -1 if not found. - */ - public int indexOf(String string) { - return strings.indexOf(string); - } - - /** - * Returns a string at the given (0-based) index. - * - * @param index The (0-based) index of the string to return. - * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()). - */ - public String getString(int index) { - return strings.get(index); - } - - public void updateString(int index, String value){ - strings.set(index, value); - } - - /** Returns the number of strings in this pool. */ - public int getStringCount() { - return strings.size(); - } - - /** - * Returns a style at the given (0-based) index. - * - * @param index The (0-based) index of the style to return. - * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()). - */ - public StringPoolStyle getStyle(int index) { - return styles.get(index); - } - - /** Returns the number of styles in this pool. */ - public int getStyleCount() { - return styles.size(); - } - - /** Returns the type of strings in this pool. */ - public BinaryResourceString.Type getStringType() { - return isUTF8() ? BinaryResourceString.Type.UTF8 : BinaryResourceString.Type.UTF16; - } - - @Override - protected Type getType() { - return Type.STRING_POOL; - } - - /** Returns the number of bytes needed for offsets based on {@code strings} and {@code styles}. */ - private int getOffsetSize() { - return (strings.size() + styles.size()) * 4; - } - - /** - * True if this string pool contains strings in UTF-8 format. Otherwise, strings are in UTF-16. - * - * @return true if @{code strings} are in UTF-8; false if they're in UTF-16. - */ - public boolean isUTF8() { - return (flags & UTF8_FLAG) != 0; - } - - /** - * True if this string pool contains already-sorted strings. - * - * @return true if @{code strings} are sorted. - */ - public boolean isSorted() { - return (flags & SORTED_FLAG) != 0; - } - - private List readStrings(ByteBuffer buffer, int offset, int count) { - List result = new ArrayList<>(); - int previousOffset = -1; - // After the header, we now have an array of offsets for the strings in this pool. - for (int i = 0; i < count; ++i) { - int stringOffset = offset + buffer.getInt(); - result.add(BinaryResourceString.decodeString(buffer, stringOffset, getStringType())); - if (stringOffset <= previousOffset) { - isOriginalDeduped = true; - } - previousOffset = stringOffset; - } - return result; - } - - private List readStyles(ByteBuffer buffer, int offset, int count) { - List result = new ArrayList<>(); - // After the array of offsets for the strings in the pool, we have an offset for the styles - // in this pool. - for (int i = 0; i < count; ++i) { - int styleOffset = offset + buffer.getInt(); - result.add(StringPoolStyle.create(buffer, styleOffset, this)); - } - return result; - } - - private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink) - throws IOException { - int stringOffset = 0; - Map used = new HashMap<>(); // Keeps track of strings already written - for (String string : strings) { - // Dedupe everything except stylized strings, unless shrink is true (then dedupe everything) - if (used.containsKey(string) && (shrink || isOriginalDeduped)) { - Integer offset = used.get(string); - offsets.putInt(offset == null ? 0 : offset); - } else { - byte[] encodedString = BinaryResourceString.encodeString(string, getStringType()); - payload.write(encodedString); - used.put(string, stringOffset); - offsets.putInt(stringOffset); - stringOffset += encodedString.length; - } + protected StringPoolChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + stringCount = buffer.getInt(); + styleCount = buffer.getInt(); + flags = buffer.getInt(); + stringsStart = buffer.getInt(); + stylesStart = buffer.getInt(); } - // ARSC files pad to a 4-byte boundary. We should do so too. - stringOffset = writePad(payload, stringOffset); - return stringOffset; - } + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + strings.addAll(readStrings(buffer, offset + stringsStart, stringCount)); + styles.addAll(readStyles(buffer, offset + stylesStart, styleCount)); + } - private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink) - throws IOException { - int styleOffset = 0; + /** + * Returns the 0-based index of the first occurrence of the given string, or -1 if the string is + * not in the pool. This runs in O(n) time. + * + * @param string The string to check the pool for. + * @return Index of the string, or -1 if not found. + */ + public int indexOf(String string) { + return strings.indexOf(string); + } + + /** + * Returns a string at the given (0-based) index. + * + * @param index The (0-based) index of the string to return. + * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()). + */ + public String getString(int index) { + return strings.get(index); + } + + public void updateString(int index, String value) { + strings.set(index, value); + } + + /** + * Returns the number of strings in this pool. + */ + public int getStringCount() { + return strings.size(); + } + + /** + * Returns a style at the given (0-based) index. + * + * @param index The (0-based) index of the style to return. + * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()). + */ + public StringPoolStyle getStyle(int index) { + return styles.get(index); + } + + /** + * Returns the number of styles in this pool. + */ + public int getStyleCount() { + return styles.size(); + } + + /** + * Returns the type of strings in this pool. + */ + public BinaryResourceString.Type getStringType() { + return isUTF8() ? BinaryResourceString.Type.UTF8 : BinaryResourceString.Type.UTF16; + } + + @Override + protected Type getType() { + return Type.STRING_POOL; + } + + /** + * Returns the number of bytes needed for offsets based on {@code strings} and {@code styles}. + */ + private int getOffsetSize() { + return (strings.size() + styles.size()) * 4; + } + + /** + * True if this string pool contains strings in UTF-8 format. Otherwise, strings are in UTF-16. + * + * @return true if @{code strings} are in UTF-8; false if they're in UTF-16. + */ + public boolean isUTF8() { + return (flags & UTF8_FLAG) != 0; + } + + /** + * True if this string pool contains already-sorted strings. + * + * @return true if @{code strings} are sorted. + */ + public boolean isSorted() { + return (flags & SORTED_FLAG) != 0; + } + + private List readStrings(ByteBuffer buffer, int offset, int count) { + List result = new ArrayList<>(); + int previousOffset = -1; + // After the header, we now have an array of offsets for the strings in this pool. + for (int i = 0; i < count; ++i) { + int stringOffset = offset + buffer.getInt(); + result.add(BinaryResourceString.decodeString(buffer, stringOffset, getStringType())); + if (stringOffset <= previousOffset) { + isOriginalDeduped = true; + } + previousOffset = stringOffset; + } + return result; + } + + private List readStyles(ByteBuffer buffer, int offset, int count) { + List result = new ArrayList<>(); + // After the array of offsets for the strings in the pool, we have an offset for the styles + // in this pool. + for (int i = 0; i < count; ++i) { + int styleOffset = offset + buffer.getInt(); + result.add(StringPoolStyle.create(buffer, styleOffset, this)); + } + return result; + } + + private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int stringOffset = 0; + Map used = new HashMap<>(); // Keeps track of strings already written + for (String string : strings) { + // Dedupe everything except stylized strings, unless shrink is true (then dedupe everything) + if (used.containsKey(string) && (shrink || isOriginalDeduped)) { + Integer offset = used.get(string); + offsets.putInt(offset == null ? 0 : offset); + } else { + byte[] encodedString = BinaryResourceString.encodeString(string, getStringType()); + payload.write(encodedString); + used.put(string, stringOffset); + offsets.putInt(stringOffset); + stringOffset += encodedString.length; + } + } + + // ARSC files pad to a 4-byte boundary. We should do so too. + stringOffset = writePad(payload, stringOffset); + return stringOffset; + } + + private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int styleOffset = 0; if (!styles.isEmpty()) { - Map used = new HashMap<>(); // Keeps track of bytes already written - for (StringPoolStyle style : styles) { - if (!used.containsKey(style) || !shrink) { - byte[] encodedStyle = style.toByteArray(shrink); - payload.write(encodedStyle); - used.put(style, styleOffset); - offsets.putInt(styleOffset); - styleOffset += encodedStyle.length; - } else { // contains key and shrink is true - Integer offset = used.get(style); - offsets.putInt(offset == null ? 0 : offset); + Map used = new HashMap<>(); // Keeps track of bytes already written + for (StringPoolStyle style : styles) { + if (!used.containsKey(style) || !shrink) { + byte[] encodedStyle = style.toByteArray(shrink); + payload.write(encodedStyle); + used.put(style, styleOffset); + offsets.putInt(styleOffset); + styleOffset += encodedStyle.length; + } else { // contains key and shrink is true + Integer offset = used.get(style); + offsets.putInt(offset == null ? 0 : offset); + } + } + // The end of the spans are terminated with another sentinel value + payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END); + styleOffset += 4; + // TODO(acornwall): There appears to be an extra SPAN_END here... why? + payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END); + styleOffset += 4; + + styleOffset = writePad(payload, styleOffset); } - } - // The end of the spans are terminated with another sentinel value - payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END); - styleOffset += 4; - // TODO(acornwall): There appears to be an extra SPAN_END here... why? - payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END); - styleOffset += 4; - - styleOffset = writePad(payload, styleOffset); - } - return styleOffset; - } - - @Override - protected void writeHeader(ByteBuffer output) { - int stringsStart = getHeaderSize() + getOffsetSize(); - output.putInt(strings.size()); - output.putInt(styles.size()); - output.putInt(flags); - output.putInt(strings.isEmpty() ? 0 : stringsStart); - output.putInt(0); // Placeholder. The styles starting offset cannot be computed at this point. - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int stringOffset = 0; - ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()); - offsets.order(ByteOrder.LITTLE_ENDIAN); - - // Write to a temporary payload so we can rearrange this and put the offsets first - try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { - stringOffset = writeStrings(payload, offsets, shrink); - writeStyles(payload, offsets, shrink); - } - - output.write(offsets.array()); - output.write(baos.toByteArray()); - if (!styles.isEmpty()) { - header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset); - } - } - - /** - * Represents all of the styles for a particular string. The string is determined by its index - * in {@link StringPoolChunk}. - */ - public static class StringPoolStyle implements SerializableResource { - - // Styles are a list of integers with 0xFFFFFFFF serving as a sentinel value. - static final int RES_STRING_POOL_SPAN_END = 0xFFFFFFFF; - private final List spans; - - static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) { - Builder spans = ImmutableList.builder(); - int nameIndex = buffer.getInt(offset); - while (nameIndex != RES_STRING_POOL_SPAN_END) { - spans.add(StringPoolSpan.create(buffer, offset, parent)); - offset += StringPoolSpan.SPAN_LENGTH; - nameIndex = buffer.getInt(offset); - } - return new StringPoolStyle(spans.build()); - } - - private StringPoolStyle(List spans) { - this.spans = spans; + return styleOffset; } @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); + protected void writeHeader(ByteBuffer output) { + int stringsStart = getHeaderSize() + getOffsetSize(); + output.putInt(strings.size()); + output.putInt(styles.size()); + output.putInt(flags); + output.putInt(strings.isEmpty() ? 0 : stringsStart); + output.putInt(0); // Placeholder. The styles starting offset cannot be computed at this point. } @Override - public byte[] toByteArray(boolean shrink) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int stringOffset = 0; + ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()); + offsets.order(ByteOrder.LITTLE_ENDIAN); - try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { - for (StringPoolSpan span : spans) { - byte[] encodedSpan = span.toByteArray(shrink); - if (encodedSpan.length != StringPoolSpan.SPAN_LENGTH) { - throw new IllegalStateException("Encountered a span of invalid length."); - } - payload.write(encodedSpan); + // Write to a temporary payload so we can rearrange this and put the offsets first + try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { + stringOffset = writeStrings(payload, offsets, shrink); + writeStyles(payload, offsets, shrink); } - payload.writeInt(RES_STRING_POOL_SPAN_END); - } - return baos.toByteArray(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - StringPoolStyle that = (StringPoolStyle)o; - return Objects.equals(spans, that.spans); - } - - @Override - public int hashCode() { - return Objects.hash(spans); + output.write(offsets.array()); + output.write(baos.toByteArray()); + if (!styles.isEmpty()) { + header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset); + } } /** - * Returns a brief description of the contents of this style. The representation of this - * information is subject to change, but below is a typical example: - * - *

"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"
+ * Represents all of the styles for a particular string. The string is determined by its index + * in {@link StringPoolChunk}. */ - @Override - public String toString() { - return String.format("StringPoolStyle{spans=%s}", spans); - } - } + public static class StringPoolStyle implements SerializableResource { - /** Represents a styled span associated with a specific string. */ - private static class StringPoolSpan implements SerializableResource { - static final int SPAN_LENGTH = 12; + // Styles are a list of integers with 0xFFFFFFFF serving as a sentinel value. + static final int RES_STRING_POOL_SPAN_END = 0xFFFFFFFF; + private final List spans; - private final int nameIndex; - private final int start; - private final int stop; - private final StringPoolChunk parent; + static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) { + Builder spans = ImmutableList.builder(); + int nameIndex = buffer.getInt(offset); + while (nameIndex != RES_STRING_POOL_SPAN_END) { + spans.add(StringPoolSpan.create(buffer, offset, parent)); + offset += StringPoolSpan.SPAN_LENGTH; + nameIndex = buffer.getInt(offset); + } + return new StringPoolStyle(spans.build()); + } - static StringPoolSpan create(ByteBuffer buffer, int offset, StringPoolChunk parent) { - int nameIndex = buffer.getInt(offset); - int start = buffer.getInt(offset + 4); - int stop = buffer.getInt(offset + 8); - return new StringPoolSpan(nameIndex, start, stop, parent); - } + private StringPoolStyle(List spans) { + this.spans = spans; + } - private StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) { - this.nameIndex = nameIndex; - this.start = start; - this.stop = stop; - this.parent = parent; - } + @Override + public byte[] toByteArray() throws IOException { + return toByteArray(false); + } - @Override - public final byte[] toByteArray() { - return toByteArray(false); - } + @Override + public byte[] toByteArray(boolean shrink) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); - @Override - public final byte[] toByteArray(boolean shrink) { - ByteBuffer buffer = ByteBuffer.allocate(SPAN_LENGTH).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(nameIndex); - buffer.putInt(start); - buffer.putInt(stop); - return buffer.array(); - } + try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { + for (StringPoolSpan span : spans) { + byte[] encodedSpan = span.toByteArray(shrink); + if (encodedSpan.length != StringPoolSpan.SPAN_LENGTH) { + throw new IllegalStateException("Encountered a span of invalid length."); + } + payload.write(encodedSpan); + } + payload.writeInt(RES_STRING_POOL_SPAN_END); + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - StringPoolSpan that = (StringPoolSpan)o; - return nameIndex == that.nameIndex && - start == that.start && - stop == that.stop && - Objects.equals(parent, that.parent); - } + return baos.toByteArray(); + } - @Override - public int hashCode() { - return Objects.hash(nameIndex, start, stop, parent); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringPoolStyle that = (StringPoolStyle) o; + return Objects.equals(spans, that.spans); + } + + @Override + public int hashCode() { + return Objects.hash(spans); + } + + /** + * Returns a brief description of the contents of this style. The representation of this + * information is subject to change, but below is a typical example: + * + *
"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"
+ */ + @Override + public String toString() { + return String.format("StringPoolStyle{spans=%s}", spans); + } } /** - * Returns a brief description of this span. The representation of this information is subject - * to change, but below is a typical example: - * - *
"StringPoolSpan{foo, start=0, stop=5}"
+ * Represents a styled span associated with a specific string. */ - @Override - public String toString() { - return String.format("StringPoolSpan{%s, start=%d, stop=%d}", - parent.getString(nameIndex), start, stop); + private static class StringPoolSpan implements SerializableResource { + static final int SPAN_LENGTH = 12; + + private final int nameIndex; + private final int start; + private final int stop; + private final StringPoolChunk parent; + + static StringPoolSpan create(ByteBuffer buffer, int offset, StringPoolChunk parent) { + int nameIndex = buffer.getInt(offset); + int start = buffer.getInt(offset + 4); + int stop = buffer.getInt(offset + 8); + return new StringPoolSpan(nameIndex, start, stop, parent); + } + + private StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) { + this.nameIndex = nameIndex; + this.start = start; + this.stop = stop; + this.parent = parent; + } + + @Override + public final byte[] toByteArray() { + return toByteArray(false); + } + + @Override + public final byte[] toByteArray(boolean shrink) { + ByteBuffer buffer = ByteBuffer.allocate(SPAN_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(nameIndex); + buffer.putInt(start); + buffer.putInt(stop); + return buffer.array(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringPoolSpan that = (StringPoolSpan) o; + return nameIndex == that.nameIndex && + start == that.start && + stop == that.stop && + Objects.equals(parent, that.parent); + } + + @Override + public int hashCode() { + return Objects.hash(nameIndex, start, stop, parent); + } + + /** + * Returns a brief description of this span. The representation of this information is subject + * to change, but below is a typical example: + * + *
"StringPoolSpan{foo, start=0, stop=5}"
+ */ + @Override + public String toString() { + return String.format("StringPoolSpan{%s, start=%d, stop=%d}", + parent.getString(nameIndex), start, stop); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java index 9a98ed54c..1030df262 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.io.LittleEndianDataOutputStream; import com.google.common.primitives.UnsignedBytes; -import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.IOException; @@ -38,412 +38,485 @@ import java.util.*; */ public final class TypeChunk extends Chunk { - /** The type identifier of the resource type this chunk is holding. */ - private final int id; - - /** The number of resources of this type at creation time. */ - private final int entryCount; - - /** The offset (from {@code offset}) in the original buffer where {@code entries} start. */ - private final int entriesStart; - - /** The resource configuration that these resource entries correspond to. */ - private BinaryResourceConfiguration configuration; - - /** A sparse list of resource entries defined by this chunk. */ - private final Map entries = new TreeMap<>(); - - protected TypeChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - id = UnsignedBytes.toInt(buffer.get()); - buffer.position(buffer.position() + 3); // Skip 3 bytes for packing - entryCount = buffer.getInt(); - entriesStart = buffer.getInt(); - configuration = BinaryResourceConfiguration.create(buffer); - } - - @Override - protected void init(ByteBuffer buffer) { - int offset = this.offset + entriesStart; - for (int i = 0; i < entryCount; ++i) { - Entry entry = Entry.create(buffer, offset, this); - if (entry != null) { - entries.put(i, entry); - } - } - } - - /** Returns the (1-based) type id of the resource types that this {@link TypeChunk} is holding. */ - public int getId() { - return id; - } - - /** Returns the name of the type this chunk represents (e.g. string, attr, id). */ - public String getTypeName() { - PackageChunk packageChunk = getPackageChunk(); - Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); - StringPoolChunk typePool = packageChunk.getTypeStringPool(); - Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass()); - return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index - } - - /** Returns the resource configuration that these resource entries correspond to. */ - public BinaryResourceConfiguration getConfiguration() { - return configuration; - } - - /** - * Sets the resource configuration that this chunk's entries correspond to. - * - * @param configuration The new configuration. - */ - public void setConfiguration(BinaryResourceConfiguration configuration) { - this.configuration = configuration; - } - - /** Returns the total number of entries for this type + configuration, including null entries. */ - public int getTotalEntryCount() { - return entryCount; - } - - /** Returns a sparse list of 0-based indices to resource entries defined by this chunk. */ - public Map getEntries() { - return Collections.unmodifiableMap(entries); - } - - /** Returns true if this chunk contains an entry for {@code resourceId}. */ - public boolean containsResource(BinaryResourceIdentifier resourceId) { - PackageChunk packageChunk = Preconditions.checkNotNull(getPackageChunk()); - int packageId = packageChunk.getId(); - int typeId = getId(); - return resourceId.packageId() == packageId - && resourceId.typeId() == typeId - && entries.containsKey(resourceId.entryId()); - } - - /** - * Overrides the entries in this chunk at the given index:entry pairs in {@code entries}. - * For example, if the current list of entries is {0: foo, 1: bar, 2: baz}, and {@code entries} - * is {1: qux, 3: quux}, then the entries will be changed to {0: foo, 1: qux, 2: baz}. If an entry - * has an index that does not exist in the dense entry list, then it is considered a no-op for - * that single entry. - * - * @param entries A sparse list containing index:entry pairs to override. - */ - public void overrideEntries(Map entries) { - for (Map.Entry entry : entries.entrySet()) { - int index = entry.getKey() != null ? entry.getKey() : -1; - overrideEntry(index, entry.getValue()); - } - } - - /** - * Overrides an entry at the given index. Passing null for the {@code entry} will remove that - * entry from {@code entries}. Indices < 0 or >= {@link #getTotalEntryCount()} are a no-op. - * - * @param index The 0-based index for the entry to override. - * @param entry The entry to override, or null if the entry should be removed at this location. - */ - public void overrideEntry(int index, @Nullable Entry entry) { - if (index >= 0 && index < entryCount) { - if (entry != null) { - entries.put(index, entry); - } else { - entries.remove(index); - } - } - } - - protected String getString(int index) { - ResourceTableChunk resourceTable = getResourceTableChunk(); - Preconditions.checkNotNull(resourceTable, "%s has no resource table.", getClass()); - return resourceTable.getStringPool().getString(index); - } - - protected String getKeyName(int index) { - PackageChunk packageChunk = getPackageChunk(); - Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); - StringPoolChunk keyPool = packageChunk.getKeyStringPool(); - Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass()); - return keyPool.getString(index); - } - - protected void updateKey(int index, String value) { - PackageChunk packageChunk = getPackageChunk(); - Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); - StringPoolChunk keyPool = packageChunk.getKeyStringPool(); - Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass()); - keyPool.updateString(index, value); - } - - @Nullable - private ResourceTableChunk getResourceTableChunk() { - Chunk chunk = getParent(); - while (chunk != null && !(chunk instanceof ResourceTableChunk)) { - chunk = chunk.getParent(); - } - return chunk != null ? (ResourceTableChunk) chunk : null; - } - - /** Returns the package enclosing this chunk, if any. Else, returns null. */ - @Nullable - public PackageChunk getPackageChunk() { - Chunk chunk = getParent(); - while (chunk != null && !(chunk instanceof PackageChunk)) { - chunk = chunk.getParent(); - } - return chunk != null ? (PackageChunk) chunk : null; - } - - @Override - protected Type getType() { - return Type.TABLE_TYPE; - } - - /** Returns the number of bytes needed for offsets based on {@code entries}. */ - private int getOffsetSize() { - return entryCount * 4; - } - - private int writeEntries(DataOutput payload, ByteBuffer offsets, boolean shrink) - throws IOException { - int entryOffset = 0; - for (int i = 0; i < entryCount; ++i) { - Entry entry = entries.get(i); - if (entry == null) { - offsets.putInt(Entry.NO_ENTRY); - } else { - byte[] encodedEntry = entry.toByteArray(shrink); - payload.write(encodedEntry); - offsets.putInt(entryOffset); - entryOffset += encodedEntry.length; - } - } - entryOffset = writePad(payload, entryOffset); - return entryOffset; - } - - @Override - protected void writeHeader(ByteBuffer output) { - int entriesStart = getHeaderSize() + getOffsetSize(); - output.putInt(id); // Write an unsigned byte with 3 bytes padding - output.putInt(entryCount); - output.putInt(entriesStart); - output.put(configuration.toByteArray(false)); - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()).order(ByteOrder.LITTLE_ENDIAN); - try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { - writeEntries(payload, offsets, shrink); - } - output.write(offsets.array()); - output.write(baos.toByteArray()); - } - - /** An {@link Entry} in a {@link TypeChunk}. Contains one or more {@link BinaryResourceValue}. */ - public static class Entry implements SerializableResource { - - /** An entry offset that indicates that a given resource is not present. */ - public static final int NO_ENTRY = 0xFFFFFFFF; - - /** Set if this is a complex resource. Otherwise, it's a simple resource. */ - private static final int FLAG_COMPLEX = 0x0001; - - /** Size of a single resource id + value mapping entry. */ - private static final int MAPPING_SIZE = 4 + BinaryResourceValue.SIZE; - - private final int headerSize; - private final int flags; - private final int keyIndex; - private BinaryResourceValue value; - private Map values; - private final int parentEntry; - private final TypeChunk parent; - - private Entry(int headerSize, - int flags, - int keyIndex, - BinaryResourceValue value, - Map values, - int parentEntry, - TypeChunk parent) { - this.headerSize = headerSize; - this.flags = flags; - this.keyIndex = keyIndex; - this.value = value; - this.values = values; - this.parentEntry = parentEntry; - this.parent = parent; - } - - /** Number of bytes in the header of the {@link Entry}. */ - public int headerSize() { return headerSize; } - - /** Resource entry flags. */ - public int flags() { return flags; } - - /** Index into {@link PackageChunk#getKeyStringPool} identifying this entry. */ - public int keyIndex() { return keyIndex; } - - /** The value of this resource entry, if this is not a complex entry. Else, null. */ - @Nullable - public BinaryResourceValue value() { return value; } + /** + * The type identifier of the resource type this chunk is holding. + */ + private final int id; /** - * Update the value of this resource entry - * @param value + * The number of resources of this type at creation time. */ - public void updateValue(@Nullable BinaryResourceValue value){ - this.value = value; + private final int entryCount; + + /** + * The offset (from {@code offset}) in the original buffer where {@code entries} start. + */ + private final int entriesStart; + + /** + * The resource configuration that these resource entries correspond to. + */ + private BinaryResourceConfiguration configuration; + + /** + * A sparse list of resource entries defined by this chunk. + */ + private final Map entries = new TreeMap<>(); + + protected TypeChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + id = UnsignedBytes.toInt(buffer.get()); + buffer.position(buffer.position() + 3); // Skip 3 bytes for packing + entryCount = buffer.getInt(); + entriesStart = buffer.getInt(); + configuration = BinaryResourceConfiguration.create(buffer); } - /** The extra values in this resource entry if this {@link #isComplex}. */ - public Map values() { return values; } - - /** Update the value in this resource entry if this {@link #isComplex}. */ - public void updateValue(int index, BinaryResourceValue value){ - if (index >= 0) { - if (value != null) { - values.put(index, value); - } else { - values.remove(index); + @Override + protected void init(ByteBuffer buffer) { + int offset = this.offset + entriesStart; + for (int i = 0; i < entryCount; ++i) { + Entry entry = Entry.create(buffer, offset, this); + if (entry != null) { + entries.put(i, entry); + } } - } } /** - * Entry into {@link PackageChunk} that is the parent {@link Entry} to this entry. - * This value only makes sense when this is complex ({@link #isComplex} returns true). + * Returns the (1-based) type id of the resource types that this {@link TypeChunk} is holding. */ - public int parentEntry() { return parentEntry; } - - /** The {@link TypeChunk} that this resource entry belongs to. */ - public TypeChunk parent() { return parent; } - - /** Returns the name of the type this chunk represents (e.g. string, attr, id). */ - public final String typeName() { - return parent().getTypeName(); - } - - /** The total number of bytes that this {@link Entry} takes up. */ - public final int size() { - return headerSize() + (isComplex() ? values().size() * MAPPING_SIZE : BinaryResourceValue.SIZE); - } - - /** Returns the key name identifying this resource entry. */ - public final String key() { - return parent().getKeyName(keyIndex()); - } - - /** Update the key name in this resource entry. */ - public final void updateKey(int keyIndex, String value) { - parent().updateKey(keyIndex, value); - } - - /** Returns true if this is a complex resource. */ - public final boolean isComplex() { - return (flags() & FLAG_COMPLEX) != 0; + public int getId() { + return id; } /** - * Creates a new {@link Entry} whose contents start at the 0-based position in - * {@code buffer} given by a 4-byte value read from {@code buffer} and then added to - * {@code baseOffset}. If the value read from {@code buffer} is equal to {@link #NO_ENTRY}, then - * null is returned as there is no resource at that position. + * Returns the name of the type this chunk represents (e.g. string, attr, id). + */ + public String getTypeName() { + PackageChunk packageChunk = getPackageChunk(); + Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); + StringPoolChunk typePool = packageChunk.getTypeStringPool(); + Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass()); + return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index + } + + /** + * Returns the resource configuration that these resource entries correspond to. + */ + public BinaryResourceConfiguration getConfiguration() { + return configuration; + } + + /** + * Sets the resource configuration that this chunk's entries correspond to. * - *

Otherwise, this position is parsed and returned as an {@link Entry}. + * @param configuration The new configuration. + */ + public void setConfiguration(BinaryResourceConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Returns the total number of entries for this type + configuration, including null entries. + */ + public int getTotalEntryCount() { + return entryCount; + } + + /** + * Returns a sparse list of 0-based indices to resource entries defined by this chunk. + */ + public Map getEntries() { + return Collections.unmodifiableMap(entries); + } + + /** + * Returns true if this chunk contains an entry for {@code resourceId}. + */ + public boolean containsResource(BinaryResourceIdentifier resourceId) { + PackageChunk packageChunk = Preconditions.checkNotNull(getPackageChunk()); + int packageId = packageChunk.getId(); + int typeId = getId(); + return resourceId.packageId() == packageId + && resourceId.typeId() == typeId + && entries.containsKey(resourceId.entryId()); + } + + /** + * Overrides the entries in this chunk at the given index:entry pairs in {@code entries}. + * For example, if the current list of entries is {0: foo, 1: bar, 2: baz}, and {@code entries} + * is {1: qux, 3: quux}, then the entries will be changed to {0: foo, 1: qux, 2: baz}. If an entry + * has an index that does not exist in the dense entry list, then it is considered a no-op for + * that single entry. * - * @param buffer A buffer positioned at an offset to an {@link Entry}. - * @param baseOffset Offset that must be added to the value at {@code buffer}'s position. - * @param parent The {@link TypeChunk} that this resource entry belongs to. - * @return New {@link Entry} or null if there is no resource at this location. + * @param entries A sparse list containing index:entry pairs to override. + */ + public void overrideEntries(Map entries) { + for (Map.Entry entry : entries.entrySet()) { + int index = entry.getKey() != null ? entry.getKey() : -1; + overrideEntry(index, entry.getValue()); + } + } + + /** + * Overrides an entry at the given index. Passing null for the {@code entry} will remove that + * entry from {@code entries}. Indices < 0 or >= {@link #getTotalEntryCount()} are a no-op. + * + * @param index The 0-based index for the entry to override. + * @param entry The entry to override, or null if the entry should be removed at this location. + */ + public void overrideEntry(int index, @Nullable Entry entry) { + if (index >= 0 && index < entryCount) { + if (entry != null) { + entries.put(index, entry); + } else { + entries.remove(index); + } + } + } + + protected String getString(int index) { + ResourceTableChunk resourceTable = getResourceTableChunk(); + Preconditions.checkNotNull(resourceTable, "%s has no resource table.", getClass()); + return resourceTable.getStringPool().getString(index); + } + + protected String getKeyName(int index) { + PackageChunk packageChunk = getPackageChunk(); + Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); + StringPoolChunk keyPool = packageChunk.getKeyStringPool(); + Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass()); + return keyPool.getString(index); + } + + protected void updateKey(int index, String value) { + PackageChunk packageChunk = getPackageChunk(); + Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); + StringPoolChunk keyPool = packageChunk.getKeyStringPool(); + Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass()); + keyPool.updateString(index, value); + } + + @Nullable + private ResourceTableChunk getResourceTableChunk() { + Chunk chunk = getParent(); + while (chunk != null && !(chunk instanceof ResourceTableChunk)) { + chunk = chunk.getParent(); + } + return chunk != null ? (ResourceTableChunk) chunk : null; + } + + /** + * Returns the package enclosing this chunk, if any. Else, returns null. */ @Nullable - public static Entry create(ByteBuffer buffer, int baseOffset, TypeChunk parent) { - int offset = buffer.getInt(); - if (offset == NO_ENTRY) { - return null; - } - int position = buffer.position(); - buffer.position(baseOffset + offset); // Set buffer position to resource entry start - Entry result = newInstance(buffer, parent); - buffer.position(position); // Restore buffer position - return result; - } - - @Nullable - private static Entry newInstance(ByteBuffer buffer, TypeChunk parent) { - int headerSize = buffer.getShort() & 0xFFFF; - int flags = buffer.getShort() & 0xFFFF; - int keyIndex = buffer.getInt(); - BinaryResourceValue value = null; - Map values = new LinkedHashMap<>(); - int parentEntry = 0; - if ((flags & FLAG_COMPLEX) != 0) { - parentEntry = buffer.getInt(); - int valueCount = buffer.getInt(); - for (int i = 0; i < valueCount; ++i) { - values.put(buffer.getInt(), BinaryResourceValue.create(buffer)); + public PackageChunk getPackageChunk() { + Chunk chunk = getParent(); + while (chunk != null && !(chunk instanceof PackageChunk)) { + chunk = chunk.getParent(); } - } else { - value = BinaryResourceValue.create(buffer); - } - return new Entry(headerSize, flags, keyIndex, value, values, parentEntry, parent); + return chunk != null ? (PackageChunk) chunk : null; } @Override - public final byte[] toByteArray() { - return toByteArray(false); + protected Type getType() { + return Type.TABLE_TYPE; } - @Override - public final byte[] toByteArray(boolean shrink) { - ByteBuffer buffer = ByteBuffer.allocate(size()); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putShort((short) headerSize()); - buffer.putShort((short) flags()); - buffer.putInt(keyIndex()); - if (isComplex()) { - buffer.putInt(parentEntry()); - buffer.putInt(values().size()); - for (Map.Entry entry : values().entrySet()) { - buffer.putInt(entry.getKey()); - buffer.put(entry.getValue().toByteArray(shrink)); + /** + * Returns the number of bytes needed for offsets based on {@code entries}. + */ + private int getOffsetSize() { + return entryCount * 4; + } + + private int writeEntries(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int entryOffset = 0; + for (int i = 0; i < entryCount; ++i) { + Entry entry = entries.get(i); + if (entry == null) { + offsets.putInt(Entry.NO_ENTRY); + } else { + byte[] encodedEntry = entry.toByteArray(shrink); + payload.write(encodedEntry); + offsets.putInt(entryOffset); + entryOffset += encodedEntry.length; + } } - } else { - BinaryResourceValue value = value(); - Preconditions.checkNotNull(value, "A non-complex TypeChunk entry must have a value."); - buffer.put(value.toByteArray()); - } - return buffer.array(); + entryOffset = writePad(payload, entryOffset); + return entryOffset; } @Override - public final String toString() { - return String.format("Entry{key=%s}", key()); + protected void writeHeader(ByteBuffer output) { + int entriesStart = getHeaderSize() + getOffsetSize(); + output.putInt(id); // Write an unsigned byte with 3 bytes padding + output.putInt(entryCount); + output.putInt(entriesStart); + output.put(configuration.toByteArray(false)); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Entry entry = (Entry)o; - return headerSize == entry.headerSize && - flags == entry.flags && - keyIndex == entry.keyIndex && - parentEntry == entry.parentEntry && - Objects.equals(value, entry.value) && - Objects.equals(values, entry.values) && - Objects.equals(parent, entry.parent); + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()).order(ByteOrder.LITTLE_ENDIAN); + try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { + writeEntries(payload, offsets, shrink); + } + output.write(offsets.array()); + output.write(baos.toByteArray()); } - @Override - public int hashCode() { - return Objects.hash(headerSize, flags, keyIndex, value, values, parentEntry, parent); + /** + * An {@link Entry} in a {@link TypeChunk}. Contains one or more {@link BinaryResourceValue}. + */ + public static class Entry implements SerializableResource { + + /** + * An entry offset that indicates that a given resource is not present. + */ + public static final int NO_ENTRY = 0xFFFFFFFF; + + /** + * Set if this is a complex resource. Otherwise, it's a simple resource. + */ + private static final int FLAG_COMPLEX = 0x0001; + + /** + * Size of a single resource id + value mapping entry. + */ + private static final int MAPPING_SIZE = 4 + BinaryResourceValue.SIZE; + + private final int headerSize; + private final int flags; + private final int keyIndex; + private BinaryResourceValue value; + private Map values; + private final int parentEntry; + private final TypeChunk parent; + + private Entry(int headerSize, + int flags, + int keyIndex, + BinaryResourceValue value, + Map values, + int parentEntry, + TypeChunk parent) { + this.headerSize = headerSize; + this.flags = flags; + this.keyIndex = keyIndex; + this.value = value; + this.values = values; + this.parentEntry = parentEntry; + this.parent = parent; + } + + /** + * Number of bytes in the header of the {@link Entry}. + */ + public int headerSize() { + return headerSize; + } + + /** + * Resource entry flags. + */ + public int flags() { + return flags; + } + + /** + * Index into {@link PackageChunk#getKeyStringPool} identifying this entry. + */ + public int keyIndex() { + return keyIndex; + } + + /** + * The value of this resource entry, if this is not a complex entry. Else, null. + */ + @Nullable + public BinaryResourceValue value() { + return value; + } + + /** + * Update the value of this resource entry + * + * @param value + */ + public void updateValue(@Nullable BinaryResourceValue value) { + this.value = value; + } + + /** + * The extra values in this resource entry if this {@link #isComplex}. + */ + public Map values() { + return values; + } + + /** + * Update the value in this resource entry if this {@link #isComplex}. + */ + public void updateValue(int index, BinaryResourceValue value) { + if (index >= 0) { + if (value != null) { + values.put(index, value); + } else { + values.remove(index); + } + } + } + + /** + * Entry into {@link PackageChunk} that is the parent {@link Entry} to this entry. + * This value only makes sense when this is complex ({@link #isComplex} returns true). + */ + public int parentEntry() { + return parentEntry; + } + + /** + * The {@link TypeChunk} that this resource entry belongs to. + */ + public TypeChunk parent() { + return parent; + } + + /** + * Returns the name of the type this chunk represents (e.g. string, attr, id). + */ + public final String typeName() { + return parent().getTypeName(); + } + + /** + * The total number of bytes that this {@link Entry} takes up. + */ + public final int size() { + return headerSize() + (isComplex() ? values().size() * MAPPING_SIZE : BinaryResourceValue.SIZE); + } + + /** + * Returns the key name identifying this resource entry. + */ + public final String key() { + return parent().getKeyName(keyIndex()); + } + + /** + * Update the key name in this resource entry. + */ + public final void updateKey(int keyIndex, String value) { + parent().updateKey(keyIndex, value); + } + + /** + * Returns true if this is a complex resource. + */ + public final boolean isComplex() { + return (flags() & FLAG_COMPLEX) != 0; + } + + /** + * Creates a new {@link Entry} whose contents start at the 0-based position in + * {@code buffer} given by a 4-byte value read from {@code buffer} and then added to + * {@code baseOffset}. If the value read from {@code buffer} is equal to {@link #NO_ENTRY}, then + * null is returned as there is no resource at that position. + * + *

Otherwise, this position is parsed and returned as an {@link Entry}. + * + * @param buffer A buffer positioned at an offset to an {@link Entry}. + * @param baseOffset Offset that must be added to the value at {@code buffer}'s position. + * @param parent The {@link TypeChunk} that this resource entry belongs to. + * @return New {@link Entry} or null if there is no resource at this location. + */ + @Nullable + public static Entry create(ByteBuffer buffer, int baseOffset, TypeChunk parent) { + int offset = buffer.getInt(); + if (offset == NO_ENTRY) { + return null; + } + int position = buffer.position(); + buffer.position(baseOffset + offset); // Set buffer position to resource entry start + Entry result = newInstance(buffer, parent); + buffer.position(position); // Restore buffer position + return result; + } + + @Nullable + private static Entry newInstance(ByteBuffer buffer, TypeChunk parent) { + int headerSize = buffer.getShort() & 0xFFFF; + int flags = buffer.getShort() & 0xFFFF; + int keyIndex = buffer.getInt(); + BinaryResourceValue value = null; + Map values = new LinkedHashMap<>(); + int parentEntry = 0; + if ((flags & FLAG_COMPLEX) != 0) { + parentEntry = buffer.getInt(); + int valueCount = buffer.getInt(); + for (int i = 0; i < valueCount; ++i) { + values.put(buffer.getInt(), BinaryResourceValue.create(buffer)); + } + } else { + value = BinaryResourceValue.create(buffer); + } + return new Entry(headerSize, flags, keyIndex, value, values, parentEntry, parent); + } + + @Override + public final byte[] toByteArray() { + return toByteArray(false); + } + + @Override + public final byte[] toByteArray(boolean shrink) { + ByteBuffer buffer = ByteBuffer.allocate(size()); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort((short) headerSize()); + buffer.putShort((short) flags()); + buffer.putInt(keyIndex()); + if (isComplex()) { + buffer.putInt(parentEntry()); + buffer.putInt(values().size()); + for (Map.Entry entry : values().entrySet()) { + buffer.putInt(entry.getKey()); + buffer.put(entry.getValue().toByteArray(shrink)); + } + } else { + BinaryResourceValue value = value(); + Preconditions.checkNotNull(value, "A non-complex TypeChunk entry must have a value."); + buffer.put(value.toByteArray()); + } + return buffer.array(); + } + + @Override + public final String toString() { + return String.format("Entry{key=%s}", key()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry entry = (Entry) o; + return headerSize == entry.headerSize && + flags == entry.flags && + keyIndex == entry.keyIndex && + parentEntry == entry.parentEntry && + Objects.equals(value, entry.value) && + Objects.equals(values, entry.values) && + Objects.equals(parent, entry.parent); + } + + @Override + public int hashCode() { + return Objects.hash(headerSize, flags, keyIndex, value, values, parentEntry, parent); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java index 691d9a38a..c12922b8d 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java @@ -16,86 +16,98 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.primitives.UnsignedBytes; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** A chunk that contains a collection of resource entries for a particular resource data type. */ +/** + * A chunk that contains a collection of resource entries for a particular resource data type. + */ public final class TypeSpecChunk extends Chunk { - /** The id of the resource type that this type spec refers to. */ - private final int id; + /** + * The id of the resource type that this type spec refers to. + */ + private final int id; - /** Resource configuration masks. */ - private final int[] resources; + /** + * Resource configuration masks. + */ + private final int[] resources; - protected TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - id = UnsignedBytes.toInt(buffer.get()); - buffer.position(buffer.position() + 3); // Skip 3 bytes for packing - int resourceCount = buffer.getInt(); - resources = new int[resourceCount]; + protected TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + id = UnsignedBytes.toInt(buffer.get()); + buffer.position(buffer.position() + 3); // Skip 3 bytes for packing + int resourceCount = buffer.getInt(); + resources = new int[resourceCount]; - for (int i = 0; i < resourceCount; ++i) { - resources[i] = buffer.getInt(); + for (int i = 0; i < resourceCount; ++i) { + resources[i] = buffer.getInt(); + } } - } - /** - * Returns the (1-based) type id of the resources that this {@link TypeSpecChunk} has - * configuration masks for. - */ - public int getId() { - return id; - } - - /** Returns the number of resource entries that this chunk has configuration masks for. */ - public int getResourceCount() { - return resources.length; - } - - @Override - protected Type getType() { - return Type.TABLE_TYPE_SPEC; - } - - /** Returns the name of the type this chunk represents (e.g. string, attr, id). */ - public String getTypeName() { - PackageChunk packageChunk = getPackageChunk(); - Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); - StringPoolChunk typePool = packageChunk.getTypeStringPool(); - Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass()); - return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index - } - - /** Returns the package enclosing this chunk, if any. Else, returns null. */ - @Nullable - private PackageChunk getPackageChunk() { - Chunk chunk = getParent(); - while (chunk != null && !(chunk instanceof PackageChunk)) { - chunk = chunk.getParent(); + /** + * Returns the (1-based) type id of the resources that this {@link TypeSpecChunk} has + * configuration masks for. + */ + public int getId() { + return id; } - return chunk != null ? (PackageChunk) chunk : null; - } - @Override - protected void writeHeader(ByteBuffer output) { - super.writeHeader(output); - // id is an unsigned byte in the range [0-255]. It is guaranteed to be non-negative. - // Because our output is in little-endian, we are making use of the 4 byte packing here - output.putInt(id); - output.putInt(resources.length); - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - for (int resource : resources) { - output.writeInt(resource); + /** + * Returns the number of resource entries that this chunk has configuration masks for. + */ + public int getResourceCount() { + return resources.length; + } + + @Override + protected Type getType() { + return Type.TABLE_TYPE_SPEC; + } + + /** + * Returns the name of the type this chunk represents (e.g. string, attr, id). + */ + public String getTypeName() { + PackageChunk packageChunk = getPackageChunk(); + Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); + StringPoolChunk typePool = packageChunk.getTypeStringPool(); + Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass()); + return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index + } + + /** + * Returns the package enclosing this chunk, if any. Else, returns null. + */ + @Nullable + private PackageChunk getPackageChunk() { + Chunk chunk = getParent(); + while (chunk != null && !(chunk instanceof PackageChunk)) { + chunk = chunk.getParent(); + } + return chunk != null ? (PackageChunk) chunk : null; + } + + @Override + protected void writeHeader(ByteBuffer output) { + super.writeHeader(output); + // id is an unsigned byte in the range [0-255]. It is guaranteed to be non-negative. + // Because our output is in little-endian, we are making use of the 4 byte packing here + output.putInt(id); + output.putInt(resources.length); + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + for (int resource : resources) { + output.writeInt(resource); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java index 7117e63f7..0d07c3199 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -27,35 +28,35 @@ import java.nio.ByteBuffer; */ public final class UnknownChunk extends Chunk { - private final Type type; + private final Type type; - private final byte[] header; + private final byte[] header; - private final byte[] payload; + private final byte[] payload; - protected UnknownChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); + protected UnknownChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); - type = Type.fromCode(buffer.getShort(offset)); - header = new byte[headerSize - Chunk.METADATA_SIZE]; - payload = new byte[chunkSize - headerSize]; - buffer.get(header); - buffer.get(payload); - } + type = Type.fromCode(buffer.getShort(offset)); + header = new byte[headerSize - Chunk.METADATA_SIZE]; + payload = new byte[chunkSize - headerSize]; + buffer.get(header); + buffer.get(payload); + } - @Override - protected void writeHeader(ByteBuffer output) { - output.put(header); - } + @Override + protected void writeHeader(ByteBuffer output) { + output.put(header); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - output.write(payload); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + output.write(payload); + } - @Override - protected Type getType() { - return type; - } + @Override + protected Type getType() { + return type; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java index 1cb2977b3..f3d696834 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java @@ -20,129 +20,149 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Objects; -/** Represents an XML attribute and value. */ +/** + * Represents an XML attribute and value. + */ public class XmlAttribute implements SerializableResource { - /** The serialized size in bytes of an {@link XmlAttribute}. */ - public static final int SIZE = 12 + BinaryResourceValue.SIZE; + /** + * The serialized size in bytes of an {@link XmlAttribute}. + */ + public static final int SIZE = 12 + BinaryResourceValue.SIZE; - private final int namespaceIndex; - private final int nameIndex; - private final int rawValueIndex; - private final BinaryResourceValue typedValue; - private final XmlNodeChunk parent; + private final int namespaceIndex; + private final int nameIndex; + private final int rawValueIndex; + private final BinaryResourceValue typedValue; + private final XmlNodeChunk parent; - /** - * Creates a new {@link XmlAttribute} based on the bytes at the current {@code buffer} position. - * - * @param buffer A buffer whose position is at the start of a {@link XmlAttribute}. - * @param parent The parent chunk that contains this attribute; used for string lookups. - */ - public static XmlAttribute create(ByteBuffer buffer, XmlNodeChunk parent) { - int namespace = buffer.getInt(); - int name = buffer.getInt(); - int rawValue = buffer.getInt(); - BinaryResourceValue typedValue = BinaryResourceValue.create(buffer); - return new XmlAttribute(namespace, name, rawValue, typedValue, parent); - } + /** + * Creates a new {@link XmlAttribute} based on the bytes at the current {@code buffer} position. + * + * @param buffer A buffer whose position is at the start of a {@link XmlAttribute}. + * @param parent The parent chunk that contains this attribute; used for string lookups. + */ + public static XmlAttribute create(ByteBuffer buffer, XmlNodeChunk parent) { + int namespace = buffer.getInt(); + int name = buffer.getInt(); + int rawValue = buffer.getInt(); + BinaryResourceValue typedValue = BinaryResourceValue.create(buffer); + return new XmlAttribute(namespace, name, rawValue, typedValue, parent); + } - private XmlAttribute(int namespaceIndex, - int nameIndex, - int rawValueIndex, - BinaryResourceValue typedValue, - XmlNodeChunk parent) { - this.namespaceIndex = namespaceIndex; - this.nameIndex = nameIndex; - this.rawValueIndex = rawValueIndex; - this.typedValue = typedValue; - this.parent = parent; - } + private XmlAttribute(int namespaceIndex, + int nameIndex, + int rawValueIndex, + BinaryResourceValue typedValue, + XmlNodeChunk parent) { + this.namespaceIndex = namespaceIndex; + this.nameIndex = nameIndex; + this.rawValueIndex = rawValueIndex; + this.typedValue = typedValue; + this.parent = parent; + } - /** A string reference to the namespace URI, or -1 if not present. */ - public int namespaceIndex() { - return namespaceIndex; - } + /** + * A string reference to the namespace URI, or -1 if not present. + */ + public int namespaceIndex() { + return namespaceIndex; + } - /** A string reference to the attribute name. */ - public int nameIndex() { - return nameIndex; - } + /** + * A string reference to the attribute name. + */ + public int nameIndex() { + return nameIndex; + } - /** A string reference to a string containing the character value. */ - public int rawValueIndex() { - return rawValueIndex; - } + /** + * A string reference to a string containing the character value. + */ + public int rawValueIndex() { + return rawValueIndex; + } - /** A {@link BinaryResourceValue} instance containing the parsed value. */ - public BinaryResourceValue typedValue() { - return typedValue; - } + /** + * A {@link BinaryResourceValue} instance containing the parsed value. + */ + public BinaryResourceValue typedValue() { + return typedValue; + } - /** The parent of this XML attribute; used for dereferencing the namespace and name. */ - public XmlNodeChunk parent() { - return parent; - } + /** + * The parent of this XML attribute; used for dereferencing the namespace and name. + */ + public XmlNodeChunk parent() { + return parent; + } - /** The namespace URI, or the empty string if not present. */ - public final String namespace() { - return getString(namespaceIndex()); - } + /** + * The namespace URI, or the empty string if not present. + */ + public final String namespace() { + return getString(namespaceIndex()); + } - /** The attribute name, or the empty string if not present. */ - public final String name() { - return getString(nameIndex()); - } + /** + * The attribute name, or the empty string if not present. + */ + public final String name() { + return getString(nameIndex()); + } - /** The raw character value. */ - public final String rawValue() { - return getString(rawValueIndex()); - } + /** + * The raw character value. + */ + public final String rawValue() { + return getString(rawValueIndex()); + } - private String getString(int index) { - return parent().getString(index); - } + private String getString(int index) { + return parent().getString(index); + } - @Override - public byte[] toByteArray() { - return toByteArray(false); - } + @Override + public byte[] toByteArray() { + return toByteArray(false); + } - @Override - public byte[] toByteArray(boolean shrink) { - ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(namespaceIndex()); - buffer.putInt(nameIndex()); - buffer.putInt(rawValueIndex()); - buffer.put(typedValue().toByteArray(shrink)); - return buffer.array(); - } + @Override + public byte[] toByteArray(boolean shrink) { + ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(namespaceIndex()); + buffer.putInt(nameIndex()); + buffer.putInt(rawValueIndex()); + buffer.put(typedValue().toByteArray(shrink)); + return buffer.array(); + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - XmlAttribute that = (XmlAttribute)o; - return namespaceIndex == that.namespaceIndex && - nameIndex == that.nameIndex && - rawValueIndex == that.rawValueIndex && - Objects.equals(typedValue, that.typedValue) && - Objects.equals(parent, that.parent); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + XmlAttribute that = (XmlAttribute) o; + return namespaceIndex == that.namespaceIndex && + nameIndex == that.nameIndex && + rawValueIndex == that.rawValueIndex && + Objects.equals(typedValue, that.typedValue) && + Objects.equals(parent, that.parent); + } - @Override - public int hashCode() { - return Objects.hash(namespaceIndex, nameIndex, rawValueIndex, typedValue, parent); - } + @Override + public int hashCode() { + return Objects.hash(namespaceIndex, nameIndex, rawValueIndex, typedValue, parent); + } - /** - * Returns a brief description of this XML attribute. The representation of this information is - * subject to change, but below is a typical example: - * - *

"XmlAttribute{namespace=foo, name=bar, value=1234}"
- */ - @Override - public String toString() { - return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}", - namespace(), name(), rawValue()); - } + /** + * Returns a brief description of this XML attribute. The representation of this information is + * subject to change, but below is a typical example: + * + *
"XmlAttribute{namespace=foo, name=bar, value=1234}"
+ */ + @Override + public String toString() { + return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}", + namespace(), name(), rawValue()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java index 44bc66bef..2f961cb35 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java @@ -17,57 +17,68 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents an XML cdata node. */ +/** + * Represents an XML cdata node. + */ public final class XmlCdataChunk extends XmlNodeChunk { - /** A string reference to a string containing the raw character data. */ - private final int rawValue; + /** + * A string reference to a string containing the raw character data. + */ + private final int rawValue; - /** A {@link BinaryResourceValue} instance containing the parsed value. */ - private final BinaryResourceValue binaryResourceValue; + /** + * A {@link BinaryResourceValue} instance containing the parsed value. + */ + private final BinaryResourceValue binaryResourceValue; - protected XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - rawValue = buffer.getInt(); - binaryResourceValue = BinaryResourceValue.create(buffer); - } + protected XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + rawValue = buffer.getInt(); + binaryResourceValue = BinaryResourceValue.create(buffer); + } - /** Returns a string containing the raw character data of this chunk. */ - public String getRawValue() { - return getString(rawValue); - } + /** + * Returns a string containing the raw character data of this chunk. + */ + public String getRawValue() { + return getString(rawValue); + } - /** Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. */ - public BinaryResourceValue getResourceValue() { - return binaryResourceValue; - } + /** + * Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. + */ + public BinaryResourceValue getResourceValue() { + return binaryResourceValue; + } - @Override - protected Type getType() { - return Type.XML_CDATA; - } + @Override + protected Type getType() { + return Type.XML_CDATA; + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(rawValue); - output.write(binaryResourceValue.toByteArray()); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(rawValue); + output.write(binaryResourceValue.toByteArray()); + } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"
- */ - @Override - public String toString() { - return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}", - getLineNumber(), getComment(), getRawValue()); - } + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"
+ */ + @Override + public String toString() { + return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}", + getLineNumber(), getComment(), getRawValue()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java index 08aba6d3a..9c55bd42a 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; /** @@ -27,22 +28,24 @@ import java.nio.ByteBuffer; */ public final class XmlChunk extends ChunkWithChunks { - protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML; - } + @Override + protected Type getType() { + return Type.XML; + } - /** Returns a string at the provided (0-based) index if the index exists in the string pool. */ - public String getString(int index) { - for (Chunk chunk : getChunks().values()) { - if (chunk instanceof StringPoolChunk) { - return ((StringPoolChunk) chunk).getString(index); - } + /** + * Returns a string at the provided (0-based) index if the index exists in the string pool. + */ + public String getString(int index) { + for (Chunk chunk : getChunks().values()) { + if (chunk instanceof StringPoolChunk) { + return ((StringPoolChunk) chunk).getString(index); + } + } + throw new IllegalStateException("XmlChunk did not contain a string pool."); } - throw new IllegalStateException("XmlChunk did not contain a string pool."); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java index c336c68b9..f3020eeb8 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java @@ -17,59 +17,70 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents the end of an XML node. */ +/** + * Represents the end of an XML node. + */ public final class XmlEndElementChunk extends XmlNodeChunk { - /** A string reference to the namespace URI, or -1 if not present. */ - private final int namespace; + /** + * A string reference to the namespace URI, or -1 if not present. + */ + private final int namespace; - /** A string reference to the attribute name. */ - private final int name; + /** + * A string reference to the attribute name. + */ + private final int name; - protected XmlEndElementChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - namespace = buffer.getInt(); - name = buffer.getInt(); - } + protected XmlEndElementChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + namespace = buffer.getInt(); + name = buffer.getInt(); + } - /** Returns the namespace URI, or the empty string if no namespace is present. */ - public String getNamespace() { - return getString(namespace); - } + /** + * Returns the namespace URI, or the empty string if no namespace is present. + */ + public String getNamespace() { + return getString(namespace); + } - /** Returns the attribute name. */ - public String getName() { - return getString(name); - } + /** + * Returns the attribute name. + */ + public String getName() { + return getString(name); + } - @Override - protected Type getType() { - return Type.XML_END_ELEMENT; - } + @Override + protected Type getType() { + return Type.XML_END_ELEMENT; + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(namespace); - output.writeInt(name); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(namespace); + output.writeInt(name); + } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
-   * 
- */ - @Override - public String toString() { - return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}", - getLineNumber(), getComment(), getNamespace(), getName()); - } + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
+     * "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
+     * 
+ */ + @Override + public String toString() { + return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}", + getLineNumber(), getComment(), getNamespace(), getName()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java index dce571456..d90955eb1 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java @@ -17,54 +17,65 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents the start/end of a namespace in an XML document. */ +/** + * Represents the start/end of a namespace in an XML document. + */ public abstract class XmlNamespaceChunk extends XmlNodeChunk { - /** A string reference to the namespace prefix. */ - private final int prefix; + /** + * A string reference to the namespace prefix. + */ + private final int prefix; - /** A string reference to the namespace URI. */ - private final int uri; + /** + * A string reference to the namespace URI. + */ + private final int uri; - protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - prefix = buffer.getInt(); - uri = buffer.getInt(); - } + protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + prefix = buffer.getInt(); + uri = buffer.getInt(); + } - /** Returns the namespace prefix. */ - public String getPrefix() { - return getString(prefix); - } + /** + * Returns the namespace prefix. + */ + public String getPrefix() { + return getString(prefix); + } - /** Returns the namespace URI. */ - public String getUri() { - return getString(uri); - } + /** + * Returns the namespace URI. + */ + public String getUri() { + return getString(uri); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(prefix); - output.writeInt(uri); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(prefix); + output.writeInt(uri); + } - /** - * Returns a brief description of this namespace chunk. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
-   * 
- */ - @Override - public String toString() { - return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}", - getLineNumber(), getComment(), getPrefix(), getUri()); - } + /** + * Returns a brief description of this namespace chunk. The representation of this information is + * subject to change, but below is a typical example: + * + *
+     * "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
+     * 
+ */ + @Override + public String toString() { + return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}", + getLineNumber(), getComment(), getPrefix(), getUri()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java index 6cce6e9fd..49f70957d 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java @@ -17,17 +17,20 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** Represents the ending tag of a namespace in an XML document. */ +/** + * Represents the ending tag of a namespace in an XML document. + */ public final class XmlNamespaceEndChunk extends XmlNamespaceChunk { - protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML_END_NAMESPACE; - } + @Override + protected Type getType() { + return Type.XML_END_NAMESPACE; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java index 3cb3daff9..1fbd23383 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java @@ -17,17 +17,20 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** Represents the starting tag of a namespace in an XML document. */ +/** + * Represents the starting tag of a namespace in an XML document. + */ public final class XmlNamespaceStartChunk extends XmlNamespaceChunk { - protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML_START_NAMESPACE; - } + @Override + protected Type getType() { + return Type.XML_START_NAMESPACE; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java index 3f293c0a9..2dddc3578 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java @@ -17,80 +17,93 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** The common superclass for the various types of XML nodes. */ +/** + * The common superclass for the various types of XML nodes. + */ public abstract class XmlNodeChunk extends Chunk { - /** The line number in the original source at which this node appeared. */ - private final int lineNumber; + /** + * The line number in the original source at which this node appeared. + */ + private final int lineNumber; - /** A string reference of this node's comment. If this is -1, then there is no comment. */ - private final int comment; + /** + * A string reference of this node's comment. If this is -1, then there is no comment. + */ + private final int comment; - protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - lineNumber = buffer.getInt(); - comment = buffer.getInt(); - } - - /** Returns true if this XML node contains a comment. Else, returns false. */ - public boolean hasComment() { - return comment != -1; - } - - /** Returns the line number in the original source at which this node appeared. */ - public int getLineNumber() { - return lineNumber; - } - - /** Returns the comment associated with this node, if any. Else, returns the empty string. */ - public String getComment() { - return getString(comment); - } - - /** - * An {@link XmlNodeChunk} does not know by itself what strings its indices reference. In order - * to get the actual string, the first {@link XmlChunk} ancestor is found. The - * {@link XmlChunk} ancestor should have a string pool which {@code index} references. - * - * @param index The index of the string. - * @return String that the given {@code index} references, or empty string if {@code index} is -1. - */ - protected String getString(int index) { - if (index == -1) { // Special case. Packed XML files use -1 for "no string entry" - return ""; + protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + lineNumber = buffer.getInt(); + comment = buffer.getInt(); } - Chunk parent = getParent(); - while (parent != null) { - if (parent instanceof XmlChunk) { - return ((XmlChunk) parent).getString(index); - } - parent = parent.getParent(); + + /** + * Returns true if this XML node contains a comment. Else, returns false. + */ + public boolean hasComment() { + return comment != -1; } - throw new IllegalStateException("XmlNodeChunk did not have an XmlChunk parent."); - } - /** - * An {@link XmlNodeChunk} and anything that is itself an {@link XmlNodeChunk} has a header size - * of 16. Anything else is, interestingly, considered to be a payload. For that reason, this - * method is final. - */ - @Override - protected final void writeHeader(ByteBuffer output) { - super.writeHeader(output); - output.putInt(lineNumber); - output.putInt(comment); - } + /** + * Returns the line number in the original source at which this node appeared. + */ + public int getLineNumber() { + return lineNumber; + } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
"XmlNodeChunk{line=1234, comment=My awesome comment.}"
- */ - @Override - public String toString() { - return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment()); - } + /** + * Returns the comment associated with this node, if any. Else, returns the empty string. + */ + public String getComment() { + return getString(comment); + } + + /** + * An {@link XmlNodeChunk} does not know by itself what strings its indices reference. In order + * to get the actual string, the first {@link XmlChunk} ancestor is found. The + * {@link XmlChunk} ancestor should have a string pool which {@code index} references. + * + * @param index The index of the string. + * @return String that the given {@code index} references, or empty string if {@code index} is -1. + */ + protected String getString(int index) { + if (index == -1) { // Special case. Packed XML files use -1 for "no string entry" + return ""; + } + Chunk parent = getParent(); + while (parent != null) { + if (parent instanceof XmlChunk) { + return ((XmlChunk) parent).getString(index); + } + parent = parent.getParent(); + } + throw new IllegalStateException("XmlNodeChunk did not have an XmlChunk parent."); + } + + /** + * An {@link XmlNodeChunk} and anything that is itself an {@link XmlNodeChunk} has a header size + * of 16. Anything else is, interestingly, considered to be a payload. For that reason, this + * method is final. + */ + @Override + protected final void writeHeader(ByteBuffer output) { + super.writeHeader(output); + output.putInt(lineNumber); + output.putInt(comment); + } + + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
"XmlNodeChunk{line=1234, comment=My awesome comment.}"
+ */ + @Override + public String toString() { + return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java index 85d389f0e..4c81d5c59 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -31,56 +32,60 @@ import java.util.List; */ public class XmlResourceMapChunk extends Chunk { - /** The size of a resource reference for {@code resources} in bytes. */ - private static final int RESOURCE_SIZE = 4; + /** + * The size of a resource reference for {@code resources} in bytes. + */ + private static final int RESOURCE_SIZE = 4; - /** - * Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the - * resourceID returned by {@code resources.get(2)}. - */ - private final List resources = new ArrayList<>(); + /** + * Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the + * resourceID returned by {@code resources.get(2)}. + */ + private final List resources = new ArrayList<>(); - protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - resources.addAll(enumerateResources(buffer)); - } - - private List enumerateResources(ByteBuffer buffer) { - int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE; - List result = new ArrayList<>(resourceCount); - int offset = this.offset + getHeaderSize(); - buffer.mark(); - buffer.position(offset); - - for (int i = 0; i < resourceCount; ++i) { - result.add(buffer.getInt()); + protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); } - buffer.reset(); - return result; - } + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + resources.addAll(enumerateResources(buffer)); + } - /** Returns the resource ID that this {@code attributeId} maps to. */ - public BinaryResourceIdentifier getResourceId(int attributeId) { - return BinaryResourceIdentifier.create(resources.get(attributeId)); - } + private List enumerateResources(ByteBuffer buffer) { + int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE; + List result = new ArrayList<>(resourceCount); + int offset = this.offset + getHeaderSize(); + buffer.mark(); + buffer.position(offset); - @Override - protected Type getType() { - return Type.XML_RESOURCE_MAP; - } + for (int i = 0; i < resourceCount; ++i) { + result.add(buffer.getInt()); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - for (Integer resource : resources) { - output.writeInt(resource); + buffer.reset(); + return result; + } + + /** + * Returns the resource ID that this {@code attributeId} maps to. + */ + public BinaryResourceIdentifier getResourceId(int attributeId) { + return BinaryResourceIdentifier.create(resources.get(attributeId)); + } + + @Override + protected Type getType() { + return Type.XML_RESOURCE_MAP; + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + for (Integer resource : resources) { + output.writeInt(resource); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java index 523dcaf1a..d219dec23 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java @@ -16,9 +16,9 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -26,120 +26,144 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** Represents the beginning of an XML node. */ +/** + * Represents the beginning of an XML node. + */ public final class XmlStartElementChunk extends XmlNodeChunk { - /** A string reference to the namespace URI, or -1 if not present. */ - private final int namespace; + /** + * A string reference to the namespace URI, or -1 if not present. + */ + private final int namespace; - /** A string reference to the element name that this chunk represents. */ - private final int name; + /** + * A string reference to the element name that this chunk represents. + */ + private final int name; - /** The offset to the start of the attributes payload. */ - private final int attributeStart; + /** + * The offset to the start of the attributes payload. + */ + private final int attributeStart; - /** The number of attributes in the original buffer. */ - private final int attributeCount; + /** + * The number of attributes in the original buffer. + */ + private final int attributeCount; - /** The (0-based) index of the id attribute, or -1 if not present. */ - private final int idIndex; + /** + * The (0-based) index of the id attribute, or -1 if not present. + */ + private final int idIndex; - /** The (0-based) index of the class attribute, or -1 if not present. */ - private final int classIndex; + /** + * The (0-based) index of the class attribute, or -1 if not present. + */ + private final int classIndex; - /** The (0-based) index of the style attribute, or -1 if not present. */ - private final int styleIndex; + /** + * The (0-based) index of the style attribute, or -1 if not present. + */ + private final int styleIndex; - /** The XML attributes associated with this element. */ - private final List attributes = new ArrayList<>(); + /** + * The XML attributes associated with this element. + */ + private final List attributes = new ArrayList<>(); - protected XmlStartElementChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - namespace = buffer.getInt(); - name = buffer.getInt(); - attributeStart = (buffer.getShort() & 0xFFFF); - int attributeSize = (buffer.getShort() & 0xFFFF); - Preconditions.checkState(attributeSize == XmlAttribute.SIZE, - "attributeSize is wrong size. Got %s, want %s", attributeSize, XmlAttribute.SIZE); - attributeCount = (buffer.getShort() & 0xFFFF); + protected XmlStartElementChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + namespace = buffer.getInt(); + name = buffer.getInt(); + attributeStart = (buffer.getShort() & 0xFFFF); + int attributeSize = (buffer.getShort() & 0xFFFF); + Preconditions.checkState(attributeSize == XmlAttribute.SIZE, + "attributeSize is wrong size. Got %s, want %s", attributeSize, XmlAttribute.SIZE); + attributeCount = (buffer.getShort() & 0xFFFF); - // The following indices are 1-based and need to be adjusted. - idIndex = (buffer.getShort() & 0xFFFF) - 1; - classIndex = (buffer.getShort() & 0xFFFF) - 1; - styleIndex = (buffer.getShort() & 0xFFFF) - 1; - } - - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - attributes.addAll(enumerateAttributes(buffer)); - } - - private List enumerateAttributes(ByteBuffer buffer) { - List result = new ArrayList<>(attributeCount); - int offset = this.offset + getHeaderSize() + attributeStart; - int endOffset = offset + XmlAttribute.SIZE * attributeCount; - buffer.mark(); - buffer.position(offset); - - while (offset < endOffset) { - result.add(XmlAttribute.create(buffer, this)); - offset += XmlAttribute.SIZE; + // The following indices are 1-based and need to be adjusted. + idIndex = (buffer.getShort() & 0xFFFF) - 1; + classIndex = (buffer.getShort() & 0xFFFF) - 1; + styleIndex = (buffer.getShort() & 0xFFFF) - 1; } - buffer.reset(); - return result; - } - - /** Returns the namespace URI, or the empty string if not present. */ - public String getNamespace() { - return getString(namespace); - } - - /** Returns the element name that this chunk represents. */ - public String getName() { - return getString(name); - } - - /** Returns an unmodifiable list of this XML element's attributes. */ - public List getAttributes() { - return Collections.unmodifiableList(attributes); - } - - @Override - protected Type getType() { - return Type.XML_START_ELEMENT; - } - - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(namespace); - output.writeInt(name); - output.writeShort((short) XmlAttribute.SIZE); // attribute start - output.writeShort((short) XmlAttribute.SIZE); - output.writeShort((short) attributes.size()); - output.writeShort((short) (idIndex + 1)); - output.writeShort((short) (classIndex + 1)); - output.writeShort((short) (styleIndex + 1)); - for (XmlAttribute attribute : attributes) { - output.write(attribute.toByteArray(shrink)); + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + attributes.addAll(enumerateAttributes(buffer)); } - } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
-   * 
- */ - @Override - public String toString() { - return String.format( - "XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}", - getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString()); - } + private List enumerateAttributes(ByteBuffer buffer) { + List result = new ArrayList<>(attributeCount); + int offset = this.offset + getHeaderSize() + attributeStart; + int endOffset = offset + XmlAttribute.SIZE * attributeCount; + buffer.mark(); + buffer.position(offset); + + while (offset < endOffset) { + result.add(XmlAttribute.create(buffer, this)); + offset += XmlAttribute.SIZE; + } + + buffer.reset(); + return result; + } + + /** + * Returns the namespace URI, or the empty string if not present. + */ + public String getNamespace() { + return getString(namespace); + } + + /** + * Returns the element name that this chunk represents. + */ + public String getName() { + return getString(name); + } + + /** + * Returns an unmodifiable list of this XML element's attributes. + */ + public List getAttributes() { + return Collections.unmodifiableList(attributes); + } + + @Override + protected Type getType() { + return Type.XML_START_ELEMENT; + } + + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(namespace); + output.writeInt(name); + output.writeShort((short) XmlAttribute.SIZE); // attribute start + output.writeShort((short) XmlAttribute.SIZE); + output.writeShort((short) attributes.size()); + output.writeShort((short) (idIndex + 1)); + output.writeShort((short) (classIndex + 1)); + output.writeShort((short) (styleIndex + 1)); + for (XmlAttribute attribute : attributes) { + output.write(attribute.toByteArray(shrink)); + } + } + + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
+     * "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
+     * 
+ */ + @Override + public String toString() { + return String.format( + "XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}", + getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString()); + } } diff --git a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java index a023f48e1..6e2df114e 100644 --- a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java +++ b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java @@ -1,21 +1,17 @@ package com.vk.api.audio; import android.text.TextUtils; - import androidx.annotation.NonNull; - import com.vk.api.base.ApiRequest; import com.vk.dto.music.MusicTrack; import com.vk.log.L; - import org.json.JSONObject; - -import java.util.ArrayList; - import ru.vtosters.lite.music.Genius; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.Preferences; +import java.util.ArrayList; + public class AudioGetLyrics extends ApiRequest { private final MusicTrack musicTrack; diff --git a/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java b/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java index 0111560cf..dfa535f88 100644 --- a/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java +++ b/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java @@ -1,10 +1,8 @@ package com.vk.api.gifts; import android.content.Context; - import com.vk.api.base.ApiRequest; import com.vk.dto.gift.CatalogedGift; - import org.json.JSONException; import org.json.JSONObject; @@ -29,7 +27,7 @@ public class GiftGetByStickerId extends ApiRequest { @Override // com.vk.api.sdk.o.VKRequest public GiftGetByStickerId.a a(JSONObject jSONObject) throws JSONException { JSONObject jSONObject2 = jSONObject.getJSONObject("response"); - return new a(jSONObject2.optInt("balance"), + return new a(jSONObject2.optInt("balance"), new CatalogedGift(jSONObject2.getJSONObject("gift"))); } } \ No newline at end of file diff --git a/app/src/main/java/com/vk/core/util/DownloadUtils.java b/app/src/main/java/com/vk/core/util/DownloadUtils.java index 23ba63975..8ddceb31e 100644 --- a/app/src/main/java/com/vk/core/util/DownloadUtils.java +++ b/app/src/main/java/com/vk/core/util/DownloadUtils.java @@ -11,13 +11,15 @@ import android.net.Uri; import android.os.Environment; import android.text.TextUtils; import android.util.Log; - +import b.h.g.m.CameraUtils; +import b.h.g.m.FileUtils; import com.facebook.common.i.MediaUtils; import com.facebook.x.g.EncodedImage; import com.vk.imageloader.VKImageLoader; import com.vk.log.L; import com.vk.metrics.eventtracking.VkTracker; import com.vtosters.lite.R; +import ru.vtosters.lite.concurrent.VTExecutors; import java.io.File; import java.io.FileOutputStream; @@ -26,10 +28,6 @@ import java.io.InputStream; import java.net.URI; import java.util.HashSet; -import b.h.g.m.CameraUtils; -import b.h.g.m.FileUtils; -import ru.vtosters.lite.concurrent.VTExecutors; - public class DownloadUtils { public static final HashSet a = new HashSet<>(); @@ -49,7 +47,7 @@ public class DownloadUtils { ToastUtils.a(context.getString(R.string.error) + " [" + e2.getMessage() + "]"); } } - + private static void b(final Context context, String str, String uri, boolean z) { if (TextUtils.isEmpty(uri)) { return; diff --git a/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java b/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java index 251e4a757..644415d33 100644 --- a/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java +++ b/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java @@ -186,8 +186,7 @@ public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd this.D == this.b.c.getLocalId(); case VK_ID -> this.D == this.b.c.C1(); diff --git a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java index 6cda32bab..23d3e3bb4 100644 --- a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java +++ b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java @@ -23,7 +23,7 @@ public class MsgPartTextHolder extends MsgPartHolderBase { MsgPartTextHolder.this.f.a(MsgPartTextHolder.this.g.getLocalId()); }); mTextPart.setOnLongClickListener(view -> { - if(!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId()); + if (!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId()); return false; }); diff --git a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java index f372dd668..808a9d02d 100644 --- a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java +++ b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java @@ -28,7 +28,6 @@ import com.vk.im.engine.models.Profile; import com.vk.im.engine.models.dialogs.BubbleColors; import com.vk.im.engine.models.dialogs.Dialog; import com.vk.im.engine.models.messages.Msg; -import com.vk.im.engine.models.messages.MsgSyncState; import com.vk.im.engine.utils.MsgPermissionHelper; import com.vk.im.ui.components.viewcontrollers.msg_list.BombView; import com.vk.im.ui.components.viewcontrollers.msg_list.SwipeToReplyItemTouchCallback; @@ -144,7 +143,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { this.X = context.getString(m.vkim_accessibility_msg_unread); MsgBubbleView var5 = this.e; var5.setContentView(this.B.a(var4, var5)); - ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams)this.e.getLayoutParams(); + ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams) this.e.getLayoutParams(); Rect var6 = this.G; var6.left = var7.leftMargin; var6.right = var7.rightMargin; @@ -337,7 +336,8 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { if (args.w()) { var12 = var11; if (args.k()) { - label30: { + label30: + { if (args.G()) { var12 = Screen.a(4); } else { @@ -671,7 +671,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { private void f(VhBindArgs args) { Msg var2 = args.b.c; if (var2 != null) { - this.P.setStateListener((BombView.d)null); + this.P.setStateListener((BombView.d) null); if (var2.M1() && !var2.E1()) { this.P.setVisibility(View.GONE); } else { @@ -832,7 +832,8 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { MsgBubbleView var8 = this.e; boolean var6 = var4; if (var5) { - label57: { + label57: + { if (!args.C()) { var6 = var4; if (!args.z()) { @@ -877,7 +878,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { if (args.s()) { this.itemView.setBackground(this.H); } else { - this.itemView.setBackground((Drawable)null); + this.itemView.setBackground((Drawable) null); } } @@ -985,7 +986,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { public void a(@NonNull Msg var1, int var2) { if (this.f0()) { - ((MsgPartCarouselHolder)this.B).a(var1, var2); + ((MsgPartCarouselHolder) this.B).a(var1, var2); } } @@ -1110,7 +1111,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b { public Unit invoke() { if (VhMsg.this.e.getMeasuredWidth() < VhMsg.h0) { - ((ViewGroup.MarginLayoutParams)VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a; + ((ViewGroup.MarginLayoutParams) VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a; VhMsg.this.e.invalidate(); VhMsg.this.e.requestLayout(); } else { diff --git a/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java b/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java index 4ef6a7831..007fadc4c 100644 --- a/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java +++ b/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java @@ -6,7 +6,6 @@ import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; -import android.widget.TextView; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.recyclerview.widget.RecyclerView; import com.vk.core.ui.VkBottomSheetBehavior; @@ -55,7 +54,7 @@ public final class MentionsController { @Override public boolean a() { - if(MentionsController.this.state) + if (MentionsController.this.state) return ViewExtKt.i(MentionsController.this.mCoordinatorLayout); return false; } @@ -105,7 +104,7 @@ public final class MentionsController { View inflate = ((ViewStub) this.j.findViewById(R.id.mentions_container_stub)).inflate(); ViewGroup viewGroup = (ViewGroup) inflate.findViewById(R.id.mentions_container); viewGroup.addView(this.mDialogMentionComponent.a(viewGroup, (Bundle) null)); - this.mCoordinatorLayout = inflate.findViewById(R.id.mentions_cl);; + this.mCoordinatorLayout = inflate.findViewById(R.id.mentions_cl); this.mBottomSheetBehavior = (VkBottomSheetBehavior) ((CoordinatorLayout.LayoutParams) viewGroup.getLayoutParams()).getBehavior(); this.mBottomSheetBehavior.a(new VkBottomSheetBehavior.b() { @Override diff --git a/app/src/main/java/com/vk/im/ui/views/Corners.java b/app/src/main/java/com/vk/im/ui/views/Corners.java index 6d09b5010..d6faee429 100644 --- a/app/src/main/java/com/vk/im/ui/views/Corners.java +++ b/app/src/main/java/com/vk/im/ui/views/Corners.java @@ -2,13 +2,13 @@ package com.vk.im.ui.views; import androidx.annotation.NonNull; -public class Corners{ +public class Corners { private int topLeft; private int topRight; private int bottomLeft; private int bottomRight; - public Corners(int i, int i2, int i3, int i4){ + public Corners(int i, int i2, int i3, int i4) { this.topLeft = i; this.topRight = i2; this.bottomLeft = i3; @@ -23,7 +23,7 @@ public class Corners{ this(0, 0, 0, 0, 15); } - public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj){ + public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj) { if ((i5 & 1) != 0) { i = corners.topLeft; } @@ -40,30 +40,30 @@ public class Corners{ return corners; } - public int a(){ + public int a() { return this.bottomLeft; } - public int b(){ + public int b() { return this.bottomRight; } - public int c(){ + public int c() { return this.topLeft; } - public int d(){ + public int d() { return this.topRight; } - public boolean e(){ + public boolean e() { int i; int i2 = this.topLeft; int i3 = this.topRight; return i2 == i3 && i3 == (i = this.bottomRight) && this.bottomLeft == i; } - public boolean equals(Object obj){ + public boolean equals(Object obj) { if (this != obj) { if (!(obj instanceof Corners)) { return false; @@ -74,24 +74,24 @@ public class Corners{ return true; } - public boolean f(){ + public boolean f() { return this.topLeft == 0 && e(); } - public void g(){ + public void g() { a(0, 0); } - public int hashCode(){ + public int hashCode() { return (((((this.topLeft * 31) + this.topRight) * 31) + this.bottomLeft) * 31) + this.bottomRight; } @NonNull - public String toString(){ + public String toString() { return "Corners(topLeft=" + this.topLeft + ", topRight=" + this.topRight + ", bottomLeft=" + this.bottomLeft + ", bottomRight=" + this.bottomRight + ")"; } - public Corners a(int i, int i2){ + public Corners a(int i, int i2) { if ((i2 & 1) > 0) { this.topLeft = i; } @@ -113,7 +113,7 @@ public class Corners{ return this; } - public Corners a(int i, int i2, int i3, int i4){ + public Corners a(int i, int i2, int i3, int i4) { this.topLeft = i; this.topRight = i2; this.bottomLeft = i3; @@ -121,7 +121,7 @@ public class Corners{ return this; } - public Corners a(Corners corners){ + public Corners a(Corners corners) { this.topLeft = corners.topLeft; this.topRight = corners.topRight; this.bottomLeft = corners.bottomLeft; diff --git a/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java b/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java index 5ada59705..27ceffa88 100644 --- a/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java +++ b/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java @@ -9,7 +9,6 @@ import android.graphics.Shader; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; - import com.vk.im.ui.o; public class StoryBorderView extends View { diff --git a/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java b/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java index fcea16294..3b5c2ec7c 100644 --- a/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java +++ b/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java @@ -1,8 +1,5 @@ package com.vk.im.ui.views.dialogs; -import static com.vk.im.ui.d.vkim_administration_title; -import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; - import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -17,10 +14,9 @@ import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.ColorInt; import androidx.appcompat.widget.AppCompatImageView; - +import b.h.g.r.MathExt; import com.vk.core.extensions.AnimationExtKt; import com.vk.core.util.ContextExtKt; import com.vk.core.util.Screen; @@ -42,9 +38,10 @@ import com.vk.im.ui.views.FixTextView; import com.vk.im.ui.views.avatars.AvatarView; import com.vk.im.ui.views.avatars.StoryBorderView; -import b.h.g.r.MathExt; +import static com.vk.im.ui.d.vkim_administration_title; +import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; -public class DialogItemView extends ViewGroup{ +public class DialogItemView extends ViewGroup { private final int B; private final int C; private final int D; @@ -80,15 +77,15 @@ public class DialogItemView extends ViewGroup{ private boolean g0; private boolean h0; - public DialogItemView(Context context){ + public DialogItemView(Context context) { this(context, null, 0, 6); } - public DialogItemView(Context context, AttributeSet attributeSet){ + public DialogItemView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0, 4); } - public DialogItemView(Context context, AttributeSet attributeSet, int i){ + public DialogItemView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); Resources resources = getResources(); this.a = resources.getDisplayMetrics(); @@ -255,11 +252,11 @@ public class DialogItemView extends ViewGroup{ addView(this.f0); } - public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2){ + public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2) { this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i); } - public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){ + public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) { if ((i & 1) != 0) { f = 0.0f; } @@ -279,7 +276,7 @@ public class DialogItemView extends ViewGroup{ return AnimationExtKt.b(view, f, j, j2, runnable); } - public static void a(View view, float f, float f2, int i, Object obj){ + public static void a(View view, float f, float f2, int i, Object obj) { if ((i & 1) != 0) { f = 1.0f; } @@ -291,7 +288,7 @@ public class DialogItemView extends ViewGroup{ AnimationExtKt.a(view, f, f2); } - public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){ + public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) { if ((i & 1) != 0) { f = 1.0f; } @@ -311,14 +308,14 @@ public class DialogItemView extends ViewGroup{ return AnimationExtKt.a(view, f, j, j2, runnable); } - static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj){ + static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj) { if ((i4 & 4) != 0) { i3 = 0; } dialogItemView.a(i, i2, i3); } - static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj){ + static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj) { if ((i7 & 1) != 0) { i = -2; } @@ -340,19 +337,19 @@ public class DialogItemView extends ViewGroup{ return dialogItemView.a(i, i2, i3, i4, i5, i6); } - private Drawable getDrOnlineMobile(){ + private Drawable getDrOnlineMobile() { return ContextExtKt.c(getContext(), f.ic_online_mobile_vkapp_composite_16); } - private Drawable getDrOnlineVkMe(){ + private Drawable getDrOnlineVkMe() { return ContextExtKt.c(getContext(), f.ic_online_mobile_vkme_composite_16); } - private Drawable getDrOnlineWeb(){ + private Drawable getDrOnlineWeb() { return ContextExtKt.c(getContext(), f.ic_online_web_composite_16); } - private int getTimeMargin(){ + private int getTimeMargin() { Layout layout = this.T.getLayout(); if (layout == null) { @@ -374,11 +371,11 @@ public class DialogItemView extends ViewGroup{ return 0; } - private int getTitleColorHighlight(){ + private int getTitleColorHighlight() { return ContextExtKt.a(getContext(), vkim_administration_title); } - private void h(int i, int i2, int i3, int i4){ + private void h(int i, int i2, int i3, int i4) { int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int measuredWidth = getMeasuredWidth() - getPaddingRight(); @@ -412,51 +409,51 @@ public class DialogItemView extends ViewGroup{ j(measuredWidth - r, max6, measuredWidth, q + max6); } - private int i(){ + private int i() { return g(this.K); } - private int j(){ + private int j() { return f(this.T) + Math.max(f(this.V), f(this.U)); } - private int k(){ + private int k() { return Math.max(g(this.T), g(this.V) + g(this.U)); } - private int l(){ + private int l() { return Math.max(Math.max(f(this.N), f(this.R)), Math.max(f(this.O), f(this.P))); } - private int m(){ + private int m() { return View.MeasureSpec.makeMeasureSpec(0, 0); } - private boolean n(){ + private boolean n() { return e(this.S); } - private int o(){ + private int o() { return f(this.S); } - private int p(){ + private int p() { return g(this.S); } - private int q(){ + private int q() { return MathExt.a(f(this.b0), f(this.c0), f(this.d0), f(this.e0), f(this.f0)); } - private int r(){ + private int r() { return g(this.b0) + MathExt.a(g(this.c0), g(this.d0), g(this.e0), g(this.f0)); } - private int s(){ + private int s() { return Math.max(f(this.a0), f(this.W)); } - private void setAttach(CharSequence charSequence){ + private void setAttach(CharSequence charSequence) { int i = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -467,57 +464,57 @@ public class DialogItemView extends ViewGroup{ this.U.setText(charSequence); } - private void setBodyLinesCount(int i){ + private void setBodyLinesCount(int i) { boolean z = i == 1; this.T.setSingleLine(z); this.T.setMaxLines(i); } - private int t(){ + private int t() { return g(this.a0) + g(this.W); } - private void u(){ + private void u() { ViewExtKt.b(this.b0, this.g0 | this.h0); } - public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo){ + public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) { this.K.a(dialog, profilesSimpleInfo); } - public void b(){ + public void b() { a(null, (ComposingType) null); } - public void c(){ + public void c() { a(getDrOnlineMobile()); } - public void d(){ + public void d() { a(getDrOnlineVkMe()); } - public void e(){ + public void e() { a(getDrOnlineWeb()); } - public void f(){ + public void f() { this.S.setVisibility(GONE); this.S.e(); } - public void g(){ + public void g() { b(this.L, 0.0f, 0L, 0L, null, 15, null); } @Override // android.view.ViewGroup, android.view.View - protected void onDetachedFromWindow(){ + protected void onDetachedFromWindow() { super.onDetachedFromWindow(); a(this.L, 0.0f, 0.0f, 3, null); } @Override // android.view.ViewGroup, android.view.View - protected void onLayout(boolean z, int i, int i2, int i3, int i4){ + protected void onLayout(boolean z, int i, int i2, int i3, int i4) { if (this.E) { g(i, i2, i3, i4); } else { @@ -526,7 +523,7 @@ public class DialogItemView extends ViewGroup{ } @Override // android.view.View - protected void onMeasure(int i, int i2){ + protected void onMeasure(int i, int i2) { if (this.E) { c(i); } else { @@ -534,11 +531,11 @@ public class DialogItemView extends ViewGroup{ } } - public void setAvatar(Profile profile){ + public void setAvatar(Profile profile) { this.K.a(profile); } - public void setBombVisible(boolean z){ + public void setBombVisible(boolean z) { if (z) { this.b0.setImageResource(f.ic_bomb_composite_24); } @@ -548,23 +545,23 @@ public class DialogItemView extends ViewGroup{ u(); } - public void setCasperIconColor(@ColorInt int i){ + public void setCasperIconColor(@ColorInt int i) { ViewExtKt.a(this.Q, i); } - public void setCasperIconVisible(boolean z){ + public void setCasperIconVisible(boolean z) { this.Q.setVisibility(z ? VISIBLE : GONE); } - public void setErrorVisible(boolean z){ + public void setErrorVisible(boolean z) { this.f0.setVisibility(z ? VISIBLE : GONE); } - public void setGiftVisible(boolean z){ + public void setGiftVisible(boolean z) { this.V.setVisibility(z ? VISIBLE : GONE); } - public void setHasStories(boolean z){ + public void setHasStories(boolean z) { if (this.I == z) { return; } @@ -590,7 +587,7 @@ public class DialogItemView extends ViewGroup{ setClipToPadding(!z); } - public void setMentionVisible(boolean z){ + public void setMentionVisible(boolean z) { if (z) { this.b0.setImageResource(f.ic_mention_composite_24); } @@ -600,15 +597,15 @@ public class DialogItemView extends ViewGroup{ u(); } - public void setMutedVisible(boolean z){ + public void setMutedVisible(boolean z) { this.P.setVisibility(z ? VISIBLE : GONE); } - public void setOnStoryClickListener(View.OnClickListener onClickListener){ + public void setOnStoryClickListener(View.OnClickListener onClickListener) { ViewExtKt.b(this.M, onClickListener); } - public void setSender(Profile profile){ + public void setSender(Profile profile) { if (profile == null) { f(); return; @@ -617,29 +614,29 @@ public class DialogItemView extends ViewGroup{ this.S.a(profile); } - public void setSendingVisible(boolean z){ + public void setSendingVisible(boolean z) { this.e0.setVisibility(z ? VISIBLE : GONE); } - public void setTime(CharSequence charSequence){ + public void setTime(CharSequence charSequence) { this.R.setText(charSequence); } - public void setUnreadOutVisible(boolean z){ + public void setUnreadOutVisible(boolean z) { this.d0.setVisibility(z ? VISIBLE : GONE); } - public void setVerifiedVisible(boolean z){ + public void setVerifiedVisible(boolean z) { this.O.setVisibility(z ? VISIBLE : GONE); } - private void a(Drawable drawable){ + private void a(Drawable drawable) { ViewExtKt.c(this.L, a(16), a(20)); this.L.setImageDrawable(drawable); a(this.L, 0.0f, 0L, 0L, null, 15, null); } - private void c(int i){ + private void c(int i) { int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight(); d(b(size - this.K.getViewSize()), m()); int r = r(); @@ -655,7 +652,7 @@ public class DialogItemView extends ViewGroup{ setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q())))); } - private void d(int i){ + private void d(int i) { int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight(); d(b(size - this.K.getViewSize()), m()); int r = r(); @@ -672,7 +669,7 @@ public class DialogItemView extends ViewGroup{ setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q())))); } - private void e(int i, int i2, int i3, int i4){ + private void e(int i, int i2, int i3, int i4) { if (e(this.T) && e(this.U)) { c(i, i2, i3, i4); } else if (e(this.T)) { @@ -683,7 +680,7 @@ public class DialogItemView extends ViewGroup{ } } - private void g(int i, int i2, int i3, int i4){ + private void g(int i, int i2, int i3, int i4) { int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int measuredWidth = getMeasuredWidth() - getPaddingRight(); @@ -706,12 +703,12 @@ public class DialogItemView extends ViewGroup{ j(measuredWidth - r, i8, measuredWidth, q + i8); } - private void i(int i, int i2, int i3, int i4){ + private void i(int i, int i2, int i3, int i4) { AvatarView avatarView = this.S; b(avatarView, i + b(avatarView), ((i2 + ((i4 - i2) / 2)) - (this.S.getMeasuredHeight() / 2)) + d(this.S)); } - private void j(int i, int i2, int i3, int i4){ + private void j(int i, int i2, int i3, int i4) { int i5 = i2 + ((i4 - i2) / 2); AppCompatImageView appCompatImageView = this.b0; b(appCompatImageView, b(appCompatImageView) + i, (i5 - (this.b0.getMeasuredHeight() / 2)) + d(this.b0)); @@ -728,7 +725,7 @@ public class DialogItemView extends ViewGroup{ b(appCompatImageView4, i + b(appCompatImageView4), (i5 - (this.f0.getMeasuredHeight() / 2)) + d(this.f0)); } - private void k(int i, int i2, int i3, int i4){ + private void k(int i, int i2, int i3, int i4) { int i5 = i2 + ((i4 - i2) / 2); TextView textView = this.W; b(textView, b(textView) + i, (i5 - (this.W.getMeasuredHeight() / 2)) + d(this.W)); @@ -741,7 +738,7 @@ public class DialogItemView extends ViewGroup{ b(appCompatImageView, i + b(appCompatImageView), (i5 - (this.a0.getMeasuredHeight() / 2)) + d(this.a0)); } - public void b(CharSequence charSequence, boolean z){ + public void b(CharSequence charSequence, boolean z) { int i = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -755,7 +752,7 @@ public class DialogItemView extends ViewGroup{ this.g.a(z ? this.B : this.h); } - private void f(int i, int i2, int i3, int i4){ + private void f(int i, int i2, int i3, int i4) { TextView textView = this.N; b(textView, i + b(textView), d(this.N) + i2); int right = this.N.getRight() + c(this.N); @@ -783,7 +780,7 @@ public class DialogItemView extends ViewGroup{ } } - private void l(int i, int i2, int i3, int i4){ + private void l(int i, int i2, int i3, int i4) { TextView textView = this.W; b(textView, i + b(textView), i2); int right = this.W.getRight() + c(this.W); @@ -791,33 +788,33 @@ public class DialogItemView extends ViewGroup{ b(appCompatImageView, right + b(appCompatImageView), (((this.W.getBottom() + this.W.getTop()) / 2) - (this.a0.getMeasuredHeight() / 2)) + d(this.a0)); } - private void e(int i, int i2){ + private void e(int i, int i2) { b(this.a0, i, 0, i2, 0); b(this.W, i, g(this.a0), i2, 0); } - public void a(CharSequence charSequence, boolean z){ + public void a(CharSequence charSequence, boolean z) { this.N.setText(charSequence); this.N.setTextColor(z ? getTitleColorHighlight() : this.f15761e); } - private void b(int i, int i2){ + private void b(int i, int i2) { b(this.O, i, 0, i2, 0); b(this.Q, i, 0, i2, 0); b(this.P, i, 0, i2, 0); b(this.R, i, 0, i2, 0); } - public void a(){ + public void a() { a(null, 1); setAttach(null); } - private boolean e(View view){ + private boolean e(View view) { return view.getVisibility() == VISIBLE; } - public void a(CharSequence charSequence, int i, CharSequence charSequence2){ + public void a(CharSequence charSequence, int i, CharSequence charSequence2) { boolean z = false; if (!(charSequence == null || charSequence.length() == 0)) { @@ -840,11 +837,11 @@ public class DialogItemView extends ViewGroup{ } } - private void b(int i, int i2, int i3){ + private void b(int i, int i2, int i3) { a(this.N, i, i3, i2, 0); } - private void b(int i, int i2, int i3, int i4){ + private void b(int i, int i2, int i3, int i4) { int g = e(this.V) ? g(this.V) + i : i; TextView textView = this.U; a(textView, g + b(textView), i2 + d(this.U)); @@ -852,7 +849,7 @@ public class DialogItemView extends ViewGroup{ b(appCompatImageView, i + b(appCompatImageView), (this.U.getTop() + (this.U.getMeasuredHeight() / 2)) - (this.V.getMeasuredHeight() / 2)); } - private void a(CharSequence charSequence, int i){ + private void a(CharSequence charSequence, int i) { int i2 = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -864,13 +861,13 @@ public class DialogItemView extends ViewGroup{ setBodyLinesCount(i); } - private void b(View view, int i, int i2, int i3, int i4){ + private void b(View view, int i, int i2, int i3, int i4) { if (view.getVisibility() != GONE) { a(view, i, i2, i3, i4); } } - public void a(CharSequence charSequence, ComposingType composingType){ + public void a(CharSequence charSequence, ComposingType composingType) { Drawable drawable = composingType == ComposingType.AUDIO ? this.H : this.G; boolean z = false; drawable.setVisible(!(charSequence == null || charSequence.length() == 0), false); @@ -885,13 +882,13 @@ public class DialogItemView extends ViewGroup{ this.W.setText(charSequence); } - private void b(View view, int i, int i2){ + private void b(View view, int i, int i2) { if (view.getVisibility() == VISIBLE) { a(view, i, i2); } } - private int b(View view){ + private int b(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -901,11 +898,11 @@ public class DialogItemView extends ViewGroup{ return 0; } - private int b(int i){ + private int b(int i) { return View.MeasureSpec.makeMeasureSpec(i, MeasureSpec.AT_MOST); } - private int g(View view){ + private int g(View view) { if (view.getVisibility() == GONE) { return 0; } @@ -915,13 +912,13 @@ public class DialogItemView extends ViewGroup{ return marginLayoutParams != null ? measuredWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin : measuredWidth; } - private void a(int i, int i2){ + private void a(int i, int i2) { a(this.K, i, 0, i2, 0); b(this.M, i, 0, i2, 0); b(this.L, i, 0, i2, 0); } - private int f(View view){ + private int f(View view) { if (view.getVisibility() == GONE) { return 0; } @@ -934,32 +931,32 @@ public class DialogItemView extends ViewGroup{ return measuredHeight; } - private void a(int i, int i2, int i3, int i4){ + private void a(int i, int i2, int i3, int i4) { AvatarView avatarView = this.K; a(avatarView, i + b(avatarView), i2 + d(this.K)); a(this.M, this.K.getLeft() - this.K.getPaddingLeft(), this.K.getTop() - this.K.getPaddingTop()); b(this.L, this.K.getRight() - this.L.getMeasuredWidth(), this.K.getBottom() - this.L.getMeasuredHeight()); } - private void c(int i, int i2){ + private void c(int i, int i2) { b(this.S, i, 0, i2, 0); } - private void c(int i, int i2, int i3, int i4){ + private void c(int i, int i2, int i3, int i4) { d(i, i2, i3, f(this.T) + i2); b(i, i2 + f(this.T), i3, i4); } - private int h(){ + private int h() { return f(this.K); } - private void d(int i, int i2, int i3, int i4){ + private void d(int i, int i2, int i3, int i4) { TextView textView = this.T; a(textView, i + b(textView), i2 + d(this.T)); } - private int c(View view){ + private int c(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -969,7 +966,7 @@ public class DialogItemView extends ViewGroup{ return 0; } - private void d(int i, int i2){ + private void d(int i, int i2) { b(this.b0, i, 0, i2, 0); b(this.c0, i, 0, i2, 0); b(this.d0, i, 0, i2, 0); @@ -977,13 +974,13 @@ public class DialogItemView extends ViewGroup{ b(this.f0, i, 0, i2, 0); } - private void a(int i, int i2, int i3){ + private void a(int i, int i2, int i3) { b(this.T, i, i3, i2, 0); b(this.V, i, i3, i2, 0); b(this.U, i, i3 + g(this.V), i2, 0); } - private int d(View view){ + private int d(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -993,7 +990,7 @@ public class DialogItemView extends ViewGroup{ return 0; } - private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6){ + private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6) { ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams(i, i2); marginLayoutParams.leftMargin = i3; marginLayoutParams.topMargin = i4; @@ -1002,23 +999,23 @@ public class DialogItemView extends ViewGroup{ return marginLayoutParams; } - public int a(int i){ + public int a(int i) { return (int) (i * (this.a.densityDpi / 160.0f)); } - public int a(float f2){ + public int a(float f2) { return (int) (f2 * (this.a.densityDpi / 160.0f)); } - private void a(View view, int i, int i2, int i3, int i4){ + private void a(View view, int i, int i2, int i3, int i4) { view.measure(ViewGroup.getChildMeasureSpec(i, i2 + b(view) + c(view), view.getLayoutParams().width), ViewGroup.getChildMeasureSpec(i3, i4 + d(view) + a(view), view.getLayoutParams().height)); } - private void a(View view, int i, int i2){ + private void a(View view, int i, int i2) { view.layout(i, i2, view.getMeasuredWidth() + i, view.getMeasuredHeight() + i2); } - private int a(View view){ + private int a(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { diff --git a/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java b/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java index 0c8c8b19b..bfcd7f239 100644 --- a/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java +++ b/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java @@ -6,7 +6,6 @@ import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.util.AttributeSet; - import com.vk.core.ui.themes.Themable; import com.vk.core.ui.themes.VKThemeHelper; import com.vk.core.util.ContextExtKt; @@ -14,7 +13,7 @@ import com.vk.dto.stories.model.StoriesContainer; import com.vk.stories.view.BorderedCircleImageView; import com.vtosters.lite.R; -public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable{ +public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable { private boolean f0; private int g0; private int h0; @@ -23,19 +22,19 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im private int unread; private int read; - public CommunityBorderedImageView(Context context){ + public CommunityBorderedImageView(Context context) { this(context, null, 0, 6); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0, 4); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2) { this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); this.g0 = VKThemeHelper.d((int) R.attr.accent); this.h0 = ContextExtKt.a(context, (int) R.color.gray_200); @@ -43,44 +42,44 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im this.j0 = new PorterDuffColorFilter(this.h0, PorterDuff.Mode.SRC_IN); } - public final void c(int unread, int read){ // get unread and read resid + public final void c(int unread, int read) { // get unread and read resid this.unread = unread; this.read = read; this.T = BitmapFactory.decodeResource(getResources(), this.unread); } - public final int getPrimaryColor(){ + public final int getPrimaryColor() { return this.g0; } - public final void setPrimaryColor(int i){ + public final void setPrimaryColor(int i) { this.g0 = i; this.i0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN); } - public final int getWasViewedColor(){ + public final int getWasViewedColor() { return this.h0; } - public final void setWasViewedColor(int i){ + public final void setWasViewedColor(int i) { this.h0 = i; this.j0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN); } - public final void o(){ + public final void o() { this.f0 = false; l(); } @Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View - public void onDraw(Canvas canvas){ + public void onDraw(Canvas canvas) { super.onDraw(canvas); if (this.f0) { a(canvas); } } - public final void setStoryContainer(StoriesContainer storiesContainer){ + public final void setStoryContainer(StoriesContainer storiesContainer) { if (storiesContainer.L1()) { this.f0 = true; m(); @@ -101,6 +100,6 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im } @Override // com.vk.core.ui.themes.Themable - public void v(){ + public void v() { } } \ No newline at end of file diff --git a/app/src/main/java/com/vk/sharing/view/TargetImageView.java b/app/src/main/java/com/vk/sharing/view/TargetImageView.java index 7358ce917..dcec58e21 100644 --- a/app/src/main/java/com/vk/sharing/view/TargetImageView.java +++ b/app/src/main/java/com/vk/sharing/view/TargetImageView.java @@ -1,26 +1,13 @@ package com.vk.sharing.view; -import static b.h.z.f.roboto_medium; -import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; - import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.Typeface; +import android.graphics.*; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.AttributeSet; - import androidx.annotation.UiThread; import androidx.core.content.ContextCompat; - import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.view.DraweeHolder; @@ -35,6 +22,9 @@ import ru.vtosters.lite.utils.Preferences; import java.util.Arrays; +import static b.h.z.f.roboto_medium; +import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; + @UiThread public class TargetImageView extends VKMultiImageView { private static final int I = Screen.a(2); diff --git a/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java b/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java index a2f830125..3aacdbe50 100644 --- a/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java +++ b/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java @@ -129,23 +129,23 @@ public final class StickerDetailsAdapter extends RecyclerView.Adapter B0 = new HashSet<>(); private final PipelineDraweeControllerBuilder f0; private int A0; @@ -72,35 +70,35 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The private String y0; private LottieDrawable z0; - public StoryCircleImageView(Context context){ + public StoryCircleImageView(Context context) { this(context, null); } - public StoryCircleImageView(Context context, AttributeSet attributeSet){ + public StoryCircleImageView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0); } - public StoryCircleImageView(Context context, AttributeSet attributeSet, int i){ + public StoryCircleImageView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); this.f0 = FrescoWrapper.c.d(); a(context, attributeSet, i); } - private void b(Canvas canvas){ + private void b(Canvas canvas) { this.z0.draw(canvas); } - private boolean o(){ + private boolean o() { return !this.u0 && !this.t0 && this.z0 == null && !this.w0 && !this.v0; } - private void p(){ + private void p() { this.z0.stop(); this.z0 = null; invalidate(); } - private void q(){ + private void q() { int i = this.j0; if (i != 0 && VKThemeHelper.k.a(i)) { this.k0 = VKThemeHelper.d(this.j0); @@ -132,19 +130,19 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The this.s0 = DrawableUtils.a(a4, i8, i8); } - private void r(){ + private void r() { this.c0 = Screen.a(o() ? 0 : 2); m(); q(); } - private void setUploadFailed(boolean z){ + private void setUploadFailed(boolean z) { this.u0 = z; invalidate(); } @Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View - public void onDraw(Canvas canvas){ + public void onDraw(Canvas canvas) { super.onDraw(canvas); setBorderAlpha(255); if (this.u0) { @@ -170,13 +168,13 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } @Override // com.vk.stories.view.BorderedCircleImageView, android.view.View - public void onSizeChanged(int i, int i2, int i3, int i4){ + public void onSizeChanged(int i, int i2, int i3, int i4) { super.onSizeChanged(i, i2, i3, i4); this.i0.setBounds(0, 0, i, i2); b(i); } - public void setStoryContainer(@Nullable StoriesContainer storiesContainer){ + public void setStoryContainer(@Nullable StoriesContainer storiesContainer) { boolean z = true; boolean z2 = storiesContainer != null && storiesContainer.Q1(); this.v0 = storiesContainer != null && storiesContainer.S1() && !storiesContainer.L1(); @@ -246,12 +244,12 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } @Override // com.vk.core.ui.themes.Themable - public void v(){ + public void v() { r(); invalidate(); } - private void b(int i){ + private void b(int i) { LottieDrawable lottieDrawable = this.z0; if (lottieDrawable == null || lottieDrawable.e() == null) { return; @@ -259,7 +257,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The this.z0.d(i / this.z0.e().a().width()); } - protected void a(Context context, AttributeSet attributeSet, int i){ + protected void a(Context context, AttributeSet attributeSet, int i) { TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, com.vtosters.lite.a0.StoryCircleImageView, i, 0); a(context, attributeSet, obtainStyledAttributes); obtainStyledAttributes.recycle(); @@ -267,7 +265,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The this.T = this.p0; } - public void b(@Nullable List list){ + public void b(@Nullable List list) { PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder = this.f0; pipelineDraweeControllerBuilder.j(); pipelineDraweeControllerBuilder.a(getController()); @@ -277,7 +275,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } @SuppressLint("ResourceType") - private void a(Context context, AttributeSet attributeSet, TypedArray typedArray){ // bullshit vk code is not working with custom bordered circle image + private void a(Context context, AttributeSet attributeSet, TypedArray typedArray) { // bullshit vk code is not working with custom bordered circle image Drawable drawable; this.A0 = typedArray.getDimensionPixelSize(0, Screen.a(64.0f)); if (typedArray.hasValue(2)) { @@ -344,13 +342,13 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The this.s0 = DrawableUtils.a(drawable4, i4, i4); } - public void a(StoriesContainer storiesContainer){ + public void a(StoriesContainer storiesContainer) { if (this.z0 == null) { LottieCompositionFactory.a(getContext(), this.y0).b(new a(storiesContainer)); } } - public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition){ + public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition) { this.z0 = new LottieDrawable(); this.z0.a(lottieComposition); this.z0.d(-1); @@ -363,7 +361,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The this.z0.stop(); } - private void a(boolean z, boolean z2){ + private void a(boolean z, boolean z2) { k(); this.t0 = z; if (!z2) { @@ -373,7 +371,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } } - private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List list){ + private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List list) { pipelineDraweeControllerBuilder.b(null); pipelineDraweeControllerBuilder.a((Supplier>>) null); if (list != null && !list.isEmpty()) { @@ -390,7 +388,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The pipelineDraweeControllerBuilder.b(null); } - public DataSource> a(@Nullable List list){ + public DataSource> a(@Nullable List list) { ArrayList arrayList = new ArrayList<>(); for (String o : Objects.requireNonNull(list)) { arrayList.add(new Image(-1, -1, o)); @@ -398,15 +396,15 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The return AvatarDataSource.a(arrayList, this.A0, 0); } - public class a implements LottieListener{ + public class a implements LottieListener { final StoriesContainer a; - a(StoriesContainer storiesContainer){ + a(StoriesContainer storiesContainer) { this.a = storiesContainer; } @Override // com.airbnb.lottie.LottieListener - public void a(LottieComposition lottieComposition){ + public void a(LottieComposition lottieComposition) { if (StoryCircleImageView.this.getWidth() > 0) { StoryCircleImageView.this.a(this.a, lottieComposition); } else { @@ -414,15 +412,15 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } } - public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener{ + public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener { final LottieComposition a; - ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition){ + ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition) { this.a = lottieComposition; } @Override // android.view.ViewTreeObserver.OnGlobalLayoutListener - public void onGlobalLayout(){ + public void onGlobalLayout() { StoryCircleImageView.this.getViewTreeObserver().removeOnGlobalLayoutListener(this); a aVar = a.this; StoryCircleImageView.this.a(aVar.a, this.a); @@ -430,12 +428,12 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The } } - public class b implements ValueAnimator.AnimatorUpdateListener{ - b(){ + public class b implements ValueAnimator.AnimatorUpdateListener { + b() { } @Override // android.animation.ValueAnimator.AnimatorUpdateListener - public void onAnimationUpdate(ValueAnimator valueAnimator){ + public void onAnimationUpdate(ValueAnimator valueAnimator) { StoryCircleImageView.this.invalidate(); } } diff --git a/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java b/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java index 137ac48d5..a800144ba 100644 --- a/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java +++ b/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java @@ -1,8 +1,5 @@ package com.vtosters.lite.ui.bottomnavigation; -import static ru.vtosters.lite.utils.Preferences.vkme; -import static ru.vtosters.lite.utils.ThemesUtils.getCSTDock; - import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; @@ -18,7 +15,6 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.view.menu.MenuItemImpl; @@ -26,12 +22,13 @@ import androidx.appcompat.view.menu.MenuView; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.view.ViewCompat; - import com.vk.core.ui.themes.VKThemeHelper; import com.vtosters.lite.R; - import me.grishka.appkit.utils.V; +import static ru.vtosters.lite.utils.Preferences.vkme; +import static ru.vtosters.lite.utils.ThemesUtils.getCSTDock; + public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView { private final TextView mSmallLabel; private final TextView mLargeLabel; @@ -224,7 +221,7 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It int textsize; - if (vkme()){ + if (vkme()) { textsize = 11; } else { textsize = 10; diff --git a/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java b/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java index 61a849862..c1c9d3d3c 100644 --- a/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java +++ b/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java @@ -2,9 +2,9 @@ package ru.vtosters.lite.deviceinfo; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.lite.utils.VersionReader; import static ru.vtosters.lite.deviceinfo.OEMDetector.*; -import static ru.vtosters.lite.utils.About.getBuildNumber; public class Device { private String boardName; @@ -88,7 +88,8 @@ public class Device { public String toDeviceName() { return "Device information: " - + "commit='" + getBuildNumber() + + "commit='" + VersionReader.getVersionBuild() + + "', fullVersionInfo='" + VersionReader.getVersionFull() + "', isMilkshake='" + ThemesUtils.isMilkshake() + "', sdkVersion='" + this.sdkVersion + "', productName='" + this.productName @@ -105,7 +106,8 @@ public class Device { public String forLogging() { return "**" + AndroidUtils.getString("device_info") + ":** " + "\n\n" - + "- Commit: " + getBuildNumber() + "\n" + + "- Commit: " + VersionReader.getVersionBuild() + "\n" + + "- Full Version Information: " + VersionReader.getVersionFull() + "\n" + "- Android SDK: " + this.sdkVersion + "\n" + "- Product: " + this.productName + "\n" + "- Device: " + this.deviceName + "\n" diff --git a/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java b/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java index 03b210c98..835a3e728 100644 --- a/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java +++ b/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java @@ -1,7 +1,6 @@ package ru.vtosters.lite.music; import android.text.TextUtils; -import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -65,7 +64,8 @@ public class VKM3UParser { if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null"); Scanner scanner = new Scanner(mData); String line = scanner.nextLine(); - if (!"#EXTM3U".equals(line)) throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line)); + if (!"#EXTM3U".equals(line)) + throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line)); boolean aes128 = false; String keyUri = ""; while (scanner.hasNextLine()) { @@ -82,7 +82,7 @@ public class VKM3UParser { } else { throw new RuntimeException(String.format("Failed to parse: %s", line)); } - mHeapSize += line.getBytes().length;; + mHeapSize += line.getBytes().length; } } diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java b/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java index 53cda25b2..037986b71 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java +++ b/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java @@ -18,7 +18,9 @@ import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.NetworkUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; public class TracklistInjector { @@ -224,6 +226,7 @@ public class TracklistInjector { } } } + public static JSONObject createVirtualCatalog(List tracks) throws JSONException { var audiosBlock = new JSONObject() .put("id", getRandomId()) diff --git a/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java b/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java index f0ab25622..adbb6637f 100644 --- a/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java +++ b/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java @@ -18,7 +18,7 @@ import ru.vtosters.lite.utils.Preferences; import java.io.IOException; -import static ru.vtosters.lite.feature.groupslist.GroupsCatalogInjector.*; +import static ru.vtosters.lite.feature.groupslist.GroupsCatalogInjector.injectIntoCatalog; import static ru.vtosters.lite.hooks.DateHook.getLocale; import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*; import static ru.vtosters.lite.proxy.ProxyUtils.getApi; @@ -152,7 +152,8 @@ public class CatalogJsonInjector { var useOldAppVer = getBoolValue("useOldAppVer", false); var isUsersCatalog = section.optString("url").equals("https://vk.com/audios" + getUserId() + "?section=" + (useOldAppVer ? "all" : "general")); - if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) return; // early return if not users catalog or no tracks or integration enabled + if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) + return; // early return if not users catalog or no tracks or integration enabled var blocks = section.getJSONArray("blocks"); var noPlaylists = !json.has("playlists"); diff --git a/app/src/main/java/ru/vtosters/lite/ssfs/ImBanner.java b/app/src/main/java/ru/vtosters/lite/ssfs/ImBanner.java index 43b802d81..6ee323557 100644 --- a/app/src/main/java/ru/vtosters/lite/ssfs/ImBanner.java +++ b/app/src/main/java/ru/vtosters/lite/ssfs/ImBanner.java @@ -10,8 +10,6 @@ import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.LifecycleUtils; import ru.vtosters.lite.utils.Preferences; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; - public class ImBanner { private static final String TAG = "ImBanner"; diff --git a/app/src/main/java/ru/vtosters/lite/ssfs/UsersList.java b/app/src/main/java/ru/vtosters/lite/ssfs/UsersList.java index a734d5fc8..030d3b646 100644 --- a/app/src/main/java/ru/vtosters/lite/ssfs/UsersList.java +++ b/app/src/main/java/ru/vtosters/lite/ssfs/UsersList.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.List; import static ru.vtosters.lite.utils.Preferences.dev; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; public class UsersList { private static final String TAG = "UsersList"; diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/SystemInfo.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/SystemInfo.java index 80c34e99c..f664814d9 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/SystemInfo.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/SystemInfo.java @@ -8,9 +8,9 @@ import android.os.Bundle; import com.vtosters.lite.R; import ru.vtosters.lite.deviceinfo.OEMDetector; import ru.vtosters.lite.ui.PreferenceFragmentUtils; -import ru.vtosters.lite.utils.About; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.Preferences; +import ru.vtosters.lite.utils.VersionReader; public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment { @@ -33,7 +33,9 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment { String miuiUiVersionCodeName = OEMDetector.getMiuiUiVersionCode(); String emuiVersionCode = OEMDetector.getEmuiVersionCode(); String packageName = requireContext().getPackageName(); - String commit = About.getBuildNumber(); + String commit = VersionReader.getVersionCommit(); + String branch = VersionReader.getVersionBranch(); + String fullbuildinfo = VersionReader.getVersionFull(); boolean isMiui = OEMDetector.isMIUI(); boolean isFlyme = OEMDetector.isFlyme(); @@ -62,6 +64,18 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment { return false; }); + PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Branch", branch, 0, preference -> { + ((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", branch)); + AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard)); + return false; + }); + + PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Build Info", fullbuildinfo, 0, preference -> { + ((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", fullbuildinfo)); + AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard)); + return false; + }); + PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null); PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null); diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java index 39b275902..bd5d81caa 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java @@ -520,7 +520,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme getPreferenceScreen(), "", requireContext().getString(R.string.menu_about), - Preferences.getBuildName() + " | " + About.getBuildNumber(), + Preferences.getBuildName() + " | " + (Preferences.isValidSignature() ? VersionReader.getVersionBuild() : VersionReader.getVersionFull()), R.drawable.ic_about_outline_28, preference -> { NavigatorUtils.switchFragment(requireContext(), AboutAppFragment.class); diff --git a/app/src/main/java/ru/vtosters/lite/utils/About.java b/app/src/main/java/ru/vtosters/lite/utils/About.java index 576ee8a53..c4ee3ae7a 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/About.java +++ b/app/src/main/java/ru/vtosters/lite/utils/About.java @@ -1,23 +1,14 @@ package ru.vtosters.lite.utils; -import android.content.Context; -import android.content.res.AssetManager; -import android.text.TextUtils; import android.view.View; import com.vtosters.lite.R; -import java.io.IOException; -import java.util.Scanner; - -import static ru.vtosters.lite.utils.AndroidUtils.*; +import static ru.vtosters.lite.utils.AndroidUtils.getApplicationName; +import static ru.vtosters.lite.utils.AndroidUtils.sendToast; import static ru.vtosters.lite.utils.Preferences.devmenu; import static ru.vtosters.lite.utils.Preferences.getBuildName; public class About { - public static String getBuildNumber() { - return getBuild(getGlobalContext(), "version.properties"); - } - public static void injectToToolBar(View view) { view.setOnLongClickListener(v -> { if (devmenu()) { @@ -31,24 +22,10 @@ public class About { } public static String getCommitLink() { - return "https://github.com/vtosters/lite/commit/" + getBuild(getGlobalContext(), "version.properties"); // https://git.maki.su/gdlbo/lite/commit/ github repo - } - - public static String getBuild(Context context, String name) { - try { - final var scanner = new Scanner(context.getAssets().open(name, AssetManager.ACCESS_BUFFER)); - while (scanner.hasNextLine()) { - final var line = scanner.findInLine("VERSION_BUILD=.+"); - if (!TextUtils.isEmpty(line)) - return line.substring(14); - } - } catch (IOException e) { - e.printStackTrace(); - } - return "0000000"; + return "https://github.com/vtosters/lite/commit/" + VersionReader.getVersionCommit(); // https://git.maki.su/gdlbo/lite/commit/ github repo } public static String getAppVersion() { - return getApplicationName() + " " + getBuildName() + " | " + getBuildNumber(); + return getApplicationName() + " " + getBuildName() + " | " + VersionReader.getVersionFull(); } } diff --git a/app/src/main/java/ru/vtosters/lite/utils/AnalyticsHelper.java b/app/src/main/java/ru/vtosters/lite/utils/AnalyticsHelper.java index 9528f98ab..dff026ed7 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/AnalyticsHelper.java +++ b/app/src/main/java/ru/vtosters/lite/utils/AnalyticsHelper.java @@ -23,8 +23,8 @@ public class AnalyticsHelper { @Override public void onPreparedLog(@NonNull Log log, @NonNull String groupName, int flags) { var device = log.getDevice(); - device.setAppVersion(About.getBuildNumber()); - device.setAppBuild(About.getBuildNumber()); + device.setAppVersion(VersionReader.getVersionBuild()); + device.setAppBuild(VersionReader.getVersionBuild()); } }; diff --git a/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java b/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java index 89ed416fc..9b0de444b 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java @@ -2,9 +2,7 @@ package ru.vtosters.lite.utils; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.content.ComponentName; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Build; import com.google.firebase.iid.FirebaseInstanceIdReceiver; import com.google.firebase.iid.FirebaseInstanceIdReceiver2; diff --git a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java index 38d0be563..0f60256a5 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java @@ -18,7 +18,7 @@ import java.util.Arrays; public class IOUtils { public static final int BUFFER_SIZE = 8192; - public static byte[] decodeStream(InputStream encIs,byte[] aesKey) + public static byte[] decodeStream(InputStream encIs, byte[] aesKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException { CipherInputStream cip = new CipherInputStream(encIs, getCipher(aesKey)); return readFully(cip); diff --git a/app/src/main/java/ru/vtosters/lite/utils/NetworkUtils.java b/app/src/main/java/ru/vtosters/lite/utils/NetworkUtils.java index 72e2129d9..0d83bb3ac 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/NetworkUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/NetworkUtils.java @@ -6,7 +6,6 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.Build; import android.telephony.TelephonyManager; -import androidx.core.content.ContextCompat; import static ru.vtosters.lite.utils.Preferences.getBoolValue; diff --git a/app/src/main/java/ru/vtosters/lite/utils/OTAUtils.java b/app/src/main/java/ru/vtosters/lite/utils/OTAUtils.java index f5ae30491..6aae19e1d 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/OTAUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/OTAUtils.java @@ -84,7 +84,7 @@ public class OTAUtils { public boolean isNewVersion() { try { - return !mCommitSHA.startsWith(About.getBuildNumber()); + return !mCommitSHA.startsWith(VersionReader.getVersionBuild()); } catch (Exception e) { return false; } diff --git a/app/src/main/java/ru/vtosters/lite/utils/VersionReader.java b/app/src/main/java/ru/vtosters/lite/utils/VersionReader.java new file mode 100644 index 000000000..455b5012f --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/utils/VersionReader.java @@ -0,0 +1,33 @@ +package ru.vtosters.lite.utils; + +import java.io.IOException; +import java.util.Properties; + +public class VersionReader { + public static String getValue(String key) { + try (var inputStream = AndroidUtils.getGlobalContext().getAssets().open("version.properties")) { + Properties properties = new Properties(); + properties.load(inputStream); + return properties.getProperty(key); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + public static String getVersionBuild() { + return getValue("VERSION_BUILD"); + } + + public static String getVersionBranch() { + return getValue("VERSION_BRANCH"); + } + + public static String getVersionCommit() { + return getValue("VERSION_COMMIT"); + } + + public static String getVersionFull() { + return getValue("VERSION_FULL"); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index 2b068d114..bda706cb2 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,30 +1,30 @@ + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + android:color="#44000000" + android:offset="0.0"/> + android:color="#00000000" + android:offset="1.0"/> + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000"/> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9cb..194b3a0a5 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,170 @@ + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z"/> + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF"/> diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cfe5..bbd3e0212 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index eca70cfe5..bbd3e0212 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file