Update to 3.1.1

This commit is contained in:
DrKLO 2015-07-22 21:56:37 +03:00
parent a93d299484
commit 82f9be238d
422 changed files with 10937 additions and 1587 deletions

View file

@ -73,7 +73,7 @@ android {
defaultConfig {
minSdkVersion 8
targetSdkVersion 22
versionCode 572
versionName "3.0.1"
versionCode 580
versionName "3.1.1"
}
}

View file

@ -161,8 +161,20 @@
<service android:name="org.telegram.android.NotificationsService" android:enabled="true"/>
<service android:name="org.telegram.android.NotificationRepeat" android:exported="false"/>
<service android:name="org.telegram.android.NotificationDelay" android:exported="false"/>
<service android:name="org.telegram.android.VideoEncodingService" android:enabled="true"/>
<service android:name="org.telegram.android.MusicPlayerService" android:exported="true" android:enabled="true"/>
<receiver android:name="org.telegram.android.MusicPlayerReceiver" >
<intent-filter>
<action android:name="org.telegram.android.musicplayer.close" />
<action android:name="org.telegram.android.musicplayer.pause" />
<action android:name="org.telegram.android.musicplayer.next" />
<action android:name="org.telegram.android.musicplayer.play" />
<action android:name="org.telegram.android.musicplayer.previous" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<receiver android:name="org.telegram.android.AppStartReceiver" android:enabled="true">
<intent-filter>

View file

@ -8,7 +8,6 @@
package org.telegram.SQLite;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import java.nio.ByteBuffer;
@ -30,7 +29,7 @@ public class SQLitePreparedStatement {
public SQLitePreparedStatement(SQLiteDatabase db, String sql, boolean finalize) throws SQLiteException {
finalizeAfterQuery = finalize;
sqliteStatementHandle = prepare(db.getSQLiteHandle(), sql);
if (BuildVars.DEBUG_VERSION) {
/*if (BuildVars.DEBUG_VERSION) {
if (hashMap == null) {
hashMap = new HashMap<>();
}
@ -38,7 +37,7 @@ public class SQLitePreparedStatement {
for (HashMap.Entry<SQLitePreparedStatement, String> entry : hashMap.entrySet()) {
FileLog.d("tmessages", "exist entry = " + entry.getValue());
}
}
}*/
}
@ -101,9 +100,9 @@ public class SQLitePreparedStatement {
return;
}
try {
if (BuildVars.DEBUG_VERSION) {
/*if (BuildVars.DEBUG_VERSION) {
hashMap.remove(this);
}
}*/
isFinalized = true;
finalize(sqliteStatementHandle);
} catch (SQLiteException e) {

View file

@ -542,16 +542,6 @@ public class AndroidUtilities {
return 0;
}
public static int getCurrentActionBarHeight() {
if (isTablet()) {
return dp(64);
} else if (ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return dp(48);
} else {
return dp(56);
}
}
public static Point getRealScreenSize() {
Point size = new Point();
try {

View file

@ -66,13 +66,13 @@ public class AnimatorSetProxy {
public void playTogether(ArrayList<Object> items) {
if (View10.NEED_PROXY) {
ArrayList<Animator10> animators = new ArrayList<Animator10>();
ArrayList<Animator10> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator10)obj);
}
((AnimatorSet10) animatorSet).playTogether(animators);
} else {
ArrayList<Animator> animators = new ArrayList<Animator>();
ArrayList<Animator> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator)obj);
}

View file

@ -20,6 +20,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.SparseArray;
import org.telegram.PhoneFormat.PhoneFormat;
@ -330,7 +331,7 @@ public class ContactsController {
ContentResolver cr = ApplicationLoader.applicationContext.getContentResolver();
HashMap<String, Contact> shortContacts = new HashMap<>();
StringBuilder ids = new StringBuilder();
ArrayList<Integer> idsArr = new ArrayList<>();
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projectionPhones, null, null, null);
if (pCur != null) {
if (pCur.getCount() > 0) {
@ -355,10 +356,9 @@ public class ContactsController {
}
Integer id = pCur.getInt(0);
if (ids.length() != 0) {
ids.append(",");
if (!idsArr.contains(id)) {
idsArr.add(id);
}
ids.append(id);
int type = pCur.getInt(2);
Contact contact = contactsMap.get(id);
@ -392,8 +392,9 @@ public class ContactsController {
}
pCur.close();
}
String ids = TextUtils.join(",", idsArr);
pCur = cr.query(ContactsContract.Data.CONTENT_URI, projectionNames, ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " IN (" + ids.toString() + ") AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
pCur = cr.query(ContactsContract.Data.CONTENT_URI, projectionNames, ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " IN (" + ids + ") AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
if (pCur != null && pCur.getCount() > 0) {
while (pCur.moveToNext()) {
int id = pCur.getInt(0);
@ -474,6 +475,23 @@ public class ContactsController {
FileLog.e("tmessages", e);
contactsMap.clear();
}
if (BuildVars.DEBUG_VERSION) {
for (HashMap.Entry<Integer, Contact> entry : contactsMap.entrySet()) {
Contact contact = entry.getValue();
FileLog.e("tmessages", "contact = " + contact.first_name + " " + contact.last_name);
if (contact.first_name.length() == 0 && contact.last_name.length() == 0 && contact.phones.size() > 0) {
FileLog.e("tmessages", "warning, empty name for contact = " + contact.id);
}
FileLog.e("tmessages", "phones:");
for (String s : contact.phones) {
FileLog.e("tmessages", "phone = " + s);
}
FileLog.e("tmessages", "short phones:");
for (String s : contact.shortPhones) {
FileLog.e("tmessages", "short phone = " + s);
}
}
}
return contactsMap;
}
@ -569,7 +587,7 @@ public class ContactsController {
}
}
boolean nameChanged = existing != null && (!existing.first_name.equals(value.first_name) || !existing.last_name.equals(value.last_name));
boolean nameChanged = existing != null && (value.first_name.length() != 0 && !existing.first_name.equals(value.first_name) || value.last_name != null && !existing.last_name.equals(value.last_name));
if (existing == null || nameChanged) {
for (int a = 0; a < value.phones.size(); a++) {
String sphone = value.shortPhones.get(a);
@ -607,8 +625,12 @@ public class ContactsController {
int index = existing.shortPhones.indexOf(sphone);
if (index == -1) {
if (request) {
if (contactsByPhone.containsKey(sphone)) {
continue;
TLRPC.TL_contact contact = contactsByPhone.get(sphone);
if (contact != null) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
if (user == null || user.first_name != null && user.first_name.length() != 0 || user.last_name != null && user.last_name.length() != 0) {
continue;
}
}
TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact();
@ -702,8 +724,12 @@ public class ContactsController {
int id = pair.getKey();
for (int a = 0; a < value.phones.size(); a++) {
String phone = value.shortPhones.get(a);
if (contactsByPhone.containsKey(phone)) {
continue;
TLRPC.TL_contact contact = contactsByPhone.get(phone);
if (contact != null) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
if (user == null || user.first_name != null && user.first_name.length() != 0 || user.last_name != null && user.last_name.length() != 0) {
continue;
}
}
TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact();
imp.client_id = id;
@ -721,9 +747,9 @@ public class ContactsController {
if (!toImport.isEmpty()) {
if (BuildVars.DEBUG_VERSION) {
FileLog.e("tmessages", "start import contacts");
// for (TLRPC.TL_inputPhoneContact contact : toImport) {
// FileLog.e("tmessages", "add contact " + contact.first_name + " " + contact.last_name + " " + contact.phone);
// }
for (TLRPC.TL_inputPhoneContact contact : toImport) {
FileLog.e("tmessages", "add contact " + contact.first_name + " " + contact.last_name + " " + contact.phone);
}
}
final int count = (int)Math.ceil(toImport.size() / 500.0f);
for (int a = 0; a < count; a++) {
@ -743,9 +769,9 @@ public class ContactsController {
}
TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts)response;
if (BuildVars.DEBUG_VERSION) {
// for (TLRPC.User user : res.users) {
// FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone);
// }
for (TLRPC.User user : res.users) {
FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone);
}
}
MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true);
ArrayList<TLRPC.TL_contact> cArr = new ArrayList<>();
@ -903,9 +929,9 @@ public class ContactsController {
if (user != null) {
usersDict.put(user.id, user);
// if (BuildVars.DEBUG_VERSION) {
// FileLog.e("tmessages", "loaded user contact " + user.first_name + " " + user.last_name + " " + user.phone);
// }
if (BuildVars.DEBUG_VERSION) {
FileLog.e("tmessages", "loaded user contact " + user.first_name + " " + user.last_name + " " + user.phone);
}
}
}
@ -1524,9 +1550,9 @@ public class ContactsController {
contactsParams.add(c);
req.contacts = contactsParams;
req.replace = false;
// if (BuildVars.DEBUG_VERSION) {
// FileLog.e("tmessages", "add contact " + user.first_name + " " + user.last_name + " " + user.phone);
// }
if (BuildVars.DEBUG_VERSION) {
FileLog.e("tmessages", "add contact " + user.first_name + " " + user.last_name + " " + user.phone);
}
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
@ -1536,11 +1562,11 @@ public class ContactsController {
final TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts)response;
MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true);
// if (BuildVars.DEBUG_VERSION) {
// for (TLRPC.User user : res.users) {
// FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone);
// }
// }
if (BuildVars.DEBUG_VERSION) {
for (TLRPC.User user : res.users) {
FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone);
}
}
for (final TLRPC.User u : res.users) {
Utilities.phoneBookQueue.postRunnable(new Runnable() {

View file

@ -33,13 +33,13 @@ import org.telegram.messenger.Utilities;
import org.telegram.messenger.ApplicationLoader;
public class Emoji {
private static HashMap<Long, DrawableInfo> rects = new HashMap<>();
private static int drawImgSize;
private static HashMap<Long, DrawableInfo> rects = new HashMap<>();
private static int drawImgSize;
private static int bigImgSize;
private static boolean inited = false;
private static Paint placeholderPaint;
private static Bitmap emojiBmp[] = new Bitmap[5];
private static boolean loadingEmoji[] = new boolean[5];
private static boolean inited = false;
private static Paint placeholderPaint;
private static Bitmap emojiBmp[] = new Bitmap[5];
private static boolean loadingEmoji[] = new boolean[5];
private static final int[] cols = {
13, 10, 15, 10, 14
@ -60,139 +60,144 @@ public class Emoji {
0x2B1C, 0x2B50, 0x2B55, 0x3030, 0x303D, 0x3297, 0x3299
};
public static long[][] data = {
public static long[][] data = {
new long[]
{},
new long[]//189
{0x00000000D83DDE04L, 0x00000000D83DDE03L, 0x00000000D83DDE00L, 0x00000000D83DDE0AL, 0x000000000000263AL, 0x00000000D83DDE09L, 0x00000000D83DDE0DL,
0x00000000D83DDE18L, 0x00000000D83DDE1AL, 0x00000000D83DDE17L, 0x00000000D83DDE19L, 0x00000000D83DDE1CL, 0x00000000D83DDE1DL, 0x00000000D83DDE1BL,
0x00000000D83DDE33L, 0x00000000D83DDE01L, 0x00000000D83DDE14L, 0x00000000D83DDE0CL, 0x00000000D83DDE12L, 0x00000000D83DDE1EL, 0x00000000D83DDE23L,
0x00000000D83DDE22L, 0x00000000D83DDE02L, 0x00000000D83DDE2DL, 0x00000000D83DDE2AL, 0x00000000D83DDE25L, 0x00000000D83DDE30L, 0x00000000D83DDE05L,
0x00000000D83DDE13L, 0x00000000D83DDE29L, 0x00000000D83DDE2BL, 0x00000000D83DDE28L, 0x00000000D83DDE31L, 0x00000000D83DDE20L, 0x00000000D83DDE21L,
0x00000000D83DDE24L, 0x00000000D83DDE16L, 0x00000000D83DDE06L, 0x00000000D83DDE0BL, 0x00000000D83DDE37L, 0x00000000D83DDE0EL, 0x00000000D83DDE34L,
0x00000000D83DDE35L, 0x00000000D83DDE32L, 0x00000000D83DDE1FL, 0x00000000D83DDE26L, 0x00000000D83DDE27L, 0x00000000D83DDE08L, 0x00000000D83DDC7FL,
0x00000000D83DDE2EL, 0x00000000D83DDE2CL, 0x00000000D83DDE10L, 0x00000000D83DDE15L, 0x00000000D83DDE2FL, 0x00000000D83DDE36L, 0x00000000D83DDE07L,
0x00000000D83DDE0FL, 0x00000000D83DDE11L, 0x00000000D83DDC72L, 0x00000000D83DDC73L, 0x00000000D83DDC6EL, 0x00000000D83DDC77L, 0x00000000D83DDC82L,
0x00000000D83DDC76L, 0x00000000D83DDC66L, 0x00000000D83DDC67L, 0x00000000D83DDC68L, 0x00000000D83DDC69L, 0x00000000D83DDC74L, 0x00000000D83DDC75L,
0x00000000D83DDC71L, 0x00000000D83DDC7CL, 0x00000000D83DDC78L, 0x00000000D83DDE3AL, 0x00000000D83DDE38L, 0x00000000D83DDE3BL, 0x00000000D83DDE3DL,
0x00000000D83DDE3CL, 0x00000000D83DDE40L, 0x00000000D83DDE3FL, 0x00000000D83DDE39L, 0x00000000D83DDE3EL, 0x00000000D83DDC79L, 0x00000000D83DDC7AL,
0x00000000D83DDE48L, 0x00000000D83DDE49L, 0x00000000D83DDE4AL, 0x00000000D83DDC80L, 0x00000000D83DDC7DL, 0x00000000D83DDCA9L, 0x00000000D83DDD25L,
0x0000000000002728L, 0x00000000D83CDF1FL, 0x00000000D83DDCABL, 0x00000000D83DDCA5L, 0x00000000D83DDCA2L, 0x00000000D83DDCA6L, 0x00000000D83DDCA7L,
0x00000000D83DDCA4L, 0x00000000D83DDCA8L, 0x00000000D83DDC42L, 0x00000000D83DDC40L, 0x00000000D83DDC43L, 0x00000000D83DDC45L, 0x00000000D83DDC44L,
0x00000000D83DDC4DL, 0x00000000D83DDC4EL, 0x00000000D83DDC4CL, 0x00000000D83DDC4AL, 0x000000000000270AL, 0x000000000000270CL, 0x00000000D83DDC4BL,
0x000000000000270BL, 0x00000000D83DDC50L, 0x00000000D83DDC46L, 0x00000000D83DDC47L, 0x00000000D83DDC49L, 0x00000000D83DDC48L, 0x00000000D83DDE4CL,
0x00000000D83DDE4FL, 0x000000000000261DL, 0x00000000D83DDC4FL, 0x00000000D83DDCAAL, 0x00000000D83DDEB6L, 0x00000000D83CDFC3L, 0x00000000D83DDC83L,
0x00000000D83DDC6BL, 0x00000000D83DDC6AL, 0x00000000D83DDC6CL, 0x00000000D83DDC6DL, 0x00000000D83DDC8FL, 0x00000000D83DDC91L, 0x00000000D83DDC6FL,
0x00000000D83DDE46L, 0x00000000D83DDE45L, 0x00000000D83DDC81L, 0x00000000D83DDE4BL, 0x00000000D83DDC86L, 0x00000000D83DDC87L, 0x00000000D83DDC85L,
0x00000000D83DDC70L, 0x00000000D83DDE4EL, 0x00000000D83DDE4DL, 0x00000000D83DDE47L, 0x00000000D83CDFA9L, 0x00000000D83DDC51L, 0x00000000D83DDC52L,
0x00000000D83DDC5FL, 0x00000000D83DDC5EL, 0x00000000D83DDC61L, 0x00000000D83DDC60L, 0x00000000D83DDC62L, 0x00000000D83DDC55L, 0x00000000D83DDC54L,
0x00000000D83DDC5AL, 0x00000000D83DDC57L, 0x00000000D83CDFBDL, 0x00000000D83DDC56L, 0x00000000D83DDC58L, 0x00000000D83DDC59L, 0x00000000D83DDCBCL,
0x00000000D83DDC5CL, 0x00000000D83DDC5DL, 0x00000000D83DDC5BL, 0x00000000D83DDC53L, 0x00000000D83CDF80L, 0x00000000D83CDF02L, 0x00000000D83DDC84L,
0x00000000D83DDC9BL, 0x00000000D83DDC99L, 0x00000000D83DDC9CL, 0x00000000D83DDC9AL, 0x0000000000002764L, 0x00000000D83DDC94L, 0x00000000D83DDC97L,
0x00000000D83DDC93L, 0x00000000D83DDC95L, 0x00000000D83DDC96L, 0x00000000D83DDC9EL, 0x00000000D83DDC98L, 0x00000000D83DDC8CL, 0x00000000D83DDC8BL,
0x00000000D83DDC8DL, 0x00000000D83DDC8EL, 0x00000000D83DDC64L, 0x00000000D83DDC65L, 0x00000000D83DDCACL, 0x00000000D83DDC63L, 0x00000000D83DDCADL},
{
0x00000000D83DDE04L, 0x00000000D83DDE03L, 0x00000000D83DDE00L, 0x00000000D83DDE0AL, 0x000000000000263AL, 0x00000000D83DDE09L, 0x00000000D83DDE0DL,
0x00000000D83DDE18L, 0x00000000D83DDE1AL, 0x00000000D83DDE17L, 0x00000000D83DDE19L, 0x00000000D83DDE1CL, 0x00000000D83DDE1DL, 0x00000000D83DDE1BL,
0x00000000D83DDE33L, 0x00000000D83DDE01L, 0x00000000D83DDE14L, 0x00000000D83DDE0CL, 0x00000000D83DDE12L, 0x00000000D83DDE1EL, 0x00000000D83DDE23L,
0x00000000D83DDE22L, 0x00000000D83DDE02L, 0x00000000D83DDE2DL, 0x00000000D83DDE2AL, 0x00000000D83DDE25L, 0x00000000D83DDE30L, 0x00000000D83DDE05L,
0x00000000D83DDE13L, 0x00000000D83DDE29L, 0x00000000D83DDE2BL, 0x00000000D83DDE28L, 0x00000000D83DDE31L, 0x00000000D83DDE20L, 0x00000000D83DDE21L,
0x00000000D83DDE24L, 0x00000000D83DDE16L, 0x00000000D83DDE06L, 0x00000000D83DDE0BL, 0x00000000D83DDE37L, 0x00000000D83DDE0EL, 0x00000000D83DDE34L,
0x00000000D83DDE35L, 0x00000000D83DDE32L, 0x00000000D83DDE1FL, 0x00000000D83DDE26L, 0x00000000D83DDE27L, 0x00000000D83DDE08L, 0x00000000D83DDC7FL,
0x00000000D83DDE2EL, 0x00000000D83DDE2CL, 0x00000000D83DDE10L, 0x00000000D83DDE15L, 0x00000000D83DDE2FL, 0x00000000D83DDE36L, 0x00000000D83DDE07L,
0x00000000D83DDE0FL, 0x00000000D83DDE11L, 0x00000000D83DDC72L, 0x00000000D83DDC73L, 0x00000000D83DDC6EL, 0x00000000D83DDC77L, 0x00000000D83DDC82L,
0x00000000D83DDC76L, 0x00000000D83DDC66L, 0x00000000D83DDC67L, 0x00000000D83DDC68L, 0x00000000D83DDC69L, 0x00000000D83DDC74L, 0x00000000D83DDC75L,
0x00000000D83DDC71L, 0x00000000D83DDC7CL, 0x00000000D83DDC78L, 0x00000000D83DDE3AL, 0x00000000D83DDE38L, 0x00000000D83DDE3BL, 0x00000000D83DDE3DL,
0x00000000D83DDE3CL, 0x00000000D83DDE40L, 0x00000000D83DDE3FL, 0x00000000D83DDE39L, 0x00000000D83DDE3EL, 0x00000000D83DDC79L, 0x00000000D83DDC7AL,
0x00000000D83DDE48L, 0x00000000D83DDE49L, 0x00000000D83DDE4AL, 0x00000000D83DDC80L, 0x00000000D83DDC7DL, 0x00000000D83DDCA9L, 0x00000000D83DDD25L,
0x0000000000002728L, 0x00000000D83CDF1FL, 0x00000000D83DDCABL, 0x00000000D83DDCA5L, 0x00000000D83DDCA2L, 0x00000000D83DDCA6L, 0x00000000D83DDCA7L,
0x00000000D83DDCA4L, 0x00000000D83DDCA8L, 0x00000000D83DDC42L, 0x00000000D83DDC40L, 0x00000000D83DDC43L, 0x00000000D83DDC45L, 0x00000000D83DDC44L,
0x00000000D83DDC4DL, 0x00000000D83DDC4EL, 0x00000000D83DDC4CL, 0x00000000D83DDC4AL, 0x000000000000270AL, 0x000000000000270CL, 0x00000000D83DDC4BL,
0x000000000000270BL, 0x00000000D83DDC50L, 0x00000000D83DDC46L, 0x00000000D83DDC47L, 0x00000000D83DDC49L, 0x00000000D83DDC48L, 0x00000000D83DDE4CL,
0x00000000D83DDE4FL, 0x000000000000261DL, 0x00000000D83DDC4FL, 0x00000000D83DDCAAL, 0x00000000D83DDEB6L, 0x00000000D83CDFC3L, 0x00000000D83DDC83L,
0x00000000D83DDC6BL, 0x00000000D83DDC6AL, 0x00000000D83DDC6CL, 0x00000000D83DDC6DL, 0x00000000D83DDC8FL, 0x00000000D83DDC91L, 0x00000000D83DDC6FL,
0x00000000D83DDE46L, 0x00000000D83DDE45L, 0x00000000D83DDC81L, 0x00000000D83DDE4BL, 0x00000000D83DDC86L, 0x00000000D83DDC87L, 0x00000000D83DDC85L,
0x00000000D83DDC70L, 0x00000000D83DDE4EL, 0x00000000D83DDE4DL, 0x00000000D83DDE47L, 0x00000000D83CDFA9L, 0x00000000D83DDC51L, 0x00000000D83DDC52L,
0x00000000D83DDC5FL, 0x00000000D83DDC5EL, 0x00000000D83DDC61L, 0x00000000D83DDC60L, 0x00000000D83DDC62L, 0x00000000D83DDC55L, 0x00000000D83DDC54L,
0x00000000D83DDC5AL, 0x00000000D83DDC57L, 0x00000000D83CDFBDL, 0x00000000D83DDC56L, 0x00000000D83DDC58L, 0x00000000D83DDC59L, 0x00000000D83DDCBCL,
0x00000000D83DDC5CL, 0x00000000D83DDC5DL, 0x00000000D83DDC5BL, 0x00000000D83DDC53L, 0x00000000D83CDF80L, 0x00000000D83CDF02L, 0x00000000D83DDC84L,
0x00000000D83DDC9BL, 0x00000000D83DDC99L, 0x00000000D83DDC9CL, 0x00000000D83DDC9AL, 0x0000000000002764L, 0x00000000D83DDC94L, 0x00000000D83DDC97L,
0x00000000D83DDC93L, 0x00000000D83DDC95L, 0x00000000D83DDC96L, 0x00000000D83DDC9EL, 0x00000000D83DDC98L, 0x00000000D83DDC8CL, 0x00000000D83DDC8BL,
0x00000000D83DDC8DL, 0x00000000D83DDC8EL, 0x00000000D83DDC64L, 0x00000000D83DDC65L, 0x00000000D83DDCACL, 0x00000000D83DDC63L, 0x00000000D83DDCADL},
new long[]//116
{0x00000000D83DDC36L, 0x00000000D83DDC3AL, 0x00000000D83DDC31L, 0x00000000D83DDC2DL, 0x00000000D83DDC39L, 0x00000000D83DDC30L, 0x00000000D83DDC38L, 0x00000000D83DDC2FL,
0x00000000D83DDC28L, 0x00000000D83DDC3BL, 0x00000000D83DDC37L, 0x00000000D83DDC3DL, 0x00000000D83DDC2EL, 0x00000000D83DDC17L, 0x00000000D83DDC35L,
0x00000000D83DDC12L, 0x00000000D83DDC34L, 0x00000000D83DDC11L, 0x00000000D83DDC18L, 0x00000000D83DDC3CL, 0x00000000D83DDC27L, 0x00000000D83DDC26L,
0x00000000D83DDC24L, 0x00000000D83DDC25L, 0x00000000D83DDC23L, 0x00000000D83DDC14L, 0x00000000D83DDC0DL, 0x00000000D83DDC22L, 0x00000000D83DDC1BL,
0x00000000D83DDC1DL, 0x00000000D83DDC1CL, 0x00000000D83DDC1EL, 0x00000000D83DDC0CL, 0x00000000D83DDC19L, 0x00000000D83DDC1AL, 0x00000000D83DDC20L,
0x00000000D83DDC1FL, 0x00000000D83DDC2CL, 0x00000000D83DDC33L, 0x00000000D83DDC0BL, 0x00000000D83DDC04L, 0x00000000D83DDC0FL, 0x00000000D83DDC00L,
0x00000000D83DDC03L, 0x00000000D83DDC05L, 0x00000000D83DDC07L, 0x00000000D83DDC09L, 0x00000000D83DDC0EL, 0x00000000D83DDC10L, 0x00000000D83DDC13L,
0x00000000D83DDC15L, 0x00000000D83DDC16L, 0x00000000D83DDC01L, 0x00000000D83DDC02L, 0x00000000D83DDC32L, 0x00000000D83DDC21L, 0x00000000D83DDC0AL,
0x00000000D83DDC2BL, 0x00000000D83DDC2AL, 0x00000000D83DDC06L, 0x00000000D83DDC08L, 0x00000000D83DDC29L, 0x00000000D83DDC3EL, 0x00000000D83DDC90L,
0x00000000D83CDF38L, 0x00000000D83CDF37L, 0x00000000D83CDF40L, 0x00000000D83CDF39L, 0x00000000D83CDF3BL, 0x00000000D83CDF3AL, 0x00000000D83CDF41L,
0x00000000D83CDF43L, 0x00000000D83CDF42L, 0x00000000D83CDF3FL, 0x00000000D83CDF3EL, 0x00000000D83CDF44L, 0x00000000D83CDF35L, 0x00000000D83CDF34L,
0x00000000D83CDF32L, 0x00000000D83CDF33L, 0x00000000D83CDF30L, 0x00000000D83CDF31L, 0x00000000D83CDF3CL, 0x00000000D83CDF10L, 0x00000000D83CDF1EL,
0x00000000D83CDF1DL, 0x00000000D83CDF1AL, 0x00000000D83CDF11L, 0x00000000D83CDF12L, 0x00000000D83CDF13L, 0x00000000D83CDF14L, 0x00000000D83CDF15L,
0x00000000D83CDF16L, 0x00000000D83CDF17L, 0x00000000D83CDF18L, 0x00000000D83CDF1CL, 0x00000000D83CDF1BL, 0x00000000D83CDF19L, 0x00000000D83CDF0DL,
0x00000000D83CDF0EL, 0x00000000D83CDF0FL, 0x00000000D83CDF0BL, 0x00000000D83CDF0CL, 0x00000000D83CDF20L, 0x0000000000002B50L, 0x0000000000002600L,
0x00000000000026C5L, 0x0000000000002601L, 0x00000000000026A1L, 0x0000000000002614L, 0x0000000000002744L, 0x00000000000026C4L, 0x00000000D83CDF00L,
0x00000000D83CDF01L, 0x00000000D83CDF08L, 0x00000000D83CDF0AL},
{
0x00000000D83DDC36L, 0x00000000D83DDC3AL, 0x00000000D83DDC31L, 0x00000000D83DDC2DL, 0x00000000D83DDC39L, 0x00000000D83DDC30L, 0x00000000D83DDC38L, 0x00000000D83DDC2FL,
0x00000000D83DDC28L, 0x00000000D83DDC3BL, 0x00000000D83DDC37L, 0x00000000D83DDC3DL, 0x00000000D83DDC2EL, 0x00000000D83DDC17L, 0x00000000D83DDC35L,
0x00000000D83DDC12L, 0x00000000D83DDC34L, 0x00000000D83DDC11L, 0x00000000D83DDC18L, 0x00000000D83DDC3CL, 0x00000000D83DDC27L, 0x00000000D83DDC26L,
0x00000000D83DDC24L, 0x00000000D83DDC25L, 0x00000000D83DDC23L, 0x00000000D83DDC14L, 0x00000000D83DDC0DL, 0x00000000D83DDC22L, 0x00000000D83DDC1BL,
0x00000000D83DDC1DL, 0x00000000D83DDC1CL, 0x00000000D83DDC1EL, 0x00000000D83DDC0CL, 0x00000000D83DDC19L, 0x00000000D83DDC1AL, 0x00000000D83DDC20L,
0x00000000D83DDC1FL, 0x00000000D83DDC2CL, 0x00000000D83DDC33L, 0x00000000D83DDC0BL, 0x00000000D83DDC04L, 0x00000000D83DDC0FL, 0x00000000D83DDC00L,
0x00000000D83DDC03L, 0x00000000D83DDC05L, 0x00000000D83DDC07L, 0x00000000D83DDC09L, 0x00000000D83DDC0EL, 0x00000000D83DDC10L, 0x00000000D83DDC13L,
0x00000000D83DDC15L, 0x00000000D83DDC16L, 0x00000000D83DDC01L, 0x00000000D83DDC02L, 0x00000000D83DDC32L, 0x00000000D83DDC21L, 0x00000000D83DDC0AL,
0x00000000D83DDC2BL, 0x00000000D83DDC2AL, 0x00000000D83DDC06L, 0x00000000D83DDC08L, 0x00000000D83DDC29L, 0x00000000D83DDC3EL, 0x00000000D83DDC90L,
0x00000000D83CDF38L, 0x00000000D83CDF37L, 0x00000000D83CDF40L, 0x00000000D83CDF39L, 0x00000000D83CDF3BL, 0x00000000D83CDF3AL, 0x00000000D83CDF41L,
0x00000000D83CDF43L, 0x00000000D83CDF42L, 0x00000000D83CDF3FL, 0x00000000D83CDF3EL, 0x00000000D83CDF44L, 0x00000000D83CDF35L, 0x00000000D83CDF34L,
0x00000000D83CDF32L, 0x00000000D83CDF33L, 0x00000000D83CDF30L, 0x00000000D83CDF31L, 0x00000000D83CDF3CL, 0x00000000D83CDF10L, 0x00000000D83CDF1EL,
0x00000000D83CDF1DL, 0x00000000D83CDF1AL, 0x00000000D83CDF11L, 0x00000000D83CDF12L, 0x00000000D83CDF13L, 0x00000000D83CDF14L, 0x00000000D83CDF15L,
0x00000000D83CDF16L, 0x00000000D83CDF17L, 0x00000000D83CDF18L, 0x00000000D83CDF1CL, 0x00000000D83CDF1BL, 0x00000000D83CDF19L, 0x00000000D83CDF0DL,
0x00000000D83CDF0EL, 0x00000000D83CDF0FL, 0x00000000D83CDF0BL, 0x00000000D83CDF0CL, 0x00000000D83CDF20L, 0x0000000000002B50L, 0x0000000000002600L,
0x00000000000026C5L, 0x0000000000002601L, 0x00000000000026A1L, 0x0000000000002614L, 0x0000000000002744L, 0x00000000000026C4L, 0x00000000D83CDF00L,
0x00000000D83CDF01L, 0x00000000D83CDF08L, 0x00000000D83CDF0AL},
new long[]//230
{0x00000000D83CDF8DL, 0x00000000D83DDC9DL, 0x00000000D83CDF8EL, 0x00000000D83CDF92L, 0x00000000D83CDF93L, 0x00000000D83CDF8FL, 0x00000000D83CDF86L, 0x00000000D83CDF87L,
0x00000000D83CDF90L, 0x00000000D83CDF91L, 0x00000000D83CDF83L, 0x00000000D83DDC7BL, 0x00000000D83CDF85L, 0x00000000D83CDF84L, 0x00000000D83CDF81L,
0x00000000D83CDF8BL, 0x00000000D83CDF89L, 0x00000000D83CDF8AL, 0x00000000D83CDF88L, 0x00000000D83CDF8CL, 0x00000000D83DDD2EL, 0x00000000D83CDFA5L,
0x00000000D83DDCF7L, 0x00000000D83DDCF9L, 0x00000000D83DDCFCL, 0x00000000D83DDCBFL, 0x00000000D83DDCC0L, 0x00000000D83DDCBDL, 0x00000000D83DDCBEL,
0x00000000D83DDCBBL, 0x00000000D83DDCF1L, 0x000000000000260EL, 0x00000000D83DDCDEL, 0x00000000D83DDCDFL, 0x00000000D83DDCE0L, 0x00000000D83DDCE1L,
0x00000000D83DDCFAL, 0x00000000D83DDCFBL, 0x00000000D83DDD0AL, 0x00000000D83DDD09L, 0x00000000D83DDD08L, 0x00000000D83DDD07L, 0x00000000D83DDD14L,
0x00000000D83DDD15L, 0x00000000D83DDCE2L, 0x00000000D83DDCE3L, 0x00000000000023F3L, 0x000000000000231BL, 0x00000000000023F0L, 0x000000000000231AL,
0x00000000D83DDD13L, 0x00000000D83DDD12L, 0x00000000D83DDD0FL, 0x00000000D83DDD10L, 0x00000000D83DDD11L, 0x00000000D83DDD0EL, 0x00000000D83DDCA1L,
0x00000000D83DDD26L, 0x00000000D83DDD06L, 0x00000000D83DDD05L, 0x00000000D83DDD0CL, 0x00000000D83DDD0BL, 0x00000000D83DDD0DL, 0x00000000D83DDEC1L, 0x00000000D83DDEC0L,
0x00000000D83DDEBFL, 0x00000000D83DDEBDL, 0x00000000D83DDD27L, 0x00000000D83DDD29L, 0x00000000D83DDD28L, 0x00000000D83DDEAAL, 0x00000000D83DDEACL,
0x00000000D83DDCA3L, 0x00000000D83DDD2BL, 0x00000000D83DDD2AL, 0x00000000D83DDC8AL, 0x00000000D83DDC89L, 0x00000000D83DDCB0L, 0x00000000D83DDCB4L,
0x00000000D83DDCB5L, 0x00000000D83DDCB7L, 0x00000000D83DDCB6L, 0x00000000D83DDCB3L, 0x00000000D83DDCB8L, 0x00000000D83DDCF2L, 0x00000000D83DDCE7L,
0x00000000D83DDCE5L, 0x00000000D83DDCE4L, 0x0000000000002709L, 0x00000000D83DDCE9L, 0x00000000D83DDCE8L, 0x00000000D83DDCEFL, 0x00000000D83DDCEBL,
0x00000000D83DDCEAL, 0x00000000D83DDCECL, 0x00000000D83DDCEDL, 0x00000000D83DDCEEL, 0x00000000D83DDCE6L, 0x00000000D83DDCDDL, 0x00000000D83DDCC4L,
0x00000000D83DDCC3L, 0x00000000D83DDCD1L, 0x00000000D83DDCCAL, 0x00000000D83DDCC8L, 0x00000000D83DDCC9L, 0x00000000D83DDCDCL, 0x00000000D83DDCCBL,
0x00000000D83DDCC5L, 0x00000000D83DDCC6L, 0x00000000D83DDCC7L, 0x00000000D83DDCC1L, 0x00000000D83DDCC2L, 0x0000000000002702L, 0x00000000D83DDCCCL,
0x00000000D83DDCCEL, 0x0000000000002712L, 0x000000000000270FL, 0x00000000D83DDCCFL, 0x00000000D83DDCD0L, 0x00000000D83DDCD5L, 0x00000000D83DDCD7L,
0x00000000D83DDCD8L, 0x00000000D83DDCD9L, 0x00000000D83DDCD3L, 0x00000000D83DDCD4L, 0x00000000D83DDCD2L, 0x00000000D83DDCDAL, 0x00000000D83DDCD6L,
0x00000000D83DDD16L, 0x00000000D83DDCDBL, 0x00000000D83DDD2CL, 0x00000000D83DDD2DL, 0x00000000D83DDCF0L, 0x00000000D83CDFA8L, 0x00000000D83CDFACL,
0x00000000D83CDFA4L, 0x00000000D83CDFA7L, 0x00000000D83CDFBCL, 0x00000000D83CDFB5L, 0x00000000D83CDFB6L, 0x00000000D83CDFB9L, 0x00000000D83CDFBBL,
0x00000000D83CDFBAL, 0x00000000D83CDFB7L, 0x00000000D83CDFB8L, 0x00000000D83DDC7EL, 0x00000000D83CDFAEL, 0x00000000D83CDCCFL, 0x00000000D83CDFB4L,
0x00000000D83CDC04L, 0x00000000D83CDFB2L, 0x00000000D83CDFAFL, 0x00000000D83CDFC8L, 0x00000000D83CDFC0L, 0x00000000000026BDL, 0x00000000000026BEL,
0x00000000D83CDFBEL, 0x00000000D83CDFB1L, 0x00000000D83CDFC9L, 0x00000000D83CDFB3L, 0x00000000000026F3L, 0x00000000D83DDEB5L, 0x00000000D83DDEB4L,
0x00000000D83CDFC1L, 0x00000000D83CDFC7L, 0x00000000D83CDFC6L, 0x00000000D83CDFBFL, 0x00000000D83CDFC2L, 0x00000000D83CDFCAL, 0x00000000D83CDFC4L,
0x00000000D83CDFA3L, 0x0000000000002615L, 0x00000000D83CDF75L, 0x00000000D83CDF76L, 0x00000000D83CDF7CL, 0x00000000D83CDF7AL, 0x00000000D83CDF7BL,
0x00000000D83CDF78L, 0x00000000D83CDF79L, 0x00000000D83CDF77L, 0x00000000D83CDF74L, 0x00000000D83CDF55L, 0x00000000D83CDF54L, 0x00000000D83CDF5FL,
0x00000000D83CDF57L, 0x00000000D83CDF56L, 0x00000000D83CDF5DL, 0x00000000D83CDF5BL, 0x00000000D83CDF64L, 0x00000000D83CDF71L, 0x00000000D83CDF63L,
0x00000000D83CDF65L, 0x00000000D83CDF59L, 0x00000000D83CDF58L, 0x00000000D83CDF5AL, 0x00000000D83CDF5CL, 0x00000000D83CDF72L, 0x00000000D83CDF62L,
0x00000000D83CDF61L, 0x00000000D83CDF73L, 0x00000000D83CDF5EL, 0x00000000D83CDF69L, 0x00000000D83CDF6EL, 0x00000000D83CDF66L, 0x00000000D83CDF68L,
0x00000000D83CDF67L, 0x00000000D83CDF82L, 0x00000000D83CDF70L, 0x00000000D83CDF6AL, 0x00000000D83CDF6BL, 0x00000000D83CDF6CL, 0x00000000D83CDF6DL,
0x00000000D83CDF6FL, 0x00000000D83CDF4EL, 0x00000000D83CDF4FL, 0x00000000D83CDF4AL, 0x00000000D83CDF4BL, 0x00000000D83CDF52L, 0x00000000D83CDF47L,
0x00000000D83CDF49L, 0x00000000D83CDF53L, 0x00000000D83CDF51L, 0x00000000D83CDF48L, 0x00000000D83CDF4CL, 0x00000000D83CDF50L, 0x00000000D83CDF4DL,
0x00000000D83CDF60L, 0x00000000D83CDF46L, 0x00000000D83CDF45L, 0x00000000D83CDF3DL},
{
0x00000000D83CDF8DL, 0x00000000D83DDC9DL, 0x00000000D83CDF8EL, 0x00000000D83CDF92L, 0x00000000D83CDF93L, 0x00000000D83CDF8FL, 0x00000000D83CDF86L, 0x00000000D83CDF87L,
0x00000000D83CDF90L, 0x00000000D83CDF91L, 0x00000000D83CDF83L, 0x00000000D83DDC7BL, 0x00000000D83CDF85L, 0x00000000D83CDF84L, 0x00000000D83CDF81L,
0x00000000D83CDF8BL, 0x00000000D83CDF89L, 0x00000000D83CDF8AL, 0x00000000D83CDF88L, 0x00000000D83CDF8CL, 0x00000000D83DDD2EL, 0x00000000D83CDFA5L,
0x00000000D83DDCF7L, 0x00000000D83DDCF9L, 0x00000000D83DDCFCL, 0x00000000D83DDCBFL, 0x00000000D83DDCC0L, 0x00000000D83DDCBDL, 0x00000000D83DDCBEL,
0x00000000D83DDCBBL, 0x00000000D83DDCF1L, 0x000000000000260EL, 0x00000000D83DDCDEL, 0x00000000D83DDCDFL, 0x00000000D83DDCE0L, 0x00000000D83DDCE1L,
0x00000000D83DDCFAL, 0x00000000D83DDCFBL, 0x00000000D83DDD0AL, 0x00000000D83DDD09L, 0x00000000D83DDD08L, 0x00000000D83DDD07L, 0x00000000D83DDD14L,
0x00000000D83DDD15L, 0x00000000D83DDCE2L, 0x00000000D83DDCE3L, 0x00000000000023F3L, 0x000000000000231BL, 0x00000000000023F0L, 0x000000000000231AL,
0x00000000D83DDD13L, 0x00000000D83DDD12L, 0x00000000D83DDD0FL, 0x00000000D83DDD10L, 0x00000000D83DDD11L, 0x00000000D83DDD0EL, 0x00000000D83DDCA1L,
0x00000000D83DDD26L, 0x00000000D83DDD06L, 0x00000000D83DDD05L, 0x00000000D83DDD0CL, 0x00000000D83DDD0BL, 0x00000000D83DDD0DL, 0x00000000D83DDEC1L, 0x00000000D83DDEC0L,
0x00000000D83DDEBFL, 0x00000000D83DDEBDL, 0x00000000D83DDD27L, 0x00000000D83DDD29L, 0x00000000D83DDD28L, 0x00000000D83DDEAAL, 0x00000000D83DDEACL,
0x00000000D83DDCA3L, 0x00000000D83DDD2BL, 0x00000000D83DDD2AL, 0x00000000D83DDC8AL, 0x00000000D83DDC89L, 0x00000000D83DDCB0L, 0x00000000D83DDCB4L,
0x00000000D83DDCB5L, 0x00000000D83DDCB7L, 0x00000000D83DDCB6L, 0x00000000D83DDCB3L, 0x00000000D83DDCB8L, 0x00000000D83DDCF2L, 0x00000000D83DDCE7L,
0x00000000D83DDCE5L, 0x00000000D83DDCE4L, 0x0000000000002709L, 0x00000000D83DDCE9L, 0x00000000D83DDCE8L, 0x00000000D83DDCEFL, 0x00000000D83DDCEBL,
0x00000000D83DDCEAL, 0x00000000D83DDCECL, 0x00000000D83DDCEDL, 0x00000000D83DDCEEL, 0x00000000D83DDCE6L, 0x00000000D83DDCDDL, 0x00000000D83DDCC4L,
0x00000000D83DDCC3L, 0x00000000D83DDCD1L, 0x00000000D83DDCCAL, 0x00000000D83DDCC8L, 0x00000000D83DDCC9L, 0x00000000D83DDCDCL, 0x00000000D83DDCCBL,
0x00000000D83DDCC5L, 0x00000000D83DDCC6L, 0x00000000D83DDCC7L, 0x00000000D83DDCC1L, 0x00000000D83DDCC2L, 0x0000000000002702L, 0x00000000D83DDCCCL,
0x00000000D83DDCCEL, 0x0000000000002712L, 0x000000000000270FL, 0x00000000D83DDCCFL, 0x00000000D83DDCD0L, 0x00000000D83DDCD5L, 0x00000000D83DDCD7L,
0x00000000D83DDCD8L, 0x00000000D83DDCD9L, 0x00000000D83DDCD3L, 0x00000000D83DDCD4L, 0x00000000D83DDCD2L, 0x00000000D83DDCDAL, 0x00000000D83DDCD6L,
0x00000000D83DDD16L, 0x00000000D83DDCDBL, 0x00000000D83DDD2CL, 0x00000000D83DDD2DL, 0x00000000D83DDCF0L, 0x00000000D83CDFA8L, 0x00000000D83CDFACL,
0x00000000D83CDFA4L, 0x00000000D83CDFA7L, 0x00000000D83CDFBCL, 0x00000000D83CDFB5L, 0x00000000D83CDFB6L, 0x00000000D83CDFB9L, 0x00000000D83CDFBBL,
0x00000000D83CDFBAL, 0x00000000D83CDFB7L, 0x00000000D83CDFB8L, 0x00000000D83DDC7EL, 0x00000000D83CDFAEL, 0x00000000D83CDCCFL, 0x00000000D83CDFB4L,
0x00000000D83CDC04L, 0x00000000D83CDFB2L, 0x00000000D83CDFAFL, 0x00000000D83CDFC8L, 0x00000000D83CDFC0L, 0x00000000000026BDL, 0x00000000000026BEL,
0x00000000D83CDFBEL, 0x00000000D83CDFB1L, 0x00000000D83CDFC9L, 0x00000000D83CDFB3L, 0x00000000000026F3L, 0x00000000D83DDEB5L, 0x00000000D83DDEB4L,
0x00000000D83CDFC1L, 0x00000000D83CDFC7L, 0x00000000D83CDFC6L, 0x00000000D83CDFBFL, 0x00000000D83CDFC2L, 0x00000000D83CDFCAL, 0x00000000D83CDFC4L,
0x00000000D83CDFA3L, 0x0000000000002615L, 0x00000000D83CDF75L, 0x00000000D83CDF76L, 0x00000000D83CDF7CL, 0x00000000D83CDF7AL, 0x00000000D83CDF7BL,
0x00000000D83CDF78L, 0x00000000D83CDF79L, 0x00000000D83CDF77L, 0x00000000D83CDF74L, 0x00000000D83CDF55L, 0x00000000D83CDF54L, 0x00000000D83CDF5FL,
0x00000000D83CDF57L, 0x00000000D83CDF56L, 0x00000000D83CDF5DL, 0x00000000D83CDF5BL, 0x00000000D83CDF64L, 0x00000000D83CDF71L, 0x00000000D83CDF63L,
0x00000000D83CDF65L, 0x00000000D83CDF59L, 0x00000000D83CDF58L, 0x00000000D83CDF5AL, 0x00000000D83CDF5CL, 0x00000000D83CDF72L, 0x00000000D83CDF62L,
0x00000000D83CDF61L, 0x00000000D83CDF73L, 0x00000000D83CDF5EL, 0x00000000D83CDF69L, 0x00000000D83CDF6EL, 0x00000000D83CDF66L, 0x00000000D83CDF68L,
0x00000000D83CDF67L, 0x00000000D83CDF82L, 0x00000000D83CDF70L, 0x00000000D83CDF6AL, 0x00000000D83CDF6BL, 0x00000000D83CDF6CL, 0x00000000D83CDF6DL,
0x00000000D83CDF6FL, 0x00000000D83CDF4EL, 0x00000000D83CDF4FL, 0x00000000D83CDF4AL, 0x00000000D83CDF4BL, 0x00000000D83CDF52L, 0x00000000D83CDF47L,
0x00000000D83CDF49L, 0x00000000D83CDF53L, 0x00000000D83CDF51L, 0x00000000D83CDF48L, 0x00000000D83CDF4CL, 0x00000000D83CDF50L, 0x00000000D83CDF4DL,
0x00000000D83CDF60L, 0x00000000D83CDF46L, 0x00000000D83CDF45L, 0x00000000D83CDF3DL},
new long[]//101
{0x00000000D83CDFE0L, 0x00000000D83CDFE1L, 0x00000000D83CDFEBL, 0x00000000D83CDFE2L, 0x00000000D83CDFE3L, 0x00000000D83CDFE5L, 0x00000000D83CDFE6L, 0x00000000D83CDFEAL,
0x00000000D83CDFE9L, 0x00000000D83CDFE8L, 0x00000000D83DDC92L, 0x00000000000026EAL, 0x00000000D83CDFECL, 0x00000000D83CDFE4L, 0x00000000D83CDF07L,
0x00000000D83CDF06L, 0x00000000D83CDFEFL, 0x00000000D83CDFF0L, 0x00000000000026FAL, 0x00000000D83CDFEDL, 0x00000000D83DDDFCL, 0x00000000D83DDDFEL,
0x00000000D83DDDFBL, 0x00000000D83CDF04L, 0x00000000D83CDF05L, 0x00000000D83CDF03L, 0x00000000D83DDDFDL, 0x00000000D83CDF09L, 0x00000000D83CDFA0L,
0x00000000D83CDFA1L, 0x00000000000026F2L, 0x00000000D83CDFA2L, 0x00000000D83DDEA2L, 0x00000000000026F5L, 0x00000000D83DDEA4L, 0x00000000D83DDEA3L,
0x0000000000002693L, 0x00000000D83DDE80L, 0x0000000000002708L, 0x00000000D83DDCBAL, 0x00000000D83DDE81L, 0x00000000D83DDE82L, 0x00000000D83DDE8AL,
0x00000000D83DDE89L, 0x00000000D83DDE9EL, 0x00000000D83DDE86L, 0x00000000D83DDE84L, 0x00000000D83DDE85L, 0x00000000D83DDE88L, 0x00000000D83DDE87L,
0x00000000D83DDE9DL, 0x00000000D83DDE8BL, 0x00000000D83DDE83L, 0x00000000D83DDE8EL, 0x00000000D83DDE8CL, 0x00000000D83DDE8DL, 0x00000000D83DDE99L,
0x00000000D83DDE98L, 0x00000000D83DDE97L, 0x00000000D83DDE95L, 0x00000000D83DDE96L, 0x00000000D83DDE9BL, 0x00000000D83DDE9AL, 0x00000000D83DDEA8L,
0x00000000D83DDE93L, 0x00000000D83DDE94L, 0x00000000D83DDE92L, 0x00000000D83DDE91L, 0x00000000D83DDE90L, 0x00000000D83DDEB2L, 0x00000000D83DDEA1L,
0x00000000D83DDE9FL, 0x00000000D83DDEA0L, 0x00000000D83DDE9CL, 0x00000000D83DDC88L, 0x00000000D83DDE8FL, 0x00000000D83CDFABL, 0x00000000D83DDEA6L,
0x00000000D83DDEA5L, 0x00000000000026A0L, 0x00000000D83DDEA7L, 0x00000000D83DDD30L, 0x00000000000026FDL, 0x00000000D83CDFEEL, 0x00000000D83CDFB0L,
0x0000000000002668L, 0x00000000D83DDDFFL, 0x00000000D83CDFAAL, 0x00000000D83CDFADL, 0x00000000D83DDCCDL, 0x00000000D83DDEA9L, 0xD83CDDEFD83CDDF5L,
0xD83CDDF0D83CDDF7L, 0xD83CDDE9D83CDDEAL, 0xD83CDDE8D83CDDF3L, 0xD83CDDFAD83CDDF8L, 0xD83CDDEBD83CDDF7L, 0xD83CDDEAD83CDDF8L, 0xD83CDDEED83CDDF9L,
0xD83CDDF7D83CDDFAL, 0xD83CDDECD83CDDE7L},
{
0x00000000D83CDFE0L, 0x00000000D83CDFE1L, 0x00000000D83CDFEBL, 0x00000000D83CDFE2L, 0x00000000D83CDFE3L, 0x00000000D83CDFE5L, 0x00000000D83CDFE6L, 0x00000000D83CDFEAL,
0x00000000D83CDFE9L, 0x00000000D83CDFE8L, 0x00000000D83DDC92L, 0x00000000000026EAL, 0x00000000D83CDFECL, 0x00000000D83CDFE4L, 0x00000000D83CDF07L,
0x00000000D83CDF06L, 0x00000000D83CDFEFL, 0x00000000D83CDFF0L, 0x00000000000026FAL, 0x00000000D83CDFEDL, 0x00000000D83DDDFCL, 0x00000000D83DDDFEL,
0x00000000D83DDDFBL, 0x00000000D83CDF04L, 0x00000000D83CDF05L, 0x00000000D83CDF03L, 0x00000000D83DDDFDL, 0x00000000D83CDF09L, 0x00000000D83CDFA0L,
0x00000000D83CDFA1L, 0x00000000000026F2L, 0x00000000D83CDFA2L, 0x00000000D83DDEA2L, 0x00000000000026F5L, 0x00000000D83DDEA4L, 0x00000000D83DDEA3L,
0x0000000000002693L, 0x00000000D83DDE80L, 0x0000000000002708L, 0x00000000D83DDCBAL, 0x00000000D83DDE81L, 0x00000000D83DDE82L, 0x00000000D83DDE8AL,
0x00000000D83DDE89L, 0x00000000D83DDE9EL, 0x00000000D83DDE86L, 0x00000000D83DDE84L, 0x00000000D83DDE85L, 0x00000000D83DDE88L, 0x00000000D83DDE87L,
0x00000000D83DDE9DL, 0x00000000D83DDE8BL, 0x00000000D83DDE83L, 0x00000000D83DDE8EL, 0x00000000D83DDE8CL, 0x00000000D83DDE8DL, 0x00000000D83DDE99L,
0x00000000D83DDE98L, 0x00000000D83DDE97L, 0x00000000D83DDE95L, 0x00000000D83DDE96L, 0x00000000D83DDE9BL, 0x00000000D83DDE9AL, 0x00000000D83DDEA8L,
0x00000000D83DDE93L, 0x00000000D83DDE94L, 0x00000000D83DDE92L, 0x00000000D83DDE91L, 0x00000000D83DDE90L, 0x00000000D83DDEB2L, 0x00000000D83DDEA1L,
0x00000000D83DDE9FL, 0x00000000D83DDEA0L, 0x00000000D83DDE9CL, 0x00000000D83DDC88L, 0x00000000D83DDE8FL, 0x00000000D83CDFABL, 0x00000000D83DDEA6L,
0x00000000D83DDEA5L, 0x00000000000026A0L, 0x00000000D83DDEA7L, 0x00000000D83DDD30L, 0x00000000000026FDL, 0x00000000D83CDFEEL, 0x00000000D83CDFB0L,
0x0000000000002668L, 0x00000000D83DDDFFL, 0x00000000D83CDFAAL, 0x00000000D83CDFADL, 0x00000000D83DDCCDL, 0x00000000D83DDEA9L, 0xD83CDDEFD83CDDF5L,
0xD83CDDF0D83CDDF7L, 0xD83CDDE9D83CDDEAL, 0xD83CDDE8D83CDDF3L, 0xD83CDDFAD83CDDF8L, 0xD83CDDEBD83CDDF7L, 0xD83CDDEAD83CDDF8L, 0xD83CDDEED83CDDF9L,
0xD83CDDF7D83CDDFAL, 0xD83CDDECD83CDDE7L},
new long[]//209
{0x00000000003120E3L, 0x00000000003220E3L, 0x00000000003320E3L, 0x00000000003420E3L, 0x00000000003520E3L, 0x00000000003620E3L, 0x00000000003720E3L,
0x00000000003820E3L, 0x00000000003920E3L, 0x00000000003020E3L, 0x00000000D83DDD1FL, 0x00000000D83DDD22L, 0x00000000002320E3L, 0x00000000D83DDD23L,
0x0000000000002B06L, 0x0000000000002B07L, 0x0000000000002B05L, 0x00000000000027A1L, 0x00000000D83DDD20L, 0x00000000D83DDD21L, 0x00000000D83DDD24L,
0x0000000000002197L, 0x0000000000002196L, 0x0000000000002198L, 0x0000000000002199L, 0x0000000000002194L, 0x0000000000002195L, 0x00000000D83DDD04L,
0x00000000000025C0L, 0x00000000000025B6L, 0x00000000D83DDD3CL, 0x00000000D83DDD3DL, 0x00000000000021A9L, 0x00000000000021AAL, 0x0000000000002139L,
0x00000000000023EAL, 0x00000000000023E9L, 0x00000000000023EBL, 0x00000000000023ECL, 0x0000000000002935L, 0x0000000000002934L, 0x00000000D83CDD97L,
0x00000000D83DDD00L, 0x00000000D83DDD01L, 0x00000000D83DDD02L, 0x00000000D83CDD95L, 0x00000000D83CDD99L, 0x00000000D83CDD92L, 0x00000000D83CDD93L,
0x00000000D83CDD96L, 0x00000000D83DDCF6L, 0x00000000D83CDFA6L, 0x00000000D83CDE01L, 0x00000000D83CDE2FL, 0x00000000D83CDE33L, 0x00000000D83CDE35L,
0x00000000D83CDE32L, 0x00000000D83CDE34L, 0x00000000D83CDE50L, 0x00000000D83CDE39L, 0x00000000D83CDE3AL, 0x00000000D83CDE36L, 0x00000000D83CDE1AL,
0x00000000D83DDEBBL, 0x00000000D83DDEB9L, 0x00000000D83DDEBAL, 0x00000000D83DDEBCL, 0x00000000D83DDEBEL, 0x00000000D83DDEB0L, 0x00000000D83DDEAEL,
0x00000000D83CDD7FL, 0x000000000000267FL, 0x00000000D83DDEADL, 0x00000000D83CDE37L, 0x00000000D83CDE38L, 0x00000000D83CDE02L, 0x00000000000024C2L,
0x00000000D83DDEC2L, 0x00000000D83DDEC4L, 0x00000000D83DDEC5L, 0x00000000D83DDEC3L, 0x00000000D83CDE51L, 0x0000000000003299L, 0x0000000000003297L,
0x00000000D83CDD91L, 0x00000000D83CDD98L, 0x00000000D83CDD94L, 0x00000000D83DDEABL,
0x00000000D83DDD1EL, 0x00000000D83DDCF5L, 0x00000000D83DDEAFL, 0x00000000D83DDEB1L, 0x00000000D83DDEB3L, 0x00000000D83DDEB7L, 0x00000000D83DDEB8L,
0x00000000000026D4L, 0x0000000000002733L, 0x0000000000002747L, 0x000000000000274EL, 0x0000000000002705L, 0x0000000000002734L, 0x00000000D83DDC9FL,
0x00000000D83CDD9AL, 0x00000000D83DDCF3L, 0x00000000D83DDCF4L, 0x00000000D83CDD70L, 0x00000000D83CDD71L, 0x00000000D83CDD8EL, 0x00000000D83CDD7EL,
0x00000000D83DDCA0L, 0x00000000000027BFL, 0x000000000000267BL, 0x0000000000002648L, 0x0000000000002649L, 0x000000000000264AL, 0x000000000000264BL,
0x000000000000264CL, 0x000000000000264DL, 0x000000000000264EL, 0x000000000000264FL, 0x0000000000002650L, 0x0000000000002651L, 0x0000000000002652L,
0x0000000000002653L, 0x00000000000026CEL, 0x00000000D83DDD2FL, 0x00000000D83CDFE7L, 0x00000000D83DDCB9L, 0x00000000D83DDCB2L, 0x00000000D83DDCB1L,
0x00000000000000A9L, 0x00000000000000AEL, 0x0000000000002122L, 0x000000000000303DL, 0x0000000000003030L, 0x00000000D83DDD1DL, 0x00000000D83DDD1AL,
0x00000000D83DDD19L, 0x00000000D83DDD1BL, 0x00000000D83DDD1CL, 0x000000000000274CL, 0x0000000000002B55L, 0x0000000000002757L, 0x000000000000203CL,
0x0000000000002049L, 0x0000000000002753L,
0x0000000000002755L, 0x0000000000002754L, 0x00000000D83DDD03L, 0x00000000D83DDD5BL, 0x00000000D83DDD67L, 0x00000000D83DDD50L, 0x00000000D83DDD5CL,
0x00000000D83DDD51L, 0x00000000D83DDD5DL, 0x00000000D83DDD52L, 0x00000000D83DDD5EL, 0x00000000D83DDD53L, 0x00000000D83DDD5FL, 0x00000000D83DDD54L,
0x00000000D83DDD60L, 0x00000000D83DDD55L, 0x00000000D83DDD56L, 0x00000000D83DDD57L, 0x00000000D83DDD58L, 0x00000000D83DDD59L, 0x00000000D83DDD5AL,
0x00000000D83DDD61L, 0x00000000D83DDD62L, 0x00000000D83DDD63L, 0x00000000D83DDD64L, 0x00000000D83DDD65L, 0x00000000D83DDD66L, 0x0000000000002716L,
0x0000000000002795L, 0x0000000000002796L, 0x0000000000002797L, 0x0000000000002660L, 0x0000000000002665L, 0x0000000000002663L, 0x0000000000002666L,
0x00000000D83DDCAEL, 0x00000000D83DDCAFL, 0x0000000000002714L, 0x0000000000002611L, 0x00000000D83DDD18L, 0x00000000D83DDD17L, 0x00000000000027B0L,
0x00000000D83DDD31L, 0x00000000D83DDD32L, 0x00000000D83DDD33L, 0x00000000000025FCL, 0x00000000000025FBL, 0x00000000000025FEL, 0x00000000000025FDL,
0x00000000000025AAL, 0x00000000000025ABL, 0x00000000D83DDD3AL, 0x0000000000002B1CL, 0x0000000000002B1BL, 0x00000000000026ABL, 0x00000000000026AAL,
0x00000000D83DDD34L, 0x00000000D83DDD35L, 0x00000000D83DDD3BL, 0x00000000D83DDD36L, 0x00000000D83DDD37L, 0x00000000D83DDD38L, 0x00000000D83DDD39L}};
static {
{
0x00000000003120E3L, 0x00000000003220E3L, 0x00000000003320E3L, 0x00000000003420E3L, 0x00000000003520E3L, 0x00000000003620E3L, 0x00000000003720E3L,
0x00000000003820E3L, 0x00000000003920E3L, 0x00000000003020E3L, 0x00000000D83DDD1FL, 0x00000000D83DDD22L, 0x00000000002320E3L, 0x00000000D83DDD23L,
0x0000000000002B06L, 0x0000000000002B07L, 0x0000000000002B05L, 0x00000000000027A1L, 0x00000000D83DDD20L, 0x00000000D83DDD21L, 0x00000000D83DDD24L,
0x0000000000002197L, 0x0000000000002196L, 0x0000000000002198L, 0x0000000000002199L, 0x0000000000002194L, 0x0000000000002195L, 0x00000000D83DDD04L,
0x00000000000025C0L, 0x00000000000025B6L, 0x00000000D83DDD3CL, 0x00000000D83DDD3DL, 0x00000000000021A9L, 0x00000000000021AAL, 0x0000000000002139L,
0x00000000000023EAL, 0x00000000000023E9L, 0x00000000000023EBL, 0x00000000000023ECL, 0x0000000000002935L, 0x0000000000002934L, 0x00000000D83CDD97L,
0x00000000D83DDD00L, 0x00000000D83DDD01L, 0x00000000D83DDD02L, 0x00000000D83CDD95L, 0x00000000D83CDD99L, 0x00000000D83CDD92L, 0x00000000D83CDD93L,
0x00000000D83CDD96L, 0x00000000D83DDCF6L, 0x00000000D83CDFA6L, 0x00000000D83CDE01L, 0x00000000D83CDE2FL, 0x00000000D83CDE33L, 0x00000000D83CDE35L,
0x00000000D83CDE32L, 0x00000000D83CDE34L, 0x00000000D83CDE50L, 0x00000000D83CDE39L, 0x00000000D83CDE3AL, 0x00000000D83CDE36L, 0x00000000D83CDE1AL,
0x00000000D83DDEBBL, 0x00000000D83DDEB9L, 0x00000000D83DDEBAL, 0x00000000D83DDEBCL, 0x00000000D83DDEBEL, 0x00000000D83DDEB0L, 0x00000000D83DDEAEL,
0x00000000D83CDD7FL, 0x000000000000267FL, 0x00000000D83DDEADL, 0x00000000D83CDE37L, 0x00000000D83CDE38L, 0x00000000D83CDE02L, 0x00000000000024C2L,
0x00000000D83DDEC2L, 0x00000000D83DDEC4L, 0x00000000D83DDEC5L, 0x00000000D83DDEC3L, 0x00000000D83CDE51L, 0x0000000000003299L, 0x0000000000003297L,
0x00000000D83CDD91L, 0x00000000D83CDD98L, 0x00000000D83CDD94L, 0x00000000D83DDEABL,
0x00000000D83DDD1EL, 0x00000000D83DDCF5L, 0x00000000D83DDEAFL, 0x00000000D83DDEB1L, 0x00000000D83DDEB3L, 0x00000000D83DDEB7L, 0x00000000D83DDEB8L,
0x00000000000026D4L, 0x0000000000002733L, 0x0000000000002747L, 0x000000000000274EL, 0x0000000000002705L, 0x0000000000002734L, 0x00000000D83DDC9FL,
0x00000000D83CDD9AL, 0x00000000D83DDCF3L, 0x00000000D83DDCF4L, 0x00000000D83CDD70L, 0x00000000D83CDD71L, 0x00000000D83CDD8EL, 0x00000000D83CDD7EL,
0x00000000D83DDCA0L, 0x00000000000027BFL, 0x000000000000267BL, 0x0000000000002648L, 0x0000000000002649L, 0x000000000000264AL, 0x000000000000264BL,
0x000000000000264CL, 0x000000000000264DL, 0x000000000000264EL, 0x000000000000264FL, 0x0000000000002650L, 0x0000000000002651L, 0x0000000000002652L,
0x0000000000002653L, 0x00000000000026CEL, 0x00000000D83DDD2FL, 0x00000000D83CDFE7L, 0x00000000D83DDCB9L, 0x00000000D83DDCB2L, 0x00000000D83DDCB1L,
0x00000000000000A9L, 0x00000000000000AEL, 0x0000000000002122L, 0x000000000000303DL, 0x0000000000003030L, 0x00000000D83DDD1DL, 0x00000000D83DDD1AL,
0x00000000D83DDD19L, 0x00000000D83DDD1BL, 0x00000000D83DDD1CL, 0x000000000000274CL, 0x0000000000002B55L, 0x0000000000002757L, 0x000000000000203CL,
0x0000000000002049L, 0x0000000000002753L,
0x0000000000002755L, 0x0000000000002754L, 0x00000000D83DDD03L, 0x00000000D83DDD5BL, 0x00000000D83DDD67L, 0x00000000D83DDD50L, 0x00000000D83DDD5CL,
0x00000000D83DDD51L, 0x00000000D83DDD5DL, 0x00000000D83DDD52L, 0x00000000D83DDD5EL, 0x00000000D83DDD53L, 0x00000000D83DDD5FL, 0x00000000D83DDD54L,
0x00000000D83DDD60L, 0x00000000D83DDD55L, 0x00000000D83DDD56L, 0x00000000D83DDD57L, 0x00000000D83DDD58L, 0x00000000D83DDD59L, 0x00000000D83DDD5AL,
0x00000000D83DDD61L, 0x00000000D83DDD62L, 0x00000000D83DDD63L, 0x00000000D83DDD64L, 0x00000000D83DDD65L, 0x00000000D83DDD66L, 0x0000000000002716L,
0x0000000000002795L, 0x0000000000002796L, 0x0000000000002797L, 0x0000000000002660L, 0x0000000000002665L, 0x0000000000002663L, 0x0000000000002666L,
0x00000000D83DDCAEL, 0x00000000D83DDCAFL, 0x0000000000002714L, 0x0000000000002611L, 0x00000000D83DDD18L, 0x00000000D83DDD17L, 0x00000000000027B0L,
0x00000000D83DDD31L, 0x00000000D83DDD32L, 0x00000000D83DDD33L, 0x00000000000025FCL, 0x00000000000025FBL, 0x00000000000025FEL, 0x00000000000025FDL,
0x00000000000025AAL, 0x00000000000025ABL, 0x00000000D83DDD3AL, 0x0000000000002B1CL, 0x0000000000002B1BL, 0x00000000000026ABL, 0x00000000000026AAL,
0x00000000D83DDD34L, 0x00000000D83DDD35L, 0x00000000D83DDD3BL, 0x00000000D83DDD36L, 0x00000000D83DDD37L, 0x00000000D83DDD38L, 0x00000000D83DDD39L}};
static {
int emojiFullSize;
if (AndroidUtilities.density <= 1.0f) {
emojiFullSize = 32;
@ -203,25 +208,25 @@ public class Emoji {
} else {
emojiFullSize = 96;
}
drawImgSize = AndroidUtilities.dp(20);
drawImgSize = AndroidUtilities.dp(20);
if (AndroidUtilities.isTablet()) {
bigImgSize = AndroidUtilities.dp(40);
} else {
bigImgSize = AndroidUtilities.dp(32);
}
for (int j = 1; j < data.length; j++) {
for (int i = 0; i < data[j].length; i++) {
for (int j = 1; j < data.length; j++) {
for (int i = 0; i < data[j].length; i++) {
Rect rect = new Rect((i % cols[j - 1]) * emojiFullSize, (i / cols[j - 1]) * emojiFullSize, (i % cols[j - 1] + 1) * emojiFullSize, (i / cols[j - 1] + 1) * emojiFullSize);
rects.put(data[j][i], new DrawableInfo(rect, (byte)(j - 1)));
}
}
placeholderPaint = new Paint();
placeholderPaint.setColor(0x00000000);
}
rects.put(data[j][i], new DrawableInfo(rect, (byte) (j - 1)));
}
}
placeholderPaint = new Paint();
placeholderPaint.setColor(0x00000000);
}
private static void loadEmoji(final int page) {
try {
private static void loadEmoji(final int page) {
try {
float scale;
int imageResize = 1;
if (AndroidUtilities.density <= 1.0f) {
@ -290,64 +295,64 @@ public class Emoji {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.emojiDidLoaded);
}
});
} catch(Throwable x) {
} catch (Throwable x) {
FileLog.e("tmessages", "Error loading emoji", x);
}
}
private static void loadEmojiAsync(final int page) {
if (loadingEmoji[page]) {
}
private static void loadEmojiAsync(final int page) {
if (loadingEmoji[page]) {
return;
}
loadingEmoji[page] = true;
new Thread(new Runnable() {
new Thread(new Runnable() {
public void run() {
loadEmoji(page);
loadingEmoji[page] = false;
}
}).start();
}
public static void invalidateAll(View view) {
if (view instanceof ViewGroup) {
ViewGroup g = (ViewGroup)view;
for (int i = 0; i < g.getChildCount(); i++) {
invalidateAll(g.getChildAt(i));
}
} else if (view instanceof TextView) {
view.invalidate();
}
}
public static EmojiDrawable getEmojiDrawable(long code) {
DrawableInfo info = rects.get(code);
if (info == null) {
}
public static void invalidateAll(View view) {
if (view instanceof ViewGroup) {
ViewGroup g = (ViewGroup) view;
for (int i = 0; i < g.getChildCount(); i++) {
invalidateAll(g.getChildAt(i));
}
} else if (view instanceof TextView) {
view.invalidate();
}
}
public static EmojiDrawable getEmojiDrawable(long code) {
DrawableInfo info = rects.get(code);
if (info == null) {
FileLog.e("tmessages", "No emoji drawable for code " + String.format("%016X", code));
return null;
}
EmojiDrawable ed = new EmojiDrawable(info);
ed.setBounds(0, 0, drawImgSize, drawImgSize);
return ed;
}
public static Drawable getEmojiBigDrawable(long code) {
EmojiDrawable ed = getEmojiDrawable(code);
if (ed == null) {
return null;
}
ed.setBounds(0, 0, bigImgSize, bigImgSize);
ed.fullSize = true;
return ed;
}
public static class EmojiDrawable extends Drawable {
EmojiDrawable ed = new EmojiDrawable(info);
ed.setBounds(0, 0, drawImgSize, drawImgSize);
return ed;
}
public static Drawable getEmojiBigDrawable(long code) {
EmojiDrawable ed = getEmojiDrawable(code);
if (ed == null) {
return null;
}
ed.setBounds(0, 0, bigImgSize, bigImgSize);
ed.fullSize = true;
return ed;
}
public static class EmojiDrawable extends Drawable {
private DrawableInfo info;
private boolean fullSize = false;
private static Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
public EmojiDrawable(DrawableInfo i) {
info = i;
}
private boolean fullSize = false;
private static Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
public EmojiDrawable(DrawableInfo i) {
info = i;
}
public DrawableInfo getDrawableInfo() {
return info;
@ -364,12 +369,12 @@ public class Emoji {
}
@Override
public void draw(Canvas canvas) {
if (emojiBmp[info.page] == null) {
public void draw(Canvas canvas) {
if (emojiBmp[info.page] == null) {
loadEmojiAsync(info.page);
canvas.drawRect(getBounds(), placeholderPaint);
return;
}
canvas.drawRect(getBounds(), placeholderPaint);
return;
}
Rect b;
if (fullSize) {
b = getDrawRect();
@ -380,33 +385,33 @@ public class Emoji {
if (!canvas.quickReject(b.left, b.top, b.right, b.bottom, Canvas.EdgeType.AA)) {
canvas.drawBitmap(emojiBmp[info.page], info.rect, b, paint);
}
}
}
@Override
public int getOpacity() {
return 0;
}
@Override
public int getOpacity() {
return 0;
}
@Override
public void setAlpha(int alpha) {
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
@Override
public void setColorFilter(ColorFilter cf) {
}
}
private static class DrawableInfo {
}
private static class DrawableInfo {
public Rect rect;
public byte page;
public DrawableInfo(Rect r, byte p) {
rect = r;
public DrawableInfo(Rect r, byte p) {
rect = r;
page = p;
}
}
}
}
private static boolean inArray(char c, char[] a) {
for (char cc : a) {
@ -425,18 +430,22 @@ public class Emoji {
return value == 0xd83cdffb || value == 0xd83cdffc || value == 0xd83cdffd || value == 0xd83cdffe || value == 0xd83cdfff;
}
public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fontMetrics, int size) {
public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fontMetrics, int size, boolean createNew) {
if (cs == null || cs.length() == 0) {
return cs;
}
//SpannableStringLight.isFieldsAvailable();
//SpannableStringLight s = new SpannableStringLight(cs.toString());
Spannable s;
if (cs instanceof Spannable) {
s = (Spannable)cs;
if (!createNew && cs instanceof Spannable) {
s = (Spannable) cs;
} else {
s = Spannable.Factory.getInstance().newSpannable(cs);
s = Spannable.Factory.getInstance().newSpannable(cs.toString());
}
long buf = 0;
int emojiCount = 0;
//s.setSpansCount(emojiCount);
try {
for (int i = 0; i < cs.length(); i++) {
char c = cs.charAt(i);
@ -450,12 +459,12 @@ public class Emoji {
if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
if (c >= 0xDDE6 && c <= 0xDDFA) {
s.setSpan(span, i - 3, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
emojiCount++;
if (nextIsSkinTone) {
i += 2;
}
@ -472,8 +481,8 @@ public class Emoji {
if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
emojiCount++;
if (nextIsSkinTone) {
i += 2;
}
@ -486,8 +495,8 @@ public class Emoji {
if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
s.setSpan(span, i, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
emojiCount++;
if (nextIsSkinTone) {
i += 2;
}

View file

@ -1285,6 +1285,9 @@ public class ImageLoader {
}
public Float getFileProgress(String location) {
if (location == null) {
return null;
}
return fileProgresses.get(location);
}

View file

@ -46,6 +46,8 @@ import android.os.Vibrator;
import android.provider.MediaStore;
import android.view.View;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.android.query.SharedMediaQuery;
import org.telegram.android.video.InputSurface;
import org.telegram.android.video.MP4Builder;
import org.telegram.android.video.Mp4Movie;
@ -78,22 +80,34 @@ import java.util.concurrent.Semaphore;
public class MediaController implements NotificationCenter.NotificationCenterDelegate, SensorEventListener {
private native int startRecord(String path);
private native int writeFrame(ByteBuffer frame, int len);
private native void stopRecord();
private native int openOpusFile(String path);
private native int seekOpusFile(float position);
private native int isOpusFile(String path);
private native void closeOpusFile();
private native void readOpusFile(ByteBuffer buffer, int capacity, int[] args);
private native long getTotalPcmDuration();
public static int[] readArgs = new int[3];
public interface FileDownloadProgressListener {
void onFailedDownload(String fileName);
void onSuccessDownload(String fileName);
void onProgressDownload(String fileName, float progress);
void onProgressUpload(String fileName, float progress, boolean isEncrypted);
int getObserverTag();
}
@ -127,6 +141,16 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
MediaStore.Video.Media.DATE_TAKEN
};
public static class AudioEntry {
public long id;
public String author;
public String title;
public String genre;
public int duration;
public String path;
public MessageObject messageObject;
}
public static class AlbumEntry {
public int bucketId;
public String bucketName;
@ -221,7 +245,10 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
private HashMap<String, DownloadObject> downloadQueueKeys = new HashMap<>();
private boolean saveToGallery = true;
private boolean shuffleMusic;
private int repeatMode;
private Runnable refreshGalleryRunnable;
public static AlbumEntry allPhotosAlbumEntry;
private HashMap<String, ArrayList<WeakReference<FileDownloadProgressListener>>> loadingFileObservers = new HashMap<>();
@ -249,6 +276,11 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
private final Object progressTimerSync = new Object();
private boolean useFrontSpeaker;
private int buffersWrited;
private ArrayList<MessageObject> playlist = new ArrayList<>();
private ArrayList<MessageObject> shuffledPlaylist = new ArrayList<>();
private int currentPlaylistNum;
private boolean downloadingCurrentMessage;
private AudioInfo audioInfo;
private AudioRecord audioRecorder = null;
private TLRPC.TL_audio recordingAudio = null;
@ -271,6 +303,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
private int recordBufferSize;
private boolean sendAfterDone;
private Runnable recordStartRunnable;
private DispatchQueue recordQueue;
private DispatchQueue fileEncodingQueue;
private Runnable recordRunnable = new Runnable() {
@ -359,7 +392,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
}
/*private class GalleryObserverInternal extends ContentObserver {
private class GalleryObserverInternal extends ContentObserver {
public GalleryObserverInternal() {
super(null);
}
@ -367,9 +400,13 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AndroidUtilities.runOnUIThread(new Runnable() {
if (refreshGalleryRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(refreshGalleryRunnable);
}
AndroidUtilities.runOnUIThread(refreshGalleryRunnable = new Runnable() {
@Override
public void run() {
refreshGalleryRunnable = null;
loadGalleryPhotosAlbums(0);
}
}, 2000);
@ -384,14 +421,18 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AndroidUtilities.runOnUIThread(new Runnable() {
if (refreshGalleryRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(refreshGalleryRunnable);
}
AndroidUtilities.runOnUIThread(refreshGalleryRunnable = new Runnable() {
@Override
public void run() {
refreshGalleryRunnable = null;
loadGalleryPhotosAlbums(0);
}
}, 2000);
}
}*/
}
private ExternalObserver externalObserver = null;
private InternalObserver internalObserver = null;
@ -402,6 +443,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
private ArrayList<Long> lastSecretChatVisibleMessages = null;
private int startObserverToken = 0;
private StopMediaObserverRunnable stopMediaObserverRunnable = null;
private final class StopMediaObserverRunnable implements Runnable {
public int currentObserverToken = 0;
@ -427,9 +469,11 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
}
}
private String[] mediaProjections = null;
private static volatile MediaController Instance = null;
public static MediaController getInstance() {
MediaController localInstance = Instance;
if (localInstance == null) {
@ -484,6 +528,8 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
wifiDownloadMask = preferences.getInt("wifiDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO);
roamingDownloadMask = preferences.getInt("roamingDownloadMask", 0);
saveToGallery = preferences.getBoolean("save_gallery", false);
shuffleMusic = preferences.getBoolean("shuffleMusic", false);
repeatMode = preferences.getInt("repeatMode", 0);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded);
@ -491,6 +537,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileUploadProgressChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesDeleted);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.removeAllMessagesFromDialog);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.musicDidLoaded);
BroadcastReceiver networkStateReceiver = new BroadcastReceiver() {
@Override
@ -506,7 +553,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
if (Build.VERSION.SDK_INT >= 16) {
mediaProjections = new String[] {
mediaProjections = new String[]{
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DISPLAY_NAME,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
@ -516,7 +563,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
MediaStore.Images.ImageColumns.HEIGHT
};
} else {
mediaProjections = new String[] {
mediaProjections = new String[]{
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DISPLAY_NAME,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
@ -525,7 +572,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
};
}
/*try {
try {
ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, new GalleryObserverExternal());
} catch (Exception e) {
FileLog.e("tmessages", e);
@ -534,7 +581,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, new GalleryObserverInternal());
} catch (Exception e) {
FileLog.e("tmessages", e);
}*/
}
}
private void startProgressTimer() {
@ -606,12 +653,13 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
public void cleanup() {
clenupPlayer(false);
clenupPlayer(false, true);
if (currentGifDrawable != null) {
currentGifDrawable.recycle();
currentGifDrawable = null;
}
currentMediaCell = null;
audioInfo = null;
currentGifMessageObject = null;
photoDownloadQueue.clear();
audioDownloadQueue.clear();
@ -619,6 +667,8 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
videoDownloadQueue.clear();
downloadQueueKeys.clear();
videoConvertQueue.clear();
playlist.clear();
shuffledPlaylist.clear();
typingTimes.clear();
cancelVideoConvert(null);
}
@ -652,7 +702,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
} else {
for (DownloadObject downloadObject : photoDownloadQueue) {
FileLoader.getInstance().cancelLoadFile((TLRPC.PhotoSize)downloadObject.object);
FileLoader.getInstance().cancelLoadFile((TLRPC.PhotoSize) downloadObject.object);
}
photoDownloadQueue.clear();
}
@ -662,7 +712,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
} else {
for (DownloadObject downloadObject : audioDownloadQueue) {
FileLoader.getInstance().cancelLoadFile((TLRPC.Audio)downloadObject.object);
FileLoader.getInstance().cancelLoadFile((TLRPC.Audio) downloadObject.object);
}
audioDownloadQueue.clear();
}
@ -672,7 +722,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
} else {
for (DownloadObject downloadObject : documentDownloadQueue) {
FileLoader.getInstance().cancelLoadFile((TLRPC.Document)downloadObject.object);
FileLoader.getInstance().cancelLoadFile((TLRPC.Document) downloadObject.object);
}
documentDownloadQueue.clear();
}
@ -682,7 +732,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
} else {
for (DownloadObject downloadObject : videoDownloadQueue) {
FileLoader.getInstance().cancelLoadFile((TLRPC.Video)downloadObject.object);
FileLoader.getInstance().cancelLoadFile((TLRPC.Video) downloadObject.object);
}
videoDownloadQueue.clear();
}
@ -713,7 +763,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
private int getCurrentDownloadMask() {
if (ConnectionsManager.isConnectedToWiFi()) {
return wifiDownloadMask;
} else if(ConnectionsManager.isRoaming()) {
} else if (ConnectionsManager.isRoaming()) {
return roamingDownloadMask;
} else {
return mobileDataDownloadMask;
@ -742,13 +792,13 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
boolean added = true;
if (downloadObject.object instanceof TLRPC.Audio) {
FileLoader.getInstance().loadFile((TLRPC.Audio)downloadObject.object, false);
FileLoader.getInstance().loadFile((TLRPC.Audio) downloadObject.object, false);
} else if (downloadObject.object instanceof TLRPC.PhotoSize) {
FileLoader.getInstance().loadFile((TLRPC.PhotoSize)downloadObject.object, null, false);
FileLoader.getInstance().loadFile((TLRPC.PhotoSize) downloadObject.object, null, false);
} else if (downloadObject.object instanceof TLRPC.Video) {
FileLoader.getInstance().loadFile((TLRPC.Video)downloadObject.object, false);
FileLoader.getInstance().loadFile((TLRPC.Video) downloadObject.object, false);
} else if (downloadObject.object instanceof TLRPC.Document) {
FileLoader.getInstance().loadFile((TLRPC.Document)downloadObject.object, false, false);
FileLoader.getInstance().loadFile((TLRPC.Document) downloadObject.object, false, false);
} else {
added = false;
}
@ -986,7 +1036,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.FileDidFailedLoad) {
listenerInProgress = true;
String fileName = (String)args[0];
String fileName = (String) args[0];
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
if (arrayList != null) {
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
@ -999,10 +1049,16 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
listenerInProgress = false;
processLaterArrays();
checkDownloadFinished(fileName, (Integer)args[1]);
checkDownloadFinished(fileName, (Integer) args[1]);
} else if (id == NotificationCenter.FileDidLoaded) {
listenerInProgress = true;
String fileName = (String)args[0];
String fileName = (String) args[0];
if (downloadingCurrentMessage && playingMessageObject != null) {
String file = FileLoader.getAttachFileName(playingMessageObject.messageOwner.media.document);
if (file.equals(fileName)) {
playAudio(playingMessageObject);
}
}
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
if (arrayList != null) {
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
@ -1018,10 +1074,10 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
checkDownloadFinished(fileName, 0);
} else if (id == NotificationCenter.FileLoadProgressChanged) {
listenerInProgress = true;
String fileName = (String)args[0];
String fileName = (String) args[0];
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
if (arrayList != null) {
Float progress = (Float)args[1];
Float progress = (Float) args[1];
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
if (reference.get() != null) {
reference.get().onProgressDownload(fileName, progress);
@ -1032,11 +1088,11 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
processLaterArrays();
} else if (id == NotificationCenter.FileUploadProgressChanged) {
listenerInProgress = true;
String fileName = (String)args[0];
String fileName = (String) args[0];
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
if (arrayList != null) {
Float progress = (Float)args[1];
Boolean enc = (Boolean)args[2];
Float progress = (Float) args[1];
Boolean enc = (Boolean) args[2];
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
if (reference.get() != null) {
reference.get().onProgressUpload(fileName, progress, enc);
@ -1070,15 +1126,27 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
} else if (id == NotificationCenter.messagesDeleted) {
if (playingMessageObject != null) {
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>)args[0];
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>) args[0];
if (markAsDeletedMessages.contains(playingMessageObject.getId())) {
clenupPlayer(false);
clenupPlayer(false, true);
}
}
} else if (id == NotificationCenter.removeAllMessagesFromDialog) {
long did = (Long)args[0];
long did = (Long) args[0];
if (playingMessageObject != null && playingMessageObject.getDialogId() == did) {
clenupPlayer(false);
clenupPlayer(false, true);
}
} else if (id == NotificationCenter.musicDidLoaded) {
long did = (Long) args[0];
if (playingMessageObject != null && playingMessageObject.isMusic() && playingMessageObject.getDialogId() == did) {
ArrayList<MessageObject> arrayList = (ArrayList<MessageObject>) args[1];
playlist.addAll(0, arrayList);
if (shuffleMusic) {
buildShuffledPlayList();
currentPlaylistNum = 0;
} else {
currentPlaylistNum += arrayList.size();
}
}
}
}
@ -1174,7 +1242,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioTrackPlayer.setNotificationMarkerPosition(1);
}
if (finalBuffersWrited == 1) {
clenupPlayer(true);
clenupPlayer(true, true);
}
}
}
@ -1221,7 +1289,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioRouteChanged, useFrontSpeaker);
MessageObject currentMessageObject = playingMessageObject;
float progress = playingMessageObject.audioProgress;
clenupPlayer(false);
clenupPlayer(false, true);
currentMessageObject.audioProgress = progress;
playAudio(currentMessageObject);
ignoreProximity = false;
@ -1266,9 +1334,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
}
private void clenupPlayer(boolean notify) {
public void clenupPlayer(boolean notify, boolean stopService) {
stopProximitySensor();
if (audioPlayer != null || audioTrackPlayer != null) {
if (playingMessageObject != null) {
if (audioPlayer != null) {
try {
audioPlayer.stop();
@ -1301,12 +1369,20 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
lastProgress = 0;
buffersWrited = 0;
isPaused = false;
if (downloadingCurrentMessage) {
FileLoader.getInstance().cancelLoadFile(playingMessageObject.messageOwner.media.document);
}
MessageObject lastFile = playingMessageObject;
playingMessageObject.audioProgress = 0.0f;
playingMessageObject.audioProgressSec = 0;
playingMessageObject = null;
downloadingCurrentMessage = false;
if (notify) {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioDidReset, lastFile.getId());
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioDidReset, lastFile.getId(), stopService);
}
if (stopService) {
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.stopService(intent);
}
}
}
@ -1364,6 +1440,151 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
return true;
}
public MessageObject getPlayingMessageObject() {
return playingMessageObject;
}
private void buildShuffledPlayList() {
ArrayList<MessageObject> all = new ArrayList<>(playlist);
shuffledPlaylist.clear();
MessageObject messageObject = playlist.get(currentPlaylistNum);
all.remove(currentPlaylistNum);
shuffledPlaylist.add(messageObject);
int count = all.size();
for (int a = 0; a < count; a++) {
int index = Utilities.random.nextInt(all.size());
shuffledPlaylist.add(all.get(index));
all.remove(index);
}
}
public boolean setPlaylist(ArrayList<MessageObject> messageObjects, MessageObject current) {
if (playingMessageObject == current) {
return playAudio(current);
}
playlist.clear();
for (int a = messageObjects.size() - 1; a >= 0; a--) {
MessageObject messageObject = messageObjects.get(a);
if (messageObject.isMusic()) {
playlist.add(messageObject);
}
}
currentPlaylistNum = playlist.indexOf(current);
if (currentPlaylistNum == -1) {
playlist.clear();
shuffledPlaylist.clear();
return false;
}
if (shuffleMusic) {
buildShuffledPlayList();
currentPlaylistNum = 0;
}
SharedMediaQuery.loadMusic(current.getDialogId(), playlist.get(0).getId());
return playAudio(current);
}
public void playNextMessage() {
playNextMessage(false);
}
private void playNextMessage(boolean byStop) {
ArrayList<MessageObject> currentPlayList = shuffleMusic ? shuffledPlaylist : playlist;
if (byStop && repeatMode == 2) {
clenupPlayer(false, false);
playAudio(currentPlayList.get(currentPlaylistNum));
return;
}
currentPlaylistNum++;
if (currentPlaylistNum >= currentPlayList.size()) {
currentPlaylistNum = 0;
if (byStop && repeatMode == 0) {
stopProximitySensor();
if (audioPlayer != null || audioTrackPlayer != null) {
if (audioPlayer != null) {
try {
audioPlayer.stop();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
try {
audioPlayer.release();
audioPlayer = null;
} catch (Exception e) {
FileLog.e("tmessages", e);
}
} else if (audioTrackPlayer != null) {
synchronized (playerObjectSync) {
try {
audioTrackPlayer.pause();
audioTrackPlayer.flush();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
try {
audioTrackPlayer.release();
audioTrackPlayer = null;
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
}
stopProgressTimer();
lastProgress = 0;
buffersWrited = 0;
isPaused = true;
playingMessageObject.audioProgress = 0.0f;
playingMessageObject.audioProgressSec = 0;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
}
return;
}
}
if (currentPlaylistNum < 0 || currentPlaylistNum >= currentPlayList.size()) {
return;
}
playAudio(currentPlayList.get(currentPlaylistNum));
}
public void playPreviousMessage() {
ArrayList<MessageObject> currentPlayList = shuffleMusic ? shuffledPlaylist : playlist;
currentPlaylistNum--;
if (currentPlaylistNum < 0) {
currentPlaylistNum = currentPlayList.size() - 1;
}
if (currentPlaylistNum < 0 || currentPlaylistNum >= currentPlayList.size()) {
return;
}
playAudio(currentPlayList.get(currentPlaylistNum));
}
private void checkIsNextMusicFileDownloaded() {
ArrayList<MessageObject> currentPlayList = shuffleMusic ? shuffledPlaylist : playlist;
if (currentPlayList == null || currentPlayList.size() < 2) {
return;
}
int nextIndex = currentPlaylistNum + 1;
if (nextIndex >= currentPlayList.size()) {
nextIndex = 0;
}
MessageObject nextAudio = currentPlayList.get(nextIndex);
File file = null;
if (nextAudio.messageOwner.attachPath != null && nextAudio.messageOwner.attachPath.length() > 0) {
file = new File(nextAudio.messageOwner.attachPath);
if (!file.exists()) {
file = null;
}
}
final File cacheFile = file != null ? file : FileLoader.getPathToMessage(nextAudio.messageOwner);
boolean exist = cacheFile != null && cacheFile.exists();
if (cacheFile != null && cacheFile != file && !cacheFile.exists() && nextAudio.isMusic()) {
FileLoader.getInstance().loadFile(nextAudio.messageOwner.media.document, true, false);
}
}
public boolean playAudio(MessageObject messageObject) {
if (messageObject == null) {
return false;
@ -1374,8 +1595,10 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
return true;
}
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioDidStarted, messageObject);
clenupPlayer(true);
if (audioTrackPlayer != null) {
MusicPlayerService.setIgnoreAudioFocus();
}
clenupPlayer(true, false);
File file = null;
if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) {
file = new File(messageObject.messageOwner.attachPath);
@ -1384,8 +1607,33 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
}
final File cacheFile = file != null ? file : FileLoader.getPathToMessage(messageObject.messageOwner);
if (cacheFile != null && cacheFile != file && !cacheFile.exists() && messageObject.isMusic()) {
FileLoader.getInstance().loadFile(messageObject.messageOwner.media.document, true, false);
downloadingCurrentMessage = true;
isPaused = false;
lastProgress = 0;
lastPlayPcm = 0;
audioInfo = null;
playingMessageObject = messageObject;
if (playingMessageObject.messageOwner.media.document != null) {
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.startService(intent);
} else {
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.stopService(intent);
}
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
return true;
} else {
downloadingCurrentMessage = false;
}
if (messageObject.isMusic()) {
checkIsNextMusicFileDownloaded();
}
if (isOpusFile(cacheFile.getAbsolutePath()) == 1) {
playlist.clear();
shuffledPlaylist.clear();
synchronized (playerObjectSync) {
try {
ignoreFirstProgress = 3;
@ -1410,7 +1658,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioTrackPlayer.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() {
@Override
public void onMarkerReached(AudioTrack audioTrack) {
clenupPlayer(true);
clenupPlayer(true, true);
}
@Override
@ -1420,7 +1668,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
});
audioTrackPlayer.play();
startProgressTimer();
startProximitySensor();
if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaAudio) {
startProximitySensor();
}
} catch (Exception e) {
FileLog.e("tmessages", e);
if (audioTrackPlayer != null) {
@ -1428,6 +1678,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioTrackPlayer = null;
isPaused = false;
playingMessageObject = null;
downloadingCurrentMessage = false;
}
return false;
}
@ -1440,13 +1691,28 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
clenupPlayer(true);
if (!playlist.isEmpty() && playlist.size() > 1) {
playNextMessage(true);
} else {
clenupPlayer(true, true);
}
}
});
audioPlayer.prepare();
audioPlayer.start();
startProgressTimer();
startProximitySensor();
if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaAudio) {
audioInfo = null;
playlist.clear();
shuffledPlaylist.clear();
startProximitySensor();
} else {
try {
audioInfo = AudioInfo.getAudioInfo(cacheFile);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
} catch (Exception e) {
FileLog.e("tmessages", e);
if (audioPlayer != null) {
@ -1454,6 +1720,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioPlayer = null;
isPaused = false;
playingMessageObject = null;
downloadingCurrentMessage = false;
}
return false;
}
@ -1463,6 +1730,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
lastProgress = 0;
lastPlayPcm = 0;
playingMessageObject = messageObject;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioDidStarted, messageObject);
if (audioPlayer != null) {
try {
@ -1484,7 +1752,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
public void run() {
try {
if (playingMessageObject != null && playingMessageObject.audioProgress != 0) {
lastPlayPcm = (long)(currentTotalPcmDuration * playingMessageObject.audioProgress);
lastPlayPcm = (long) (currentTotalPcmDuration * playingMessageObject.audioProgress);
seekOpusFile(playingMessageObject.audioProgress);
}
} catch (Exception e) {
@ -1500,6 +1768,14 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
});
}
if (playingMessageObject.messageOwner.media.document != null) {
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.startService(intent);
} else {
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.stopService(intent);
}
return true;
}
@ -1533,7 +1809,55 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
stopProgressTimer();
playingMessageObject = null;
downloadingCurrentMessage = false;
isPaused = false;
Intent intent = new Intent(ApplicationLoader.applicationContext, MusicPlayerService.class);
ApplicationLoader.applicationContext.stopService(intent);
}
public AudioInfo getAudioInfo() {
return audioInfo;
}
public boolean isShuffleMusic() {
return shuffleMusic;
}
public int getRepeatMode() {
return repeatMode;
}
public void toggleShuffleMusic() {
shuffleMusic = !shuffleMusic;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("shuffleMusic", shuffleMusic);
editor.commit();
if (shuffleMusic) {
buildShuffledPlayList();
currentPlaylistNum = 0;
} else {
if (playingMessageObject != null) {
currentPlaylistNum = playlist.indexOf(playingMessageObject);
if (currentPlaylistNum == -1) {
playlist.clear();
shuffledPlaylist.clear();
clenupPlayer(true, true);
}
}
}
}
public void toggleRepeatMode() {
repeatMode++;
if (repeatMode > 2) {
repeatMode = 0;
}
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("repeatMode", repeatMode);
editor.commit();
}
public boolean pauseAudio(MessageObject messageObject) {
@ -1549,6 +1873,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
audioTrackPlayer.pause();
}
isPaused = true;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
} catch (Exception e) {
FileLog.e("tmessages", e);
isPaused = false;
@ -1561,7 +1886,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
if (audioTrackPlayer == null && audioPlayer == null || messageObject == null || playingMessageObject == null || playingMessageObject != null && playingMessageObject.getId() != messageObject.getId()) {
return false;
}
startProximitySensor();
if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaAudio) {
startProximitySensor();
}
try {
startProgressTimer();
if (audioPlayer != null) {
@ -1571,6 +1898,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
checkPlayerQueue();
}
isPaused = false;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
} catch (Exception e) {
FileLog.e("tmessages", e);
return false;
@ -1579,15 +1907,23 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
public boolean isPlayingAudio(MessageObject messageObject) {
return !(audioTrackPlayer == null && audioPlayer == null || messageObject == null || playingMessageObject == null || playingMessageObject != null && playingMessageObject.getId() != messageObject.getId());
return !(audioTrackPlayer == null && audioPlayer == null || messageObject == null || playingMessageObject == null || playingMessageObject != null && (playingMessageObject.getId() != messageObject.getId() || downloadingCurrentMessage));
}
public boolean isAudioPaused() {
return isPaused;
return isPaused || downloadingCurrentMessage;
}
public boolean isDownloadingCurrentMessage() {
return downloadingCurrentMessage;
}
public void startRecording(final long dialog_id, final MessageObject reply_to_msg) {
clenupPlayer(true);
boolean paused = false;
if (playingMessageObject != null && isPlayingAudio(playingMessageObject) && !isAudioPaused()) {
paused = true;
pauseAudio(playingMessageObject);
}
try {
Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE);
@ -1596,13 +1932,14 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
FileLog.e("tmessages", e);
}
recordQueue.postRunnable(new Runnable() {
recordQueue.postRunnable(recordStartRunnable = new Runnable() {
@Override
public void run() {
if (audioRecorder != null) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
recordStartRunnable = null;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStartError);
}
});
@ -1624,6 +1961,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
recordStartRunnable = null;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStartError);
}
});
@ -1653,6 +1991,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
recordStartRunnable = null;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStartError);
}
});
@ -1663,11 +2002,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
recordStartRunnable = null;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStarted);
}
});
}
});
}, paused ? 500 : 0);
}
private void stopRecordingInternal(final boolean send) {
@ -1709,6 +2049,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
public void stopRecording(final boolean send) {
if (recordStartRunnable != null) {
recordQueue.cancelRunnable(recordStartRunnable);
}
recordQueue.postRunnable(new Runnable() {
@Override
public void run() {
@ -1790,10 +2133,15 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
destFile = AndroidUtilities.generateVideoPath();
} else if (type == 2) {
File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
f.mkdir();
destFile = new File(f, name);
} else if (type == 3) {
File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
f.mkdirs();
destFile = new File(f, name);
}
if(!destFile.exists()) {
if (!destFile.exists()) {
destFile.createNewFile();
}
FileChannel source = null;
@ -1835,7 +2183,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
}
}
if (result && (type == 0 || type == 1)) {
if (result && (type == 0 || type == 1 || type == 3)) {
AndroidUtilities.addMediaToGallery(Uri.fromFile(destFile));
}
} catch (Exception e) {
@ -1940,7 +2288,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
String str = new String(header);
if (str != null) {
str = str.toLowerCase();
if (str.startsWith("riff") && str.endsWith("webp")){
if (str.startsWith("riff") && str.endsWith("webp")) {
return true;
}
}

View file

@ -313,6 +313,8 @@ public class MessageObject {
} else {
messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker);
}
} else if (isMusic()) {
messageText = LocaleController.getString("AttachMusic", R.string.AttachMusic);
} else {
String name = FileLoader.getDocumentFileName(message.media.document);
if (name != null && name.length() > 0) {
@ -327,7 +329,9 @@ public class MessageObject {
} else {
messageText = message.message;
}
messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20));
if (generateLayout) {
messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
}
if (message instanceof TLRPC.TL_message || message instanceof TLRPC.TL_messageForwarded_old2) {
if (isMediaEmpty()) {
@ -355,6 +359,9 @@ public class MessageObject {
type = 8;
} else if (message.media.document.mime_type.equals("image/webp") && isSticker()) {
type = 13;
} else if (isMusic()) {
type = 14;
contentType = 8;
} else {
type = 9;
}
@ -600,7 +607,7 @@ public class MessageObject {
return;
}
if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) {
caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20));
caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
if (containsUrls(caption)) {
try {
Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS);
@ -950,6 +957,17 @@ public class MessageObject {
return false;
}
public static boolean isMusicMessage(TLRPC.Message message) {
if (message.media != null && message.media.document != null) {
for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
return true;
}
}
}
return false;
}
public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.Message message) {
if (message.media != null && message.media.document != null) {
for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
@ -986,6 +1004,8 @@ public class MessageObject {
return AndroidUtilities.dp(100);
} else if (type == 4) {
return AndroidUtilities.dp(114);
} else if (type == 14) {
return AndroidUtilities.dp(78);
} else if (type == 13) {
float maxHeight = AndroidUtilities.displaySize.y * 0.4f;
float maxWidth;
@ -1061,6 +1081,39 @@ public class MessageObject {
return isStickerMessage(messageOwner);
}
public boolean isMusic() {
return isMusicMessage(messageOwner);
}
public String getMusicTitle() {
for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
String title = attribute.title;
if (title == null || title.length() == 0) {
title = FileLoader.getDocumentFileName(messageOwner.media.document);
if (title == null || title.length() == 0) {
title = LocaleController.getString("AudioUnknownTitle", R.string.AudioUnknownTitle);
}
}
return title;
}
}
return "";
}
public String getMusicAuthor() {
for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
String performer = attribute.performer;
if (performer == null || performer.length() == 0) {
performer = LocaleController.getString("AudioUnknownArtist", R.string.AudioUnknownArtist);
}
return performer;
}
}
return "";
}
public TLRPC.InputStickerSet getInputStickerSet() {
return getInputStickerSet(messageOwner);
}

View file

@ -3900,7 +3900,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty() || !markAsReadEncrypted.isEmpty()) {
if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty()) {
MessagesStorage.getInstance().updateDialogsWithReadedMessages(markAsReadMessagesInbox, true);
MessagesStorage.getInstance().updateDialogsWithReadMessages(markAsReadMessagesInbox, true);
}
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true);
}

View file

@ -1149,7 +1149,7 @@ public class MessagesStorage {
});
}
private void updateDialogsWithReadedMessagesInternal(final ArrayList<Integer> messages, final HashMap<Integer, Integer> inbox) {
private void updateDialogsWithReadMessagesInternal(final ArrayList<Integer> messages, final HashMap<Integer, Integer> inbox) {
try {
HashMap<Long, Integer> dialogsToUpdate = new HashMap<>();
StringBuilder dialogsToReload = new StringBuilder();
@ -1184,14 +1184,13 @@ public class MessagesStorage {
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid <= %d AND read_state IN(0,2) AND out = 0", entry.getKey(), entry.getValue()));
if (cursor.next()) {
int count = cursor.intValue(0);
if (count == 0) {
continue;
if (count != 0) {
dialogsToUpdate.put((long) entry.getKey(), count);
if (dialogsToReload.length() != 0) {
dialogsToReload.append(",");
}
dialogsToReload.append(entry.getKey());
}
dialogsToUpdate.put((long) entry.getKey(), count);
if (dialogsToReload.length() != 0) {
dialogsToReload.append(",");
}
dialogsToReload.append(entry.getKey());
}
cursor.dispose();
}
@ -1231,7 +1230,7 @@ public class MessagesStorage {
}
}
public void updateDialogsWithReadedMessages(final HashMap<Integer, Integer> inbox, boolean useQueue) {
public void updateDialogsWithReadMessages(final HashMap<Integer, Integer> inbox, boolean useQueue) {
if (inbox.isEmpty()) {
return;
}
@ -1239,11 +1238,11 @@ public class MessagesStorage {
storageQueue.postRunnable(new Runnable() {
@Override
public void run() {
updateDialogsWithReadedMessagesInternal(null, inbox);
updateDialogsWithReadMessagesInternal(null, inbox);
}
});
} else {
updateDialogsWithReadedMessagesInternal(null, inbox);
updateDialogsWithReadMessagesInternal(null, inbox);
}
}
@ -3473,7 +3472,7 @@ public class MessagesStorage {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids);
}
});
MessagesStorage.getInstance().updateDialogsWithReadedMessagesInternal(mids, null);
MessagesStorage.getInstance().updateDialogsWithReadMessagesInternal(mids, null);
MessagesStorage.getInstance().markMessagesAsDeletedInternal(mids);
MessagesStorage.getInstance().updateDialogsWithDeletedMessagesInternal(mids);
}

View file

@ -0,0 +1,69 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.android;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.view.KeyEvent;
public class MusicPlayerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
if (intent.getExtras() == null) {
return;
}
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (keyEvent == null) {
return;
}
if (keyEvent.getAction() != KeyEvent.ACTION_DOWN)
return;
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
} else {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
}
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
MediaController.getInstance().playNextMessage();
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
MediaController.getInstance().playPreviousMessage();
break;
}
} else {
if (intent.getAction().equals(MusicPlayerService.NOTIFY_PLAY)) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_PAUSE) || intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_NEXT)) {
MediaController.getInstance().playNextMessage();
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_CLOSE)) {
MediaController.getInstance().clenupPlayer(true, true);
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_PREVIOUS)) {
MediaController.getInstance().playPreviousMessage();
}
}
}
}

View file

@ -0,0 +1,295 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.android;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.RemoteViews;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
import org.telegram.ui.LaunchActivity;
public class MusicPlayerService extends Service implements AudioManager.OnAudioFocusChangeListener, NotificationCenter.NotificationCenterDelegate {
public static final String NOTIFY_PREVIOUS = "org.telegram.android.musicplayer.previous";
public static final String NOTIFY_CLOSE = "org.telegram.android.musicplayer.close";
public static final String NOTIFY_PAUSE = "org.telegram.android.musicplayer.pause";
public static final String NOTIFY_PLAY = "org.telegram.android.musicplayer.play";
public static final String NOTIFY_NEXT = "org.telegram.android.musicplayer.next";
private RemoteControlClient remoteControlClient;
private AudioManager audioManager;
private static boolean ignoreAudioFocus = false;
private PhoneStateListener phoneStateListener;
private static boolean supportBigNotifications = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
private static boolean supportLockScreenControls = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
try {
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
if (state == TelephonyManager.CALL_STATE_RINGING) {
if (MediaController.getInstance().isPlayingAudio(MediaController.getInstance().getPlayingMessageObject()) && !MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
}
} else if (state == TelephonyManager.CALL_STATE_IDLE) {
} else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
}
super.onCallStateChanged(state, incomingNumber);
}
};
TelephonyManager mgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (mgr != null) {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
} catch (Exception e) {
FileLog.e("tmessages", e);
}
super.onCreate();
}
@SuppressLint("NewApi")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject == null) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
stopSelf();
}
});
return START_STICKY;
}
if (supportLockScreenControls) {
ComponentName remoteComponentName = new ComponentName(getApplicationContext(), MusicPlayerReceiver.class.getName());
try {
if (remoteControlClient == null) {
audioManager.registerMediaButtonEventReceiver(remoteComponentName);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(remoteComponentName);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent);
audioManager.registerRemoteControlClient(remoteControlClient);
}
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_STOP | RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS | RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
createNotification(messageObject);
} catch (Exception e) {
e.printStackTrace();
}
return START_STICKY;
}
@SuppressLint("NewApi")
private void createNotification(MessageObject messageObject) {
String songName = messageObject.getMusicTitle();
String authorName = messageObject.getMusicAuthor();
AudioInfo audioInfo = MediaController.getInstance().getAudioInfo();
RemoteViews simpleContentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_small_notification);
RemoteViews expandedView = null;
if (supportBigNotifications) {
expandedView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.player_big_notification);
}
Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class);
intent.setAction("com.tmessages.openplayer");
intent.setFlags(32768);
PendingIntent contentIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.drawable.player)
.setContentIntent(contentIntent)
.setContentTitle(songName).build();
notification.contentView = simpleContentView;
if (supportBigNotifications) {
notification.bigContentView = expandedView;
}
setListeners(simpleContentView);
if (supportBigNotifications) {
setListeners(expandedView);
}
Bitmap albumArt = audioInfo != null ? audioInfo.getSmallCover() : null;
if (albumArt != null) {
notification.contentView.setImageViewBitmap(R.id.player_album_art, albumArt);
if (supportBigNotifications) {
notification.bigContentView.setImageViewBitmap(R.id.player_album_art, albumArt);
}
} else {
notification.contentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_small);
if (supportBigNotifications) {
notification.bigContentView.setImageViewResource(R.id.player_album_art, R.drawable.nocover_big);
}
}
if (MediaController.getInstance().isDownloadingCurrentMessage()) {
notification.contentView.setViewVisibility(R.id.player_pause, View.GONE);
notification.contentView.setViewVisibility(R.id.player_play, View.GONE);
notification.contentView.setViewVisibility(R.id.player_next, View.GONE);
notification.contentView.setViewVisibility(R.id.player_previous, View.GONE);
notification.contentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE);
if (supportBigNotifications) {
notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE);
notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE);
notification.bigContentView.setViewVisibility(R.id.player_next, View.GONE);
notification.bigContentView.setViewVisibility(R.id.player_previous, View.GONE);
notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.VISIBLE);
}
} else {
notification.contentView.setViewVisibility(R.id.player_progress_bar, View.GONE);
notification.contentView.setViewVisibility(R.id.player_next, View.VISIBLE);
notification.contentView.setViewVisibility(R.id.player_previous, View.VISIBLE);
if (supportBigNotifications) {
notification.bigContentView.setViewVisibility(R.id.player_next, View.VISIBLE);
notification.bigContentView.setViewVisibility(R.id.player_previous, View.VISIBLE);
notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.GONE);
}
if (MediaController.getInstance().isAudioPaused()) {
notification.contentView.setViewVisibility(R.id.player_pause, View.GONE);
notification.contentView.setViewVisibility(R.id.player_play, View.VISIBLE);
if (supportBigNotifications) {
notification.bigContentView.setViewVisibility(R.id.player_pause, View.GONE);
notification.bigContentView.setViewVisibility(R.id.player_play, View.VISIBLE);
}
} else {
notification.contentView.setViewVisibility(R.id.player_pause, View.VISIBLE);
notification.contentView.setViewVisibility(R.id.player_play, View.GONE);
if (supportBigNotifications) {
notification.bigContentView.setViewVisibility(R.id.player_pause, View.VISIBLE);
notification.bigContentView.setViewVisibility(R.id.player_play, View.GONE);
}
}
}
notification.contentView.setTextViewText(R.id.player_song_name, songName);
notification.contentView.setTextViewText(R.id.player_author_name, authorName);
if (supportBigNotifications) {
notification.bigContentView.setTextViewText(R.id.player_song_name, songName);
notification.bigContentView.setTextViewText(R.id.player_author_name, authorName);
}
notification.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(5, notification);
if (remoteControlClient != null) {
RemoteControlClient.MetadataEditor metadataEditor = remoteControlClient.editMetadata(true);
metadataEditor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, authorName);
metadataEditor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, songName);
if (audioInfo != null && audioInfo.getCover() != null) {
metadataEditor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, audioInfo.getCover());
}
metadataEditor.apply();
audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
}
public void setListeners(RemoteViews view) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT);
view.setOnClickPendingIntent(R.id.player_previous, pendingIntent);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT);
view.setOnClickPendingIntent(R.id.player_close, pendingIntent);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
view.setOnClickPendingIntent(R.id.player_pause, pendingIntent);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT);
view.setOnClickPendingIntent(R.id.player_next, pendingIntent);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(NOTIFY_PLAY), PendingIntent.FLAG_UPDATE_CURRENT);
view.setOnClickPendingIntent(R.id.player_play, pendingIntent);
}
@SuppressLint("NewApi")
@Override
public void onDestroy() {
super.onDestroy();
if (remoteControlClient != null) {
RemoteControlClient.MetadataEditor metadataEditor = remoteControlClient.editMetadata(true);
metadataEditor.clear();
metadataEditor.apply();
audioManager.unregisterRemoteControlClient(remoteControlClient);
}
try {
TelephonyManager mgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (mgr != null) {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
} catch (Exception e) {
FileLog.e("tmessages", e);
}
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
}
@Override
public void onAudioFocusChange(int focusChange) {
if (ignoreAudioFocus) {
ignoreAudioFocus = false;
return;
}
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
if (MediaController.getInstance().isPlayingAudio(MediaController.getInstance().getPlayingMessageObject()) && !MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
//MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
}
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.audioPlayStateChanged) {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject != null) {
createNotification(messageObject);
} else {
stopSelf();
}
}
}
public static void setIgnoreAudioFocus() {
ignoreAudioFocus = true;
}
}

View file

@ -64,6 +64,7 @@ public class NotificationCenter {
public static final int botInfoDidLoaded = totalEvents++;
public static final int botKeyboardDidLoaded = totalEvents++;
public static final int chatSearchResultsAvailable = totalEvents++;
public static final int musicDidLoaded = totalEvents++;
public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++;
@ -89,6 +90,7 @@ public class NotificationCenter {
public static final int audioProgressDidChanged = totalEvents++;
public static final int audioDidReset = totalEvents++;
public static final int audioPlayStateChanged = totalEvents++;
public static final int recordProgressChanged = totalEvents++;
public static final int recordStarted = totalEvents++;
public static final int recordStartError = totalEvents++;

View file

@ -31,8 +31,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.RemoteInput;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.FileLog;
@ -602,11 +600,8 @@ public class NotificationsController {
}
String lastMessage = null;
String lastMessageFull = null;
if (pushMessages.size() == 1) {
String message = lastMessageFull = getStringForMessage(pushMessages.get(0), false);
//lastMessage = getStringForMessage(pushMessages.get(0), true);
lastMessage = lastMessageFull;
String message = lastMessage = getStringForMessage(pushMessages.get(0), false);
if (message == null) {
return;
}
@ -630,8 +625,7 @@ public class NotificationsController {
continue;
}
if (i == 0) {
lastMessageFull = message;
lastMessage = lastMessageFull;
lastMessage = message;
}
if (pushDialogs.size() == 1) {
if (replace) {
@ -692,9 +686,6 @@ public class NotificationsController {
showExtraNotifications(mBuilder, notifyAboutLast);
notificationManager.notify(1, mBuilder.build());
if (preferences.getBoolean("EnablePebbleNotifications", false)) {
sendAlertToPebble(lastMessageFull);
}
scheduleNotificationRepeat();
} catch (Exception e) {
@ -897,26 +888,6 @@ public class NotificationsController {
}
}
private void sendAlertToPebble(String message) {
try {
final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION");
final HashMap<String, String> data = new HashMap<>();
data.put("title", LocaleController.getString("AppName", R.string.AppName));
data.put("body", message);
final JSONObject jsonData = new JSONObject(data);
final String notificationData = new JSONArray().put(jsonData).toString();
i.putExtra("messageType", "PEBBLE_ALERT");
i.putExtra("sender", LocaleController.formatString("AppName", R.string.AppName));
i.putExtra("notificationData", notificationData);
ApplicationLoader.applicationContext.sendBroadcast(i);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
public void processReadMessages(HashMap<Integer, Integer> inbox, long dialog_id, int max_date, int max_id, boolean isPopup) {
int oldCount = popupMessages.size();
if (inbox != null) {

View file

@ -19,6 +19,7 @@ import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
@ -557,6 +558,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
for (int a = 0; a < messages.size(); a++) {
MessageObject msgObj = messages.get(a);
if (msgObj.getId() <= 0) {
continue;
}
final TLRPC.Message newMsg = new TLRPC.TL_message();
newMsg.flags |= TLRPC.MESSAGE_FLAG_FWD;
@ -1858,6 +1862,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
return false;
}
MimeTypeMap myMime = MimeTypeMap.getSingleton();
TLRPC.TL_documentAttributeAudio attributeAudio = null;
if (uri != null) {
String extension = null;
if (mime != null) {
@ -1885,8 +1890,31 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (idx != -1) {
ext = path.substring(idx + 1);
}
if (ext.toLowerCase().equals("mp3") || ext.toLowerCase().equals("m4a")) {
AudioInfo audioInfo = AudioInfo.getAudioInfo(f);
if (audioInfo != null && audioInfo.getDuration() != 0) {
if (isEncrypted) {
attributeAudio = new TLRPC.TL_documentAttributeAudio_old();
} else {
attributeAudio = new TLRPC.TL_documentAttributeAudio();
}
attributeAudio.duration = (int) (audioInfo.getDuration() / 1000);
attributeAudio.title = audioInfo.getTitle();
attributeAudio.performer = audioInfo.getArtist();
if (attributeAudio.title == null) {
attributeAudio.title = "";
}
if (attributeAudio.performer == null) {
attributeAudio.performer = "";
}
}
}
if (originalPath != null) {
originalPath += "" + f.length();
if (attributeAudio != null) {
originalPath += "audio" + f.length();
} else {
originalPath += "" + f.length();
}
}
TLRPC.TL_document document = null;
@ -1905,6 +1933,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
document.attributes.add(fileName);
document.size = (int) f.length();
document.dc_id = 0;
if (attributeAudio != null) {
document.attributes.add(attributeAudio);
}
if (ext.length() != 0) {
if (ext.toLowerCase().equals("webp")) {
document.mime_type = "image/webp";
@ -1990,6 +2021,56 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg);
}
public static void prepareSendingAudioDocuments(final ArrayList<MessageObject> messageObjects, final long dialog_id, final MessageObject reply_to_msg) {
new Thread(new Runnable() {
@Override
public void run() {
int size = messageObjects.size();
for (int a = 0; a < size; a++) {
final MessageObject messageObject = messageObjects.get(a);
String originalPath = messageObject.messageOwner.attachPath;
final File f = new File(originalPath);
boolean isEncrypted = (int) dialog_id == 0;
if (originalPath != null) {
originalPath += "audio" + f.length();
}
TLRPC.TL_document document = null;
if (!isEncrypted) {
document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
}
if (document == null) {
document = (TLRPC.TL_document) messageObject.messageOwner.media.document;
}
if (isEncrypted) {
for (int b = 0; b < document.attributes.size(); b++) {
if (document.attributes.get(b) instanceof TLRPC.TL_documentAttributeAudio) {
TLRPC.TL_documentAttributeAudio_old old = new TLRPC.TL_documentAttributeAudio_old();
old.duration = document.attributes.get(b).duration;
document.attributes.remove(b);
document.attributes.add(old);
break;
}
}
}
final String originalPathFinal = originalPath;
final TLRPC.TL_document documentFinal = document;
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(documentFinal, originalPathFinal, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg);
}
});
}
}
}).start();
}
public static void prepareSendingDocuments(final ArrayList<String> paths, final ArrayList<String> originalPaths, final ArrayList<Uri> uris, final String mime, final long dialog_id, final MessageObject reply_to_msg) {
if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) {
return;

View file

@ -8,6 +8,7 @@
package org.telegram.android;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC;
@ -29,7 +30,8 @@ public class UserObject {
if (user == null || isDeleted(user)) {
return LocaleController.getString("HiddenName", R.string.HiddenName);
}
return ContactsController.formatName(user.first_name, user.last_name);
String name = ContactsController.formatName(user.first_name, user.last_name);
return name.length() != 0 || user.phone == null || user.phone.length() == 0 ? name : PhoneFormat.getInstance().format("+" + user.phone);
}
public static String getFirstName(TLRPC.User user) {
@ -40,6 +42,6 @@ public class UserObject {
if (name == null || name.length() == 0) {
name = user.last_name;
}
return name != null && name.length() > 0 ? name : "DELETED";
return name != null && name.length() > 0 ? name : LocaleController.getString("HiddenName", R.string.HiddenName);
}
}

View file

@ -0,0 +1,154 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo;
import android.graphics.Bitmap;
import org.telegram.android.audioinfo.m4a.M4AInfo;
import org.telegram.android.audioinfo.mp3.MP3Info;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
public abstract class AudioInfo {
protected String brand; // brand, e.g. "M4A", "ID3", ...
protected String version; // version, e.g. "0", "2.3.0", ...
protected long duration; // track duration (milliseconds)
protected String title; // track title
protected String artist; // track artist
protected String albumArtist; // album artist
protected String album; // album title
protected short year; // year...
protected String genre; // genre name
protected String comment; // comment...
protected short track; // track number
protected short tracks; // number of tracks
protected short disc; // disc number
protected short discs; // number of discs
protected String copyright; // copyright notice
protected String composer; // composer name
protected String grouping; // track grouping
protected boolean compilation; // compilation flag
protected String lyrics; // song lyrics
protected Bitmap cover; // cover image data
protected Bitmap smallCover; // cover image data
public String getBrand() {
return brand;
}
public String getVersion() {
return version;
}
public long getDuration() {
return duration;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbumArtist() {
return albumArtist;
}
public String getAlbum() {
return album;
}
public short getYear() {
return year;
}
public String getGenre() {
return genre;
}
public String getComment() {
return comment;
}
public short getTrack() {
return track;
}
public short getTracks() {
return tracks;
}
public short getDisc() {
return disc;
}
public short getDiscs() {
return discs;
}
public String getCopyright() {
return copyright;
}
public String getComposer() {
return composer;
}
public String getGrouping() {
return grouping;
}
public boolean isCompilation() {
return compilation;
}
public String getLyrics() {
return lyrics;
}
public Bitmap getCover() {
return cover;
}
public Bitmap getSmallCover() {
return smallCover;
}
public static AudioInfo getAudioInfo(File file) {
try {
byte header[] = new byte[12];
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
randomAccessFile.readFully(header, 0, 8);
randomAccessFile.close();
InputStream input = new BufferedInputStream(new FileInputStream(file));
if (header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p') {
return new M4AInfo(input);
} else {
return new MP3Info(input, file.length());
}
} catch (Exception e) {
return null;
}
}
}

View file

@ -0,0 +1,325 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.android.audioinfo.mp3.ID3v1Genre;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.logging.Level;
import java.util.logging.Logger;
public class M4AInfo extends AudioInfo {
static final Logger LOGGER = Logger.getLogger(M4AInfo.class.getName());
private static final String ASCII = "ISO8859_1";
private static final String UTF_8 = "UTF-8";
private BigDecimal volume; // normal = 1.0
private BigDecimal speed; // normal = 1.0
private short tempo;
private byte rating; // none = 0, clean = 2, explicit = 4
private final Level debugLevel;
public M4AInfo(InputStream input) throws IOException {
this(input, Level.FINEST);
}
public M4AInfo(InputStream input, Level debugLevel) throws IOException {
this.debugLevel = debugLevel;
MP4Input mp4 = new MP4Input(input);
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, mp4.toString());
}
ftyp(mp4.nextChild("ftyp"));
moov(mp4.nextChildUpTo("moov"));
}
void ftyp(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
brand = atom.readString(4, ASCII).trim();
if (brand.matches("M4V|MP4|mp42|isom")) { // experimental file types
LOGGER.warning(atom.getPath() + ": brand=" + brand + " (experimental)");
} else if (!brand.matches("M4A|M4P")) {
LOGGER.warning(atom.getPath() + ": brand=" + brand + " (expected M4A or M4P)");
}
version = String.valueOf(atom.readInt());
}
void moov(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
switch (child.getType()) {
case "mvhd":
mvhd(child);
break;
case "trak":
trak(child);
break;
case "udta":
udta(child);
break;
default:
break;
}
}
}
void mvhd(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
byte version = atom.readByte();
atom.skip(3); // flags
atom.skip(version == 1 ? 16 : 8); // created/modified date
int scale = atom.readInt();
long units = version == 1 ? atom.readLong() : atom.readInt();
if (duration == 0) {
duration = 1000 * units / scale;
} else if (LOGGER.isLoggable(debugLevel) && Math.abs(duration - 1000 * units / scale) > 2) {
LOGGER.log(debugLevel, "mvhd: duration " + duration + " -> " + (1000 * units / scale));
}
speed = atom.readIntegerFixedPoint();
volume = atom.readShortFixedPoint();
}
void trak(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
mdia(atom.nextChildUpTo("mdia"));
}
void mdia(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
mdhd(atom.nextChild("mdhd"));
}
void mdhd(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
byte version = atom.readByte();
atom.skip(3);
atom.skip(version == 1 ? 16 : 8); // created/modified date
int sampleRate = atom.readInt();
long samples = version == 1 ? atom.readLong() : atom.readInt();
if (duration == 0) {
duration = 1000 * samples / sampleRate;
} else if (LOGGER.isLoggable(debugLevel) && Math.abs(duration - 1000 * samples / sampleRate) > 2) {
LOGGER.log(debugLevel, "mdhd: duration " + duration + " -> " + (1000 * samples / sampleRate));
}
}
void udta(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if ("meta".equals(child.getType())) {
meta(child);
break;
}
}
}
void meta(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
atom.skip(4); // version/flags
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if ("ilst".equals(child.getType())) {
ilst(child);
break;
}
}
}
void ilst(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, child.toString());
}
if (child.getRemaining() == 0) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, child.getPath() + ": contains no value");
}
continue;
}
data(child.nextChildUpTo("data"));
}
}
void data(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
atom.skip(4); // version & flags
atom.skip(4); // reserved
switch (atom.getParent().getType()) {
case "©alb":
album = atom.readString(UTF_8);
break;
case "aART":
albumArtist = atom.readString(UTF_8);
break;
case "©ART":
artist = atom.readString(UTF_8);
break;
case "©cmt":
comment = atom.readString(UTF_8);
break;
case "©com":
case "©wrt":
if (composer == null || composer.trim().length() == 0) {
composer = atom.readString(UTF_8);
}
break;
case "covr":
try {
byte[] bytes = atom.readBytes();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
opts.inSampleSize = 1;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (opts.outWidth > 800 || opts.outHeight > 800) {
int size = Math.max(opts.outWidth, opts.outHeight);
while (size > 800) {
opts.inSampleSize *= 2;
size /= 2;
}
}
opts.inJustDecodeBounds = false;
cover = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (cover != null) {
float scale = Math.max(cover.getWidth(), cover.getHeight()) / 120.0f;
if (scale > 0) {
smallCover = Bitmap.createScaledBitmap(cover, (int) (cover.getWidth() / scale), (int) (cover.getHeight() / scale), true);
} else {
smallCover = cover;
}
if (smallCover == null) {
smallCover = cover;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case "cpil":
compilation = atom.readBoolean();
break;
case "cprt":
case "©cpy":
if (copyright == null || copyright.trim().length() == 0) {
copyright = atom.readString(UTF_8);
}
break;
case "©day":
String day = atom.readString(UTF_8).trim();
if (day.length() >= 4) {
try {
year = Short.valueOf(day.substring(0, 4));
} catch (NumberFormatException e) {
// ignore
}
}
break;
case "disk":
atom.skip(2); // padding?
disc = atom.readShort();
discs = atom.readShort();
break;
case "gnre":
if (genre == null || genre.trim().length() == 0) {
if (atom.getRemaining() == 2) { // id3v1 genre?
int index = atom.readShort() - 1;
ID3v1Genre id3v1Genre = ID3v1Genre.getGenre(index);
if (id3v1Genre != null) {
genre = id3v1Genre.getDescription();
}
} else {
genre = atom.readString(UTF_8);
}
}
break;
case "©gen":
if (genre == null || genre.trim().length() == 0) {
genre = atom.readString(UTF_8);
}
break;
case "©grp":
grouping = atom.readString(UTF_8);
break;
case "©lyr":
lyrics = atom.readString(UTF_8);
break;
case "©nam":
title = atom.readString(UTF_8);
break;
case "rtng":
rating = atom.readByte();
break;
case "tmpo":
tempo = atom.readShort();
break;
case "trkn":
atom.skip(2); // padding?
track = atom.readShort();
tracks = atom.readShort();
break;
default:
break;
}
}
public short getTempo() {
return tempo;
}
public byte getRating() {
return rating;
}
public BigDecimal getSpeed() {
return speed;
}
public BigDecimal getVolume() {
return volume;
}
}

View file

@ -0,0 +1,151 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.math.BigDecimal;
public class MP4Atom extends MP4Box<RangeInputStream> {
public MP4Atom(RangeInputStream input, MP4Box<?> parent, String type) {
super(input, parent, type);
}
public long getLength() {
return getInput().getPosition() + getInput().getRemainingLength();
}
public long getOffset() {
return getParent().getPosition() - getPosition();
}
public long getRemaining() {
return getInput().getRemainingLength();
}
public boolean hasMoreChildren() {
return (getChild() != null ? getChild().getRemaining() : 0) < getRemaining();
}
public MP4Atom nextChildUpTo(String expectedTypeExpression) throws IOException {
while (getRemaining() > 0) {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
}
throw new IOException("atom type mismatch, not found: " + expectedTypeExpression);
}
public boolean readBoolean() throws IOException {
return data.readBoolean();
}
public byte readByte() throws IOException {
return data.readByte();
}
public short readShort() throws IOException {
return data.readShort();
}
public int readInt() throws IOException {
return data.readInt();
}
public long readLong() throws IOException {
return data.readLong();
}
public byte[] readBytes(int len) throws IOException {
byte[] bytes = new byte[len];
data.readFully(bytes);
return bytes;
}
public byte[] readBytes() throws IOException {
return readBytes((int) getRemaining());
}
public BigDecimal readShortFixedPoint() throws IOException {
int integer = data.readByte();
int decimal = data.readUnsignedByte();
return new BigDecimal(String.valueOf(integer) + "" + String.valueOf(decimal));
}
public BigDecimal readIntegerFixedPoint() throws IOException {
int integer = data.readShort();
int decimal = data.readUnsignedShort();
return new BigDecimal(String.valueOf(integer) + "" + String.valueOf(decimal));
}
public String readString(int len, String enc) throws IOException {
String s = new String(readBytes(len), enc);
int end = s.indexOf(0);
return end < 0 ? s : s.substring(0, end);
}
public String readString(String enc) throws IOException {
return readString((int) getRemaining(), enc);
}
public void skip(int len) throws IOException {
int total = 0;
while (total < len) {
int current = data.skipBytes(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public void skip() throws IOException {
while (getRemaining() > 0) {
if (getInput().skip(getRemaining()) == 0) {
throw new EOFException("Cannot skip atom");
}
}
}
private StringBuffer appendPath(StringBuffer s, MP4Box<?> box) {
if (box.getParent() != null) {
appendPath(s, box.getParent());
s.append("/");
}
return s.append(box.getType());
}
public String getPath() {
return appendPath(new StringBuffer(), this).toString();
}
public String toString() {
StringBuffer s = new StringBuffer();
appendPath(s, this);
s.append("[off=");
s.append(getOffset());
s.append(",pos=");
s.append(getPosition());
s.append(",len=");
s.append(getLength());
s.append("]");
return s.toString();
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.PositionInputStream;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
public class MP4Box<I extends PositionInputStream> {
protected static final String ASCII = "ISO8859_1";
private final I input;
private final MP4Box<?> parent;
private final String type;
protected final DataInput data;
private MP4Atom child;
public MP4Box(I input, MP4Box<?> parent, String type) {
this.input = input;
this.parent = parent;
this.type = type;
this.data = new DataInputStream(input);
}
public String getType() {
return type;
}
public MP4Box<?> getParent() {
return parent;
}
public long getPosition() {
return input.getPosition();
}
public I getInput() {
return input;
}
protected MP4Atom getChild() {
return child;
}
public MP4Atom nextChild() throws IOException {
if (child != null) {
child.skip();
}
int atomLength = data.readInt();
byte[] typeBytes = new byte[4];
data.readFully(typeBytes);
String atomType = new String(typeBytes, ASCII);
RangeInputStream atomInput;
if (atomLength == 1) { // extended length
atomInput = new RangeInputStream(input, 16, data.readLong() - 16);
} else {
atomInput = new RangeInputStream(input, 8, atomLength - 8);
}
return child = new MP4Atom(atomInput, this, atomType);
}
public MP4Atom nextChild(String expectedTypeExpression) throws IOException {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
throw new IOException("atom type mismatch, expected " + expectedTypeExpression + ", got " + atom.getType());
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.IOException;
import java.io.InputStream;
public final class MP4Input extends MP4Box<PositionInputStream> {
public MP4Input(InputStream delegate) {
super(new PositionInputStream(delegate), null, "");
}
public MP4Atom nextChildUpTo(String expectedTypeExpression) throws IOException {
while (true) {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
}
}
public String toString() {
return "mp4[pos=" + getPosition() + "]";
}
}

View file

@ -0,0 +1,171 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public enum ID3v1Genre {
/*
* The following genres is defined in ID3v1 (0-79)
*/
Blues("Blues"),
ClassicRock("Classic Rock"),
Country("Country"),
Dance("Dance"),
Disco("Disco"),
Funk("Funk"),
Grunge("Grunge"),
HipHop("Hip-Hop"),
Jazz("Jazz"),
Metal("Metal"),
NewAge("New Age"),
Oldies("Oldies"),
Other("Other"),
Pop("Pop"),
RnB("R&B"),
Rap("Rap"),
Reggae("Reggae"),
Rock("Rock"),
Techno("Techno"),
Industrial("Industrial"),
Alternative("Alternative"),
Ska("Ska"),
DeathMetal("Death Metal"),
Pranks("Pranks"),
Soundtrack("Soundtrack"),
EuroTechno("Euro-Techno"),
Ambient("Ambient"),
TripHop("Trip-Hop"),
Vocal("Vocal"),
JazzFunk("Jazz+Funk"),
Fusion("Fusion"),
Trance("Trance"),
Classical("Classical"),
Instrumental("Instrumental"),
Acid("Acid"),
House("House"),
Game("Game"),
SoundClip("Sound Clip"),
Gospel("Gospel"),
Noise("Noise"),
AlternRock("AlternRock"),
Bass("Bass"),
Soul("Soul"),
Punk("Punk"),
Space("Space"),
Meditative("Meditative"),
InstrumentalPop("Instrumental Pop"),
InstrumentalRock("Instrumental Rock"),
Ethnic("Ethnic"),
Gothic("Gothic"),
Darkwave("Darkwave"),
TechnoIndustrial("Techno-Industrial"),
Electronic("Electronic"),
PopFolk("Pop-Folk"),
Eurodance("Eurodance"),
Dream("Dream"),
SouthernRock("Southern Rock"),
Comedy("Comedy"),
Cult("Cult"),
Gangsta("Gangsta"),
Top40("Top 40"),
ChristianRap("Christian Rap"),
PopFunk("Pop/Funk"),
Jungle("Jungle"),
NativeAmerican("Native American"),
Cabaret("Cabaret"),
NewWave("New Wave"),
Psychadelic("Psychadelic"),
Rave("Rave"),
Showtunes("Showtunes"),
Trailer("Trailer"),
LoFi("Lo-Fi"),
Tribal("Tribal"),
AcidPunk("Acid Punk"),
AcidJazz("Acid Jazz"),
Polka("Polka"),
Retro("Retro"),
Musical("Musical"),
RockAndRoll("Rock & Roll"),
HardRock("Hard Rock"),
/*
* The following genres are Winamp extensions (80-125)
*/
Folk("Folk"),
FolkRock("Folk-Rock"),
NationalFolk("National Folk"),
Swing("Swing"),
FastFusion("Fast Fusion"),
Bebop("Bebop"),
Latin("Latin"),
Revival("Revival"),
Celtic("Celtic"),
Bluegrass("Bluegrass"),
Avantgarde("Avantgarde"),
GothicRock("Gothic Rock"),
ProgressiveRock("Progressive Rock"),
PsychedelicRock("Psychedelic Rock"),
SymphonicRock("Symphonic Rock"),
SlowRock("Slow Rock"),
BigBand("Big Band"),
Chorus("Chorus"),
EasyListening("Easy Listening"),
Acoustic("Acoustic"),
Humour("Humour"),
Speech("Speech"),
Chanson("Chanson"),
Opera("Opera"),
ChamberMusic("Chamber Music"),
Sonata("Sonata"),
Symphony("Symphony"),
BootyBass("Booty Bass"),
Primus("Primus"),
PornGroove("Porn Groove"),
Satire("Satire"),
SlowJam("Slow Jam"),
Club("Club"),
Tango("Tango"),
Samba("Samba"),
Folklore("Folklore"),
Ballad("Ballad"),
PowerBallad("Power Ballad"),
RhytmicSoul("Rhythmic Soul"),
Freestyle("Freestyle"),
Duet("Duet"),
PunkRock("Punk Rock"),
DrumSolo("Drum Solo"),
ACapella("A capella"),
EuroHouse("Euro-House"),
DanceHall("Dance Hall");
public static ID3v1Genre getGenre(int id) {
ID3v1Genre[] values = values();
return id >= 0 && id < values.length ? values[id] : null;
}
private final String description;
ID3v1Genre(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public int getId() {
return ordinal();
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.AudioInfo;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class ID3v1Info extends AudioInfo {
public static boolean isID3v1StartPosition(InputStream input) throws IOException {
input.mark(3);
try {
return input.read() == 'T' && input.read() == 'A' && input.read() == 'G';
} finally {
input.reset();
}
}
public ID3v1Info(InputStream input) throws IOException {
if (isID3v1StartPosition(input)) {
brand = "ID3";
version = "1.0";
byte[] bytes = readBytes(input, 128);
title = extractString(bytes, 3, 30);
artist = extractString(bytes, 33, 30);
album = extractString(bytes, 63, 30);
try {
year = Short.parseShort(extractString(bytes, 93, 4));
} catch (NumberFormatException e) {
year = 0;
}
comment = extractString(bytes, 97, 30);
ID3v1Genre id3v1Genre = ID3v1Genre.getGenre(bytes[127]);
if (id3v1Genre != null) {
genre = id3v1Genre.getDescription();
}
/*
* ID3v1.1
*/
if (bytes[125] == 0 && bytes[126] != 0) {
version = "1.1";
track = (short) (bytes[126] & 0xFF);
}
}
}
byte[] readBytes(InputStream input, int len) throws IOException {
int total = 0;
byte[] bytes = new byte[len];
while (total < len) {
int current = input.read(bytes, total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
return bytes;
}
String extractString(byte[] bytes, int offset, int length) {
try {
String text = new String(bytes, offset, length, "ISO-8859-1");
int zeroIndex = text.indexOf(0);
return zeroIndex < 0 ? text : text.substring(0, zeroIndex);
} catch (Exception e) {
return "";
}
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2DataInput {
private final InputStream input;
public ID3v2DataInput(InputStream in) {
this.input = in;
}
public final void readFully(byte b[], int off, int len) throws IOException {
int total = 0;
while (total < len) {
int current = input.read(b, off + total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public byte[] readFully(int len) throws IOException {
byte[] bytes = new byte[len];
readFully(bytes, 0, len);
return bytes;
}
public void skipFully(long len) throws IOException {
long total = 0;
while (total < len) {
long current = input.skip(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public byte readByte() throws IOException {
int b = input.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
public int readInt() throws IOException {
return ((readByte() & 0xFF) << 24) | ((readByte() & 0xFF) << 16) | ((readByte() & 0xFF) << 8) | (readByte() & 0xFF);
}
public int readSyncsafeInt() throws IOException {
return ((readByte() & 0x7F) << 21) | ((readByte() & 0x7F) << 14) | ((readByte() & 0x7F) << 7) | (readByte() & 0x7F);
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.nio.charset.Charset;
public enum ID3v2Encoding {
ISO_8859_1(Charset.forName("ISO-8859-1"), 1),
UTF_16(Charset.forName("UTF-16"), 2),
UTF_16BE(Charset.forName("UTF-16BE"), 2),
UTF_8(Charset.forName("UTF-8"), 1);
private final Charset charset;
private final int zeroBytes;
ID3v2Encoding(Charset charset, int zeroBytes) {
this.charset = charset;
this.zeroBytes = zeroBytes;
}
public Charset getCharset() {
return charset;
}
public int getZeroBytes() {
return zeroBytes;
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class ID3v2Exception extends Exception {
private static final long serialVersionUID = 1L;
public ID3v2Exception(String message) {
super(message);
}
}

View file

@ -0,0 +1,155 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2FrameBody {
static final class Buffer {
byte[] bytes;
Buffer(int initialLength) {
bytes = new byte[initialLength];
}
byte[] bytes(int minLength) {
if (minLength > bytes.length) {
int length = bytes.length * 2;
while (minLength > length) {
length *= 2;
}
bytes = new byte[length];
}
return bytes;
}
}
static final ThreadLocal<Buffer> textBuffer = new ThreadLocal<Buffer>() {
@Override
protected Buffer initialValue() {
return new Buffer(4096);
}
};
private final RangeInputStream input;
private final ID3v2TagHeader tagHeader;
private final ID3v2FrameHeader frameHeader;
private final ID3v2DataInput data;
ID3v2FrameBody(InputStream delegate, long position, int dataLength, ID3v2TagHeader tagHeader, ID3v2FrameHeader frameHeader) throws IOException {
this.input = new RangeInputStream(delegate, position, dataLength);
this.data = new ID3v2DataInput(input);
this.tagHeader = tagHeader;
this.frameHeader = frameHeader;
}
public ID3v2DataInput getData() {
return data;
}
public long getPosition() {
return input.getPosition();
}
public long getRemainingLength() {
return input.getRemainingLength();
}
public ID3v2TagHeader getTagHeader() {
return tagHeader;
}
public ID3v2FrameHeader getFrameHeader() {
return frameHeader;
}
private String extractString(byte[] bytes, int offset, int length, ID3v2Encoding encoding, boolean searchZeros) {
if (searchZeros) {
int zeros = 0;
for (int i = 0; i < length; i++) {
// UTF-16LE may have a zero byte as second byte of a 2-byte character -> skip first zero at odd index
if (bytes[offset + i] == 0 && (encoding != ID3v2Encoding.UTF_16 || zeros != 0 || (offset + i) % 2 == 0)) {
if (++zeros == encoding.getZeroBytes()) {
length = i + 1 - encoding.getZeroBytes();
break;
}
} else {
zeros = 0;
}
}
}
try {
String string = new String(bytes, offset, length, encoding.getCharset().name());
if (string.length() > 0 && string.charAt(0) == '\uFEFF') { // remove BOM
string = string.substring(1);
}
return string;
} catch (Exception e) {
return "";
}
}
public String readZeroTerminatedString(int maxLength, ID3v2Encoding encoding) throws IOException, ID3v2Exception {
int zeros = 0;
int length = Math.min(maxLength, (int) getRemainingLength());
byte[] bytes = textBuffer.get().bytes(length);
for (int i = 0; i < length; i++) {
// UTF-16LE may have a zero byte as second byte of a 2-byte character -> skip first zero at odd index
if ((bytes[i] = data.readByte()) == 0 && (encoding != ID3v2Encoding.UTF_16 || zeros != 0 || i % 2 == 0)) {
if (++zeros == encoding.getZeroBytes()) {
return extractString(bytes, 0, i + 1 - encoding.getZeroBytes(), encoding, false);
}
} else {
zeros = 0;
}
}
throw new ID3v2Exception("Could not read zero-termiated string");
}
public String readFixedLengthString(int length, ID3v2Encoding encoding) throws IOException, ID3v2Exception {
if (length > getRemainingLength()) {
throw new ID3v2Exception("Could not read fixed-length string of length: " + length);
}
byte[] bytes = textBuffer.get().bytes(length);
data.readFully(bytes, 0, length);
return extractString(bytes, 0, length, encoding, true);
}
public ID3v2Encoding readEncoding() throws IOException, ID3v2Exception {
byte value = data.readByte();
switch (value) {
case 0:
return ID3v2Encoding.ISO_8859_1;
case 1:
return ID3v2Encoding.UTF_16;
case 2:
return ID3v2Encoding.UTF_16BE;
case 3:
return ID3v2Encoding.UTF_8;
default:
break;
}
throw new ID3v2Exception("Invalid encoding: " + value);
}
public String toString() {
return "id3v2frame[pos=" + getPosition() + ", " + getRemainingLength() + " left]";
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.io.IOException;
public class ID3v2FrameHeader {
private String frameId;
private int headerSize;
private int bodySize;
private boolean unsynchronization;
private boolean compression;
private boolean encryption;
private int dataLengthIndicator;
public ID3v2FrameHeader(ID3v2TagBody input) throws IOException, ID3v2Exception {
long startPosition = input.getPosition();
ID3v2DataInput data = input.getData();
/*
* Frame Id
*/
if (input.getTagHeader().getVersion() == 2) { // $xx xx xx (three characters)
frameId = new String(data.readFully(3), "ISO-8859-1");
} else { // $xx xx xx xx (four characters)
frameId = new String(data.readFully(4), "ISO-8859-1");
}
/*
* Size
*/
if (input.getTagHeader().getVersion() == 2) { // $xx xx xx
bodySize = ((data.readByte() & 0xFF) << 16) | ((data.readByte() & 0xFF) << 8) | (data.readByte() & 0xFF);
} else if (input.getTagHeader().getVersion() == 3) { // $xx xx xx xx
bodySize = data.readInt();
} else { // 4 * %0xxxxxxx (sync-save integer)
bodySize = data.readSyncsafeInt();
}
/*
* Flags
*/
if (input.getTagHeader().getVersion() > 2) { // $xx xx
data.readByte(); // status flags
byte formatFlags = data.readByte();
int compressionMask;
int encryptionMask;
int groupingIdentityMask;
int unsynchronizationMask = 0x00;
int dataLengthIndicatorMask = 0x00;
if (input.getTagHeader().getVersion() == 3) { // %(compression)(encryption)(groupingIdentity)00000
compressionMask = 0x80;
encryptionMask = 0x40;
groupingIdentityMask = 0x20;
} else { // %0(groupingIdentity)00(compression)(encryption)(unsynchronization)(dataLengthIndicator)
groupingIdentityMask = 0x40;
compressionMask = 0x08;
encryptionMask = 0x04;
unsynchronizationMask = 0x02;
dataLengthIndicatorMask = 0x01;
}
compression = (formatFlags & compressionMask) != 0;
unsynchronization = (formatFlags & unsynchronizationMask) != 0;
encryption = (formatFlags & encryptionMask) != 0;
/*
* Read flag attachments in the order of the flags (version dependent).
*/
if (input.getTagHeader().getVersion() == 3) {
if (compression) {
dataLengthIndicator = data.readInt();
bodySize -= 4;
}
if (encryption) {
data.readByte(); // just skip
bodySize -= 1;
}
if ((formatFlags & groupingIdentityMask) != 0) {
data.readByte(); // just skip
bodySize -= 1;
}
} else {
if ((formatFlags & groupingIdentityMask) != 0) {
data.readByte(); // just skip
bodySize -= 1;
}
if (encryption) {
data.readByte(); // just skip
bodySize -= 1;
}
if ((formatFlags & dataLengthIndicatorMask) != 0) {
dataLengthIndicator = data.readSyncsafeInt();
bodySize -= 4;
}
}
}
headerSize = (int) (input.getPosition() - startPosition);
}
public String getFrameId() {
return frameId;
}
public int getHeaderSize() {
return headerSize;
}
public int getBodySize() {
return bodySize;
}
public boolean isCompression() {
return compression;
}
public boolean isEncryption() {
return encryption;
}
public boolean isUnsynchronization() {
return unsynchronization;
}
public int getDataLengthIndicator() {
return dataLengthIndicator;
}
public boolean isValid() {
for (int i = 0; i < frameId.length(); i++) {
if ((frameId.charAt(i) < 'A' || frameId.charAt(i) > 'Z') && (frameId.charAt(i) < '0' || frameId.charAt(i) > '9')) {
return false;
}
}
return bodySize > 0;
}
public boolean isPadding() {
for (int i = 0; i < frameId.length(); i++) {
if (frameId.charAt(0) != 0) {
return false;
}
}
return bodySize == 0;
}
@Override
public String toString() {
return String.format("%s[id=%s, bodysize=%d]", getClass().getSimpleName(), frameId, bodySize);
}
}

View file

@ -0,0 +1,376 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.telegram.android.audioinfo.AudioInfo;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ID3v2Info extends AudioInfo {
static final Logger LOGGER = Logger.getLogger(ID3v2Info.class.getName());
static class AttachedPicture {
static final byte TYPE_OTHER = 0x00;
static final byte TYPE_COVER_FRONT = 0x03;
final byte type;
final String description;
final String imageType;
final byte[] imageData;
public AttachedPicture(byte type, String description, String imageType, byte[] imageData) {
this.type = type;
this.description = description;
this.imageType = imageType;
this.imageData = imageData;
}
}
static class CommentOrUnsynchronizedLyrics {
final String language;
final String description;
final String text;
public CommentOrUnsynchronizedLyrics(String language, String description, String text) {
this.language = language;
this.description = description;
this.text = text;
}
}
public static boolean isID3v2StartPosition(InputStream input) throws IOException {
input.mark(3);
try {
return input.read() == 'I' && input.read() == 'D' && input.read() == '3';
} finally {
input.reset();
}
}
private final Level debugLevel;
private byte coverPictureType;
public ID3v2Info(InputStream input) throws IOException, ID3v2Exception {
this(input, Level.FINEST);
}
public ID3v2Info(InputStream input, Level debugLevel) throws IOException, ID3v2Exception {
this.debugLevel = debugLevel;
if (isID3v2StartPosition(input)) {
ID3v2TagHeader tagHeader = new ID3v2TagHeader(input);
brand = "ID3";
version = String.format("2.%d.%d", tagHeader.getVersion(), tagHeader.getRevision());
ID3v2TagBody tagBody = tagHeader.tagBody(input);
try {
while (tagBody.getRemainingLength() > 10) { // TODO > tag.minimumFrameSize()
ID3v2FrameHeader frameHeader = new ID3v2FrameHeader(tagBody);
if (frameHeader.isPadding()) { // we ran into padding
break;
}
if (frameHeader.getBodySize() > tagBody.getRemainingLength()) { // something wrong...
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "ID3 frame claims to extend frames area");
}
break;
}
if (frameHeader.isValid() && !frameHeader.isEncryption()) {
ID3v2FrameBody frameBody = tagBody.frameBody(frameHeader);
try {
parseFrame(frameBody);
} catch (ID3v2Exception e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, String.format("ID3 exception occured in frame %s: %s", frameHeader.getFrameId(), e.getMessage()));
}
} finally {
frameBody.getData().skipFully(frameBody.getRemainingLength());
}
} else {
tagBody.getData().skipFully(frameHeader.getBodySize());
}
}
} catch (ID3v2Exception e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "ID3 exception occured: " + e.getMessage());
}
}
tagBody.getData().skipFully(tagBody.getRemainingLength());
if (tagHeader.getFooterSize() > 0) {
input.skip(tagHeader.getFooterSize());
}
}
}
void parseFrame(ID3v2FrameBody frame) throws IOException, ID3v2Exception {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Parsing frame: " + frame.getFrameHeader().getFrameId());
}
switch (frame.getFrameHeader().getFrameId()) {
case "PIC":
case "APIC": // cover: prefer TYPE_COVER_FRONT, then TYPE_OTHER, then anything else
if (cover == null || coverPictureType != AttachedPicture.TYPE_COVER_FRONT) {
AttachedPicture picture = parseAttachedPictureFrame(frame);
if (cover == null || picture.type == AttachedPicture.TYPE_COVER_FRONT || picture.type == AttachedPicture.TYPE_OTHER) {
try {
byte[] bytes = picture.imageData;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
opts.inSampleSize = 1;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (opts.outWidth > 800 || opts.outHeight > 800) {
int size = Math.max(opts.outWidth, opts.outHeight);
while (size > 800) {
opts.inSampleSize *= 2;
size /= 2;
}
}
opts.inJustDecodeBounds = false;
cover = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (cover != null) {
float scale = Math.max(cover.getWidth(), cover.getHeight()) / 120.0f;
if (scale > 0) {
smallCover = Bitmap.createScaledBitmap(cover, (int) (cover.getWidth() / scale), (int) (cover.getHeight() / scale), true);
} else {
smallCover = cover;
}
if (smallCover == null) {
smallCover = cover;
}
}
} catch (Exception e) {
e.printStackTrace();
}
coverPictureType = picture.type;
}
}
break;
case "COM":
case "COMM":
CommentOrUnsynchronizedLyrics comm = parseCommentOrUnsynchronizedLyricsFrame(frame);
if (comment == null || comm.description == null || "".equals(comm.description)) { // prefer "default" comment (without description)
comment = comm.text;
}
break;
case "TAL":
case "TALB":
album = parseTextFrame(frame);
break;
case "TCP":
case "TCMP":
compilation = "1".equals(parseTextFrame(frame));
break;
case "TCM":
case "TCOM":
composer = parseTextFrame(frame);
break;
case "TCO":
case "TCON":
String tcon = parseTextFrame(frame);
if (tcon.length() > 0) {
genre = tcon;
try {
ID3v1Genre id3v1Genre = null;
if (tcon.charAt(0) == '(') {
int pos = tcon.indexOf(')');
if (pos > 1) { // (123)
id3v1Genre = ID3v1Genre.getGenre(Integer.parseInt(tcon.substring(1, pos)));
if (id3v1Genre == null && tcon.length() > pos + 1) { // (789)Special
genre = tcon.substring(pos + 1);
}
}
} else { // 123
id3v1Genre = ID3v1Genre.getGenre(Integer.parseInt(tcon));
}
if (id3v1Genre != null) {
genre = id3v1Genre.getDescription();
}
} catch (NumberFormatException e) {
// ignore
}
}
break;
case "TCR":
case "TCOP":
copyright = parseTextFrame(frame);
break;
case "TDRC": // v2.4, replaces TYER
String tdrc = parseTextFrame(frame);
if (tdrc.length() >= 4) {
try {
year = Short.valueOf(tdrc.substring(0, 4));
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse year from: " + tdrc);
}
}
}
break;
case "TLE":
case "TLEN":
String tlen = parseTextFrame(frame);
try {
duration = Long.valueOf(tlen);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse track duration: " + tlen);
}
}
break;
case "TP1":
case "TPE1":
artist = parseTextFrame(frame);
break;
case "TP2":
case "TPE2":
albumArtist = parseTextFrame(frame);
break;
case "TPA":
case "TPOS":
String tpos = parseTextFrame(frame);
if (tpos.length() > 0) {
int index = tpos.indexOf('/');
if (index < 0) {
try {
disc = Short.valueOf(tpos);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse disc number: " + tpos);
}
}
} else {
try {
disc = Short.valueOf(tpos.substring(0, index));
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse disc number: " + tpos);
}
}
try {
discs = Short.valueOf(tpos.substring(index + 1));
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse number of discs: " + tpos);
}
}
}
}
break;
case "TRK":
case "TRCK":
String trck = parseTextFrame(frame);
if (trck.length() > 0) {
int index = trck.indexOf('/');
if (index < 0) {
try {
track = Short.valueOf(trck);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse track number: " + trck);
}
}
} else {
try {
track = Short.valueOf(trck.substring(0, index));
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse track number: " + trck);
}
}
try {
tracks = Short.valueOf(trck.substring(index + 1));
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse number of tracks: " + trck);
}
}
}
}
break;
case "TT1":
case "TIT1":
grouping = parseTextFrame(frame);
break;
case "TT2":
case "TIT2":
title = parseTextFrame(frame);
break;
case "TYE":
case "TYER":
String tyer = parseTextFrame(frame);
if (tyer.length() > 0) {
try {
year = Short.valueOf(tyer);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not parse year: " + tyer);
}
}
}
break;
case "ULT":
case "USLT":
if (lyrics == null) {
lyrics = parseCommentOrUnsynchronizedLyricsFrame(frame).text;
}
break;
default:
break;
}
}
String parseTextFrame(ID3v2FrameBody frame) throws IOException, ID3v2Exception {
ID3v2Encoding encoding = frame.readEncoding();
return frame.readFixedLengthString((int) frame.getRemainingLength(), encoding);
}
CommentOrUnsynchronizedLyrics parseCommentOrUnsynchronizedLyricsFrame(ID3v2FrameBody data) throws IOException, ID3v2Exception {
ID3v2Encoding encoding = data.readEncoding();
String language = data.readFixedLengthString(3, ID3v2Encoding.ISO_8859_1);
String description = data.readZeroTerminatedString(200, encoding);
String text = data.readFixedLengthString((int) data.getRemainingLength(), encoding);
return new CommentOrUnsynchronizedLyrics(language, description, text);
}
AttachedPicture parseAttachedPictureFrame(ID3v2FrameBody data) throws IOException, ID3v2Exception {
ID3v2Encoding encoding = data.readEncoding();
String imageType;
if (data.getTagHeader().getVersion() == 2) { // file type, e.g. "JPG"
String fileType = data.readFixedLengthString(3, ID3v2Encoding.ISO_8859_1);
switch (fileType.toUpperCase()) {
case "PNG":
imageType = "image/png";
break;
case "JPG":
imageType = "image/jpeg";
break;
default:
imageType = "image/unknown";
}
} else { // mime type, e.g. "image/jpeg"
imageType = data.readZeroTerminatedString(20, ID3v2Encoding.ISO_8859_1);
}
byte pictureType = data.getData().readByte();
String description = data.readZeroTerminatedString(200, encoding);
byte[] imageData = data.getData().readFully((int) data.getRemainingLength());
return new AttachedPicture(pictureType, description, imageType, imageData);
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.InflaterInputStream;
public class ID3v2TagBody {
private final RangeInputStream input;
private final ID3v2TagHeader tagHeader;
private final ID3v2DataInput data;
ID3v2TagBody(InputStream delegate, long position, int length, ID3v2TagHeader tagHeader) throws IOException {
this.input = new RangeInputStream(delegate, position, length);
this.data = new ID3v2DataInput(input);
this.tagHeader = tagHeader;
}
public ID3v2DataInput getData() {
return data;
}
public long getPosition() {
return input.getPosition();
}
public long getRemainingLength() {
return input.getRemainingLength();
}
public ID3v2TagHeader getTagHeader() {
return tagHeader;
}
public ID3v2FrameBody frameBody(ID3v2FrameHeader frameHeader) throws IOException, ID3v2Exception {
int dataLength = frameHeader.getBodySize();
InputStream input = this.input;
if (frameHeader.isUnsynchronization()) {
byte[] bytes = data.readFully(frameHeader.getBodySize());
boolean ff = false;
int len = 0;
for (byte b : bytes) {
if (!ff || b != 0) {
bytes[len++] = b;
}
ff = (b == 0xFF);
}
dataLength = len;
input = new ByteArrayInputStream(bytes, 0, len);
}
if (frameHeader.isEncryption()) {
throw new ID3v2Exception("Frame encryption is not supported");
}
if (frameHeader.isCompression()) {
dataLength = frameHeader.getDataLengthIndicator();
input = new InflaterInputStream(input);
}
return new ID3v2FrameBody(input, frameHeader.getHeaderSize(), dataLength, tagHeader, frameHeader);
}
public String toString() {
return "id3v2tag[pos=" + getPosition() + ", " + getRemainingLength() + " left]";
}
}

View file

@ -0,0 +1,191 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2TagHeader {
private int version = 0;
private int revision = 0;
private int headerSize = 0; // size of header, including extended header (with attachments)
private int totalTagSize = 0; // everything, i.e. inluding tag header, extended header, footer & padding
private int paddingSize = 0; // size of zero padding after frames
private int footerSize = 0; // size of footer (version 4 only)
private boolean unsynchronization;
private boolean compression;
public ID3v2TagHeader(InputStream input) throws IOException, ID3v2Exception {
this(new PositionInputStream(input));
}
ID3v2TagHeader(PositionInputStream input) throws IOException, ID3v2Exception {
long startPosition = input.getPosition();
ID3v2DataInput data = new ID3v2DataInput(input);
/*
* Identifier: "ID3"
*/
String id = new String(data.readFully(3), "ISO-8859-1");
if (!"ID3".equals(id)) {
throw new ID3v2Exception("Invalid ID3 identifier: " + id);
}
/*
* Version: $02, $03 or $04
*/
version = data.readByte();
if (version != 2 && version != 3 && version != 4) {
throw new ID3v2Exception("Unsupported ID3v2 version: " + version);
}
/*
* Revision: $xx
*/
revision = data.readByte();
/*
* Flags (evaluated below)
*/
byte flags = data.readByte();
/*
* Size: 4 * %0xxxxxxx (sync-save integer)
*/
totalTagSize = 10 + data.readSyncsafeInt();
/*
* Evaluate flags
*/
if (version == 2) { // %(unsynchronisation)(compression)000000
unsynchronization = (flags & 0x80) != 0;
compression = (flags & 0x40) != 0;
} else { // %(unsynchronisation)(extendedHeader)(experimentalIndicator)(version == 3 ? 0 : footerPresent)0000
unsynchronization = (flags & 0x80) != 0;
/*
* Extended Header
*/
if ((flags & 0x40) != 0) {
if (version == 3) {
/*
* Extended header size: $xx xx xx xx (6 or 10 if CRC data present)
* In version 3, the size excludes itself.
*/
int extendedHeaderSize = data.readInt();
/*
* Extended Flags: $xx xx (skip)
*/
data.readByte(); // flags...
data.readByte(); // more flags...
/*
* Size of padding: $xx xx xx xx
*/
paddingSize = data.readInt();
/*
* consume the rest
*/
data.skipFully(extendedHeaderSize - 6);
} else {
/*
* Extended header size: 4 * %0xxxxxxx (sync-save integer)
* In version 4, the size includes itself.
*/
int extendedHeaderSize = data.readSyncsafeInt();
/*
* consume the rest
*/
data.skipFully(extendedHeaderSize - 4);
}
}
/*
* Footer Present
*/
if (version >= 4 && (flags & 0x10) != 0) { // footer present
footerSize = 10;
totalTagSize += 10;
}
}
headerSize = (int) (input.getPosition() - startPosition);
}
public ID3v2TagBody tagBody(InputStream input) throws IOException, ID3v2Exception {
if (compression) {
throw new ID3v2Exception("Tag compression is not supported");
}
if (version < 4 && unsynchronization) {
byte[] bytes = new ID3v2DataInput(input).readFully(totalTagSize - headerSize);
boolean ff = false;
int len = 0;
for (byte b : bytes) {
if (!ff || b != 0) {
bytes[len++] = b;
}
ff = (b == 0xFF);
}
return new ID3v2TagBody(new ByteArrayInputStream(bytes, 0, len), headerSize, len, this);
} else {
return new ID3v2TagBody(input, headerSize, totalTagSize - headerSize - footerSize, this);
}
}
public int getVersion() {
return version;
}
public int getRevision() {
return revision;
}
public int getTotalTagSize() {
return totalTagSize;
}
public boolean isUnsynchronization() {
return unsynchronization;
}
public boolean isCompression() {
return compression;
}
public int getHeaderSize() {
return headerSize;
}
public int getFooterSize() {
return footerSize;
}
public int getPaddingSize() {
return paddingSize;
}
@Override
public String toString() {
return String.format("%s[version=%s, totalTagSize=%d]", getClass().getSimpleName(), version, totalTagSize);
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class MP3Exception extends Exception {
private static final long serialVersionUID = 1L;
public MP3Exception(String message) {
super(message);
}
}

View file

@ -0,0 +1,315 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class MP3Frame {
static final class CRC16 {
private short crc = (short) 0xFFFF;
public void update(int value, int length) {
int mask = 1 << (length - 1);
do {
if (((crc & 0x8000) == 0) ^ ((value & mask) == 0)) {
crc <<= 1;
crc ^= 0x8005;
} else {
crc <<= 1;
}
} while ((mask >>>= 1) != 0);
}
public void update(byte value) {
update(value, 8);
}
public short getValue() {
return crc;
}
public void reset() {
crc = (short) 0xFFFF;
}
}
public static class Header {
private static final int MPEG_LAYER_RESERVED = 0;
private static final int MPEG_VERSION_RESERVED = 1;
private static final int MPEG_BITRATE_FREE = 0;
private static final int MPEG_BITRATE_RESERVED = 15;
private static final int MPEG_FRQUENCY_RESERVED = 3;
// [frequency][version]
private static final int[][] FREQUENCIES = new int[][] {
// 2.5 reserved 2 1
{ 11025, -1, 22050, 44100 },
{ 12000, -1, 24000, 48000 },
{ 8000, -1, 16000, 32000 },
{ -1, -1, -1, -1 } // reserved
};
// [bitrate][version,layer]
private static final int[][] BITRATES = new int[][] {
{ 0, 0, 0, 0, 0 }, // free
{ 32000, 32000, 32000, 32000, 8000 },
{ 64000, 48000, 40000, 48000, 16000 },
{ 96000, 56000, 48000, 56000, 24000 },
{ 128000, 64000, 56000, 64000, 32000 },
{ 160000, 80000, 64000, 80000, 40000 },
{ 192000, 96000, 80000, 96000, 48000 },
{ 224000, 112000, 96000, 112000, 56000 },
{ 256000, 128000, 112000, 128000, 64000 },
{ 288000, 160000, 128000, 144000, 80000 },
{ 320000, 192000, 160000, 160000, 96000 },
{ 352000, 224000, 192000, 176000, 112000 },
{ 384000, 256000, 224000, 192000, 128000 },
{ 416000, 320000, 256000, 224000, 144000 },
{ 448000, 384000, 320000, 256000, 160000 },
{ -1, -1, -1, -1, -1 } // reserved
};
// [version][layer]
private static final int[][] BITRATES_COLUMN = new int[][] {
// reserved III II I
{ -1, 4, 4, 3 }, // 2.5
{ -1, -1, -1, -1 }, // reserved
{ -1, 4, 4, 3 }, // 2
{ -1, 2, 1, 0 } // 1
};
// [version][layer]
private static final int[][] SIZE_COEFFICIENTS = new int[][] {
// reserved III II I
{ -1, 72, 144, 12 }, // 2.5
{ -1, -1, -1, -1 }, // reserved
{ -1, 72, 144, 12 }, // 2
{ -1, 144, 144, 12 } // 1
};
// [layer]
private static final int[] SLOT_SIZES = new int[] {
// reserved III II I
-1, 1, 1, 4
};
// [channelMode][version]
private static final int[][] SIDE_INFO_SIZES = new int[][] {
// 2.5 reserved 2 1
{ 17, -1, 17, 32 }, // stereo
{ 17, -1, 17, 32 }, // joint stereo
{ 17, -1, 17, 32 }, // dual channel
{ 9, -1, 9, 17 }, // mono
};
public static final int MPEG_LAYER_1 = 3;
public static final int MPEG_LAYER_2 = 2;
public static final int MPEG_LAYER_3 = 1;
public static final int MPEG_VERSION_1 = 3;
public static final int MPEG_VERSION_2 = 2;
public static final int MPEG_VERSION_2_5 = 0;
public static final int MPEG_CHANNEL_MODE_MONO = 3;
public static final int MPEG_PROTECTION_CRC = 0;
private final int version;
private final int layer;
private final int frequency;
private final int bitrate;
private final int channelMode;
private final int padding;
private final int protection;
public Header(int b1, int b2, int b3) throws MP3Exception {
version = b1 >> 3 & 0x3;
if (version == MPEG_VERSION_RESERVED) {
throw new MP3Exception("Reserved version");
}
layer = b1 >> 1 & 0x3;
if (layer == MPEG_LAYER_RESERVED) {
throw new MP3Exception("Reserved layer");
}
bitrate = b2 >> 4 & 0xF;
if (bitrate == MPEG_BITRATE_RESERVED) {
throw new MP3Exception("Reserved bitrate");
}
if (bitrate == MPEG_BITRATE_FREE) {
throw new MP3Exception("Free bitrate");
}
frequency = b2 >> 2 & 0x3;
if (frequency == MPEG_FRQUENCY_RESERVED) {
throw new MP3Exception("Reserved frequency");
}
channelMode = b3 >> 6 & 0x3;
padding = b2 >> 1 & 0x1;
protection = b1 & 0x1;
int minFrameSize = 4;
if (protection == MPEG_PROTECTION_CRC) {
minFrameSize += 2;
}
if (layer == MPEG_LAYER_3) {
minFrameSize += getSideInfoSize();
}
if (getFrameSize() < minFrameSize) {
throw new MP3Exception("Frame size must be at least " + minFrameSize);
}
}
public int getVersion() {
return version;
}
public int getLayer() {
return layer;
}
public int getFrequency() {
return FREQUENCIES[frequency][version];
}
public int getChannelMode() {
return channelMode;
}
public int getProtection() {
return protection;
}
public int getSampleCount() {
if (layer == MPEG_LAYER_1) {
return 384;
} else { // TODO correct?
return 1152;
}
}
public int getFrameSize() {
return ((SIZE_COEFFICIENTS[version][layer] * getBitrate() / getFrequency()) + padding) * SLOT_SIZES[layer];
}
public int getBitrate() {
return BITRATES[bitrate][BITRATES_COLUMN[version][layer]];
}
public int getDuration() {
return (int)getTotalDuration(getFrameSize());
}
public long getTotalDuration(long totalSize) {
long duration = 1000L * (getSampleCount() * totalSize) / (getFrameSize() * getFrequency());
if (getVersion() != MPEG_VERSION_1 && getChannelMode() == MPEG_CHANNEL_MODE_MONO) {
duration /= 2;
}
return duration;
}
public boolean isCompatible(Header header) {
return layer == header.layer && version == header.version && frequency == header.frequency && channelMode == header.channelMode;
}
public int getSideInfoSize() {
return SIDE_INFO_SIZES[channelMode][version];
}
public int getXingOffset() {
return 4 + getSideInfoSize();
}
public int getVBRIOffset() {
return 4 + 32;
}
}
private final byte[] bytes;
private final Header header;
MP3Frame(Header header, byte[] bytes) {
this.header = header;
this.bytes = bytes;
}
boolean isChecksumError() {
if (header.getProtection() == Header.MPEG_PROTECTION_CRC) {
if (header.getLayer() == Header.MPEG_LAYER_3) {
CRC16 crc16 = new CRC16();
crc16.update(bytes[2]);
crc16.update(bytes[3]);
// skip crc bytes 4+5
int sideInfoSize = header.getSideInfoSize();
for (int i = 0; i < sideInfoSize; i++) {
crc16.update(bytes[6 + i]);
}
int crc = ((bytes[4] & 0xFF) << 8) | (bytes[5] & 0xFF);
return crc != crc16.getValue();
}
}
return false;
}
public int getSize() {
return bytes.length;
}
public Header getHeader() {
return header;
}
boolean isXingFrame() {
int xingOffset = header.getXingOffset();
if (bytes.length < xingOffset + 12) { // minimum Xing header size == 12
return false;
}
if (xingOffset < 0 || bytes.length < xingOffset + 8) {
return false;
}
if (bytes[xingOffset] == 'X' && bytes[xingOffset + 1] == 'i' && bytes[xingOffset + 2] == 'n' && bytes[xingOffset + 3] == 'g') {
return true;
}
if (bytes[xingOffset] == 'I' && bytes[xingOffset + 1] == 'n' && bytes[xingOffset + 2] == 'f' && bytes[xingOffset + 3] == 'o') {
return true;
}
return false;
}
boolean isVBRIFrame() {
int vbriOffset = header.getVBRIOffset();
if (bytes.length < vbriOffset + 26) { // minimum VBRI header size == 26
return false;
}
return bytes[vbriOffset] == 'V' && bytes[vbriOffset + 1] == 'B' && bytes[vbriOffset + 2] == 'R' && bytes[vbriOffset + 3] == 'I';
}
public int getNumberOfFrames() {
if (isXingFrame()) {
int xingOffset = header.getXingOffset();
byte flags = bytes[xingOffset + 7];
if ((flags & 0x01) != 0) {
return ((bytes[xingOffset + 8] & 0xFF) << 24) |
((bytes[xingOffset + 9] & 0xFF) << 16) |
((bytes[xingOffset + 10] & 0xFF) << 8) |
( bytes[xingOffset + 11] & 0xFF);
}
} else if (isVBRIFrame()) {
int vbriOffset = header.getVBRIOffset();
return ((bytes[vbriOffset + 14] & 0xFF) << 24) |
((bytes[vbriOffset + 15] & 0xFF) << 16) |
((bytes[vbriOffset + 16] & 0xFF) << 8) |
( bytes[vbriOffset + 17] & 0xFF);
}
return -1;
}
}

View file

@ -0,0 +1,270 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.AudioInfo;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MP3Info extends AudioInfo {
static final Logger LOGGER = Logger.getLogger(MP3Info.class.getName());
interface StopReadCondition {
boolean stopRead(MP3Input data) throws IOException;
}
public MP3Info(InputStream input, long fileLength) throws IOException, ID3v2Exception, MP3Exception {
this(input, fileLength, Level.FINEST);
}
public MP3Info(InputStream input, final long fileLength, Level debugLevel) throws IOException, ID3v2Exception, MP3Exception {
brand = "MP3";
version = "0";
MP3Input data = new MP3Input(input);
if (ID3v2Info.isID3v2StartPosition(data)) {
ID3v2Info info = new ID3v2Info(data, debugLevel);
album = info.getAlbum();
albumArtist = info.getAlbumArtist();
artist = info.getArtist();
comment = info.getComment();
cover = info.getCover();
smallCover = info.getSmallCover();
compilation = info.isCompilation();
composer = info.getComposer();
copyright = info.getCopyright();
disc = info.getDisc();
discs = info.getDiscs();
duration = info.getDuration();
genre = info.getGenre();
grouping = info.getGrouping();
lyrics = info.getLyrics();
title = info.getTitle();
track = info.getTrack();
tracks = info.getTracks();
year = info.getYear();
}
if (duration <= 0 || duration >= 3600000L) { // don't trust strange durations (e.g. old lame versions always write TLEN 97391548)
try {
duration = calculateDuration(data, fileLength, new StopReadCondition() {
final long stopPosition = fileLength - 128;
@Override
public boolean stopRead(MP3Input data) throws IOException {
return (data.getPosition() == stopPosition) && ID3v1Info.isID3v1StartPosition(data);
}
});
} catch (MP3Exception e) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, "Could not determine MP3 duration", e);
}
}
}
if (title == null || album == null || artist == null) {
if (data.getPosition() <= fileLength - 128) { // position to last 128 bytes
data.skipFully(fileLength - 128 - data.getPosition());
if (ID3v1Info.isID3v1StartPosition(input)) {
ID3v1Info info = new ID3v1Info(input);
if (album == null) {
album = info.getAlbum();
}
if (artist == null) {
artist = info.getArtist();
}
if (comment == null) {
comment = info.getComment();
}
if (genre == null) {
genre = info.getGenre();
}
if (title == null) {
title = info.getTitle();
}
if (track == 0) {
track = info.getTrack();
}
if (year == 0) {
year = info.getYear();
}
}
}
}
}
MP3Frame readFirstFrame(MP3Input data, StopReadCondition stopCondition) throws IOException {
int b0 = 0;
int b1 = stopCondition.stopRead(data) ? -1 : data.read();
while (b1 != -1) {
if (b0 == 0xFF && (b1 & 0xE0) == 0xE0) { // first 11 bits should be 1
data.mark(2); // set mark at b2
int b2 = stopCondition.stopRead(data) ? -1 : data.read();
if (b2 == -1) {
break;
}
int b3 = stopCondition.stopRead(data) ? -1 : data.read();
if (b3 == -1) {
break;
}
MP3Frame.Header header = null;
try {
header = new MP3Frame.Header(b1, b2, b3);
} catch (MP3Exception e) {
// not a valid frame header
}
if (header != null) { // we have a candidate
/*
* The code gets a bit complex here, because we need to be able to reset() to b2 if
* the check fails. Thus, we have to reset() to b2 before doing a call to mark().
*/
data.reset(); // reset input to b2
data.mark(header.getFrameSize() + 2); // rest of frame (size - 2) + next header
/*
* read frame data
*/
byte[] frameBytes = new byte[header.getFrameSize()];
frameBytes[0] = (byte) 0xFF;
frameBytes[1] = (byte) b1;
try {
data.readFully(frameBytes, 2, frameBytes.length - 2); // may throw EOFException
} catch (EOFException e) {
break;
}
MP3Frame frame = new MP3Frame(header, frameBytes);
/*
* read next header
*/
if (!frame.isChecksumError()) {
int nextB0 = stopCondition.stopRead(data) ? -1 : data.read();
int nextB1 = stopCondition.stopRead(data) ? -1 : data.read();
if (nextB0 == -1 || nextB1 == -1) {
return frame;
}
if (nextB0 == 0xFF && (nextB1 & 0xFE) == (b1 & 0xFE)) { // quick check: nextB1 must match b1's version & layer
int nextB2 = stopCondition.stopRead(data) ? -1 : data.read();
int nextB3 = stopCondition.stopRead(data) ? -1 : data.read();
if (nextB2 == -1 || nextB3 == -1) {
return frame;
}
try {
if (new MP3Frame.Header(nextB1, nextB2, nextB3).isCompatible(header)) {
data.reset(); // reset input to b2
data.skipFully(frameBytes.length - 2); // skip to end of frame
return frame;
}
} catch (MP3Exception e) {
// not a valid frame header
}
}
}
}
/*
* seems to be a false sync...
*/
data.reset(); // reset input to b2
}
/*
* read next byte
*/
b0 = b1;
b1 = stopCondition.stopRead(data) ? -1 : data.read();
}
return null;
}
MP3Frame readNextFrame(MP3Input data, StopReadCondition stopCondition, MP3Frame previousFrame) throws IOException {
MP3Frame.Header previousHeader = previousFrame.getHeader();
data.mark(4);
int b0 = stopCondition.stopRead(data) ? -1 : data.read();
int b1 = stopCondition.stopRead(data) ? -1 : data.read();
if (b0 == -1 || b1 == -1) {
return null;
}
if (b0 == 0xFF && (b1 & 0xE0) == 0xE0) { // first 11 bits should be 1
int b2 = stopCondition.stopRead(data) ? -1 : data.read();
int b3 = stopCondition.stopRead(data) ? -1 : data.read();
if (b2 == -1 || b3 == -1) {
return null;
}
MP3Frame.Header nextHeader = null;
try {
nextHeader = new MP3Frame.Header(b1, b2, b3);
} catch (MP3Exception e) {
// not a valid frame header
}
if (nextHeader != null && nextHeader.isCompatible(previousHeader)) {
byte[] frameBytes = new byte[nextHeader.getFrameSize()];
frameBytes[0] = (byte) b0;
frameBytes[1] = (byte) b1;
frameBytes[2] = (byte) b2;
frameBytes[3] = (byte) b3;
try {
data.readFully(frameBytes, 4, frameBytes.length - 4);
} catch (EOFException e) {
return null;
}
return new MP3Frame(nextHeader, frameBytes);
}
}
data.reset();
return null;
}
long calculateDuration(MP3Input data, long totalLength, StopReadCondition stopCondition) throws IOException, MP3Exception {
MP3Frame frame = readFirstFrame(data, stopCondition);
if (frame != null) {
// check for Xing header
int numberOfFrames = frame.getNumberOfFrames();
if (numberOfFrames > 0) { // from Xing/VBRI header
return frame.getHeader().getTotalDuration(numberOfFrames * frame.getSize());
} else { // scan file
numberOfFrames = 1;
long firstFramePosition = data.getPosition() - frame.getSize();
long frameSizeSum = frame.getSize();
int firstFrameBitrate = frame.getHeader().getBitrate();
long bitrateSum = firstFrameBitrate;
boolean vbr = false;
int cbrThreshold = 10000 / frame.getHeader().getDuration(); // assume CBR after 10 seconds
while (true) {
if (numberOfFrames == cbrThreshold && !vbr && totalLength > 0) {
return frame.getHeader().getTotalDuration(totalLength - firstFramePosition);
}
if ((frame = readNextFrame(data, stopCondition, frame)) == null) {
break;
}
int bitrate = frame.getHeader().getBitrate();
if (bitrate != firstFrameBitrate) {
vbr = true;
}
bitrateSum += bitrate;
frameSizeSum += frame.getSize();
numberOfFrames++;
}
return 1000L * frameSizeSum * numberOfFrames * 8 / bitrateSum;
}
} else {
throw new MP3Exception("No audio frame");
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class MP3Input extends PositionInputStream {
public MP3Input(InputStream delegate) throws IOException {
super(delegate);
}
public MP3Input(InputStream delegate, long position) {
super(delegate, position);
}
public final void readFully(byte b[], int off, int len) throws IOException {
int total = 0;
while (total < len) {
int current = read(b, off + total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public void skipFully(long len) throws IOException {
long total = 0;
while (total < len) {
long current = skip(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public String toString() {
return "mp3[pos=" + getPosition() + "]";
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.util;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class PositionInputStream extends FilterInputStream {
private long position;
private long positionMark;
public PositionInputStream(InputStream delegate) {
this(delegate, 0L);
}
public PositionInputStream(InputStream delegate, long position) {
super(delegate);
this.position = position;
}
@Override
public synchronized void mark(int readlimit) {
positionMark = position;
super.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
super.reset();
position = positionMark;
}
public int read() throws IOException {
int data = super.read();
if (data >= 0) {
position++;
}
return data;
}
public int read(byte[] b, int off, int len) throws IOException {
long p = position;
int read = super.read(b, off, len);
if (read > 0) {
position = p + read;
}
return read;
}
@Override
public final int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public long skip(long n) throws IOException {
long p = position;
long skipped = super.skip(n);
position = p + skipped;
return skipped;
}
public long getPosition() {
return position;
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.util;
import java.io.IOException;
import java.io.InputStream;
/**
* Input stream filter that keeps track of the current read position
* and has a read length limit.
*/
public class RangeInputStream extends PositionInputStream {
private final long endPosition;
public RangeInputStream(InputStream delegate, long position, long length) throws IOException {
super(delegate, position);
this.endPosition = position + length;
}
public long getRemainingLength() {
return endPosition - getPosition();
}
@Override
public int read() throws IOException {
if (getPosition() == endPosition) {
return -1;
}
return super.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (getPosition() + len > endPosition) {
len = (int)(endPosition - getPosition());
if (len == 0) {
return -1;
}
}
return super.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
if (getPosition() + n > endPosition) {
n = (int)(endPosition - getPosition());
}
return super.skip(n);
}
}

View file

@ -400,4 +400,38 @@ public class SharedMediaQuery {
}
});
}
public static void loadMusic(final long uid, final int max_id) {
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
@Override
public void run() {
final ArrayList<MessageObject> arrayList = new ArrayList<>();
try {
SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, max_id, MEDIA_FILE));
while (cursor.next()) {
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
if (MessageObject.isMusicMessage(message)) {
message.id = cursor.intValue(1);
message.dialog_id = uid;
arrayList.add(0, new MessageObject(message, null, false));
}
}
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
}
cursor.dispose();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.musicDidLoaded, uid, arrayList);
}
});
}
});
}
}

View file

@ -14,9 +14,10 @@
* limitations under the License.
*/
package android.support.v7.util;
package org.telegram.android.support.util;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* A Sorted list implementation that can keep items in order and also notify for changes in the
@ -418,6 +419,19 @@ public class SortedList<T> {
mSize++;
}
/**
* Removes all items from the SortedList.
*/
public void clear() {
if (mSize == 0) {
return;
}
final int prevSize = mSize;
Arrays.fill(mData, 0, prevSize, null);
mSize = 0;
mCallback.onRemoved(0, prevSize);
}
/**
* The class that controls the behavior of the {@link SortedList}.
* <p>

View file

@ -19,6 +19,9 @@ package org.telegram.android.support.widget;
import android.support.v4.util.Pools;
import android.util.Log;
import org.telegram.android.support.widget.OpReorderer;
import org.telegram.android.support.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

View file

@ -18,6 +18,7 @@ package org.telegram.android.support.widget;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import org.telegram.android.support.widget.RecyclerView.ViewHolder;
import android.view.View;

View file

@ -24,6 +24,8 @@ import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import org.telegram.android.support.widget.RecyclerView;
import java.util.Arrays;
/**

View file

@ -60,11 +60,14 @@ class LayoutState {
int mLayoutDirection;
/**
* Used if you want to pre-layout items that are not yet visible.
* The difference with {@link #mAvailable} is that, when recycling, distance rendered for
* {@link #mExtra} is not considered not to recycle visible children.
* This is the target pixel closest to the start of the layout that we are trying to fill
*/
int mExtra = 0;
int mStartLine = 0;
/**
* This is the target pixel closest to the end of the layout that we are trying to fill
*/
int mEndLine = 0;
/**
* @return true if there are more items in the data adapter
@ -84,4 +87,16 @@ class LayoutState {
mCurrentPosition += mItemDirection;
return view;
}
@Override
public String toString() {
return "LayoutState{" +
"mAvailable=" + mAvailable +
", mCurrentPosition=" + mCurrentPosition +
", mItemDirection=" + mItemDirection +
", mLayoutDirection=" + mLayoutDirection +
", mStartLine=" + mStartLine +
", mEndLine=" + mEndLine +
'}';
}
}

View file

@ -23,6 +23,12 @@ import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.util.AttributeSet;
import org.telegram.android.support.widget.OrientationHelper;
import org.telegram.android.support.widget.RecyclerView;
import org.telegram.android.support.widget.ScrollbarHelper;
import org.telegram.android.support.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -33,10 +39,11 @@ import java.util.List;
import static org.telegram.android.support.widget.RecyclerView.NO_POSITION;
/**
* A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
* A {@link RecyclerView.LayoutManager} implementation which provides
* similar functionality to {@link android.widget.ListView}.
*/
public class LinearLayoutManager extends RecyclerView.LayoutManager {
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler {
private static final String TAG = "LinearLayoutManager";
@ -130,7 +137,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* Re-used variable to keep anchor information on re-layout.
* Anchor position and coordinate defines the reference point for LLM while doing a layout.
* */
final AnchorInfo mAnchorInfo;
final AnchorInfo mAnchorInfo = new AnchorInfo();
/**
* Creates a vertical LinearLayoutManager
@ -148,7 +155,6 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* @param reverseLayout When set to true, layouts from end to start.
*/
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
mAnchorInfo = new AnchorInfo();
setOrientation(orientation);
setReverseLayout(reverseLayout);
}
@ -342,8 +348,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* laid out at the end of the UI, second item is laid out before it etc.
*
* For horizontal layouts, it depends on the layout direction.
* When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
* layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout
* When set to true, If {@link RecyclerView} is LTR, than it will
* layout from RTL, if {@link RecyclerView}} is RTL, it will layout
* from LTR.
*
* If you are looking for the exact same behavior of
@ -371,9 +377,13 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
final int firstChild = getPosition(getChildAt(0));
final int viewPosition = position - firstChild;
if (viewPosition >= 0 && viewPosition < childCount) {
return getChildAt(viewPosition);
final View child = getChildAt(viewPosition);
if (getPosition(child) == position) {
return child; // in pre-layout, this may not match
}
}
return null;
// fallback to traversal. This might be necessary in pre-layout.
return super.findViewByPosition(position);
}
/**
@ -796,6 +806,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
}
// override layout from end values for consistency
anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
// if this changes, we should update prepareForDrop as well
if (mShouldReverseLayout) {
anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() -
mPendingScrollPositionOffset;
@ -941,7 +952,6 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
* <p>
* Note that scroll position change will not be reflected until the next layout call.
*
* <p>
* If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
*
@ -1175,11 +1185,10 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
/**
* Recycles views that went out of bounds after scrolling towards the end of the layout.
*
* @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
* @param recycler Recycler instance of {@link RecyclerView}
* @param dt This can be used to add additional padding to the visible area. This is used
* to
* detect children that will go out of bounds after scrolling, without actually
* moving them.
* to detect children that will go out of bounds after scrolling, without
* actually moving them.
*/
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
if (dt < 0) {
@ -1215,7 +1224,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
/**
* Recycles views that went out of bounds after scrolling towards the start of the layout.
*
* @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
* @param recycler Recycler instance of {@link RecyclerView}
* @param dt This can be used to add additional padding to the visible area. This is used
* to detect children that will go out of bounds after scrolling, without
* actually moving them.
@ -1257,8 +1266,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* @param layoutState Current layout state. Right now, this object does not change but
* we may consider moving it out of this view so passing around as a
* parameter for now, rather than accessing {@link #mLayoutState}
* @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int)
* @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int)
* @see #recycleViewsFromStart(RecyclerView.Recycler, int)
* @see #recycleViewsFromEnd(RecyclerView.Recycler, int)
* @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
*/
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
@ -1788,6 +1797,40 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
}
/**
* @hide This method should be called by ItemTouchHelper only.
*/
@Override
public void prepareForDrop(View view, View target, int x, int y) {
assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
ensureLayoutState();
resolveShouldLayoutReverse();
final int myPos = getPosition(view);
final int targetPos = getPosition(target);
final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL :
LayoutState.ITEM_DIRECTION_HEAD;
if (mShouldReverseLayout) {
if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getEndAfterPadding() -
(mOrientationHelper.getDecoratedStart(target) +
mOrientationHelper.getDecoratedMeasurement(view)));
} else {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getEndAfterPadding() -
mOrientationHelper.getDecoratedEnd(target));
}
} else {
if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
} else {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getDecoratedEnd(target) -
mOrientationHelper.getDecoratedMeasurement(view));
}
}
}
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.

View file

@ -16,14 +16,14 @@
package org.telegram.android.support.widget;
import java.util.List;
import org.telegram.android.support.widget.AdapterHelper.UpdateOp;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.ADD;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.MOVE;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.REMOVE;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.UPDATE;
import java.util.List;
class OpReorderer {
final Callback mCallback;
@ -58,7 +58,7 @@ class OpReorderer {
}
void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
int removePos, UpdateOp removeOp) {
int removePos, UpdateOp removeOp) {
UpdateOp extraRm = null;
// check if move is nulled out by remove
boolean revertedMove = false;
@ -83,7 +83,7 @@ class OpReorderer {
removeOp.positionStart--;
} else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
// move is removed.
removeOp.itemCount --;
removeOp.itemCount--;
moveOp.cmd = REMOVE;
moveOp.itemCount = 1;
if (removeOp.itemCount == 0) {
@ -157,7 +157,7 @@ class OpReorderer {
}
private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
UpdateOp addOp) {
UpdateOp addOp) {
int offset = 0;
// going in reverse, first revert the effect of add
if (moveOp.itemCount < addOp.positionStart) {
@ -178,7 +178,7 @@ class OpReorderer {
}
void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
UpdateOp updateOp) {
UpdateOp updateOp) {
UpdateOp extraUp1 = null;
UpdateOp extraUp2 = null;
// going in reverse, first revert the effect of add
@ -228,7 +228,7 @@ class OpReorderer {
return -1;
}
static interface Callback {
interface Callback {
UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount);

View file

@ -456,5 +456,4 @@ class PositionMap<E> implements Cloneable {
return ~lo; // value not present
}
}
}

View file

@ -18,6 +18,7 @@
package org.telegram.android.support.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable;
import android.graphics.Canvas;
import android.graphics.PointF;
@ -30,6 +31,8 @@ import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.InputDeviceCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
@ -39,9 +42,6 @@ import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.ScrollerCompat;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp;
import static org.telegram.android.support.widget.AdapterHelper.Callback;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@ -58,12 +58,15 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import org.telegram.android.AndroidUtilities;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.telegram.android.support.widget.AdapterHelper.Callback;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp;
/**
* A flexible view for providing a limited window into a large data set.
*
@ -128,8 +131,10 @@ import java.util.List;
* <p>
* When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
* writing an {@link Adapter}, you probably want to use adapter positions.
*
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_layoutManager
*/
public class RecyclerView extends ViewGroup implements ScrollingView {
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
private static final String TAG = "RecyclerView";
@ -221,6 +226,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* >Try increasing your pool size and item cache size.
*/
private static final String TRACE_CREATE_VIEW_TAG = "RV CreateView";
private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
new Class[]{Context.class, AttributeSet.class, int.class, int.class};
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
@ -283,6 +290,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
private boolean mAdapterUpdateDuringMeasure;
private final boolean mPostUpdatesOnAnimation;
private final AccessibilityManager mAccessibilityManager;
private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
/**
* Set to true when an adapter data set changed notification is received.
@ -354,11 +362,17 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
new ItemAnimatorRestoreListener();
private boolean mPostedAnimatorRunner = false;
private RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
private ChildDrawingOrderCallback mChildDrawingOrderCallback;
// simple array to keep min and max child position during a layout calculation
// preserved not to create a new one in each layout pass
private final int[] mMinMaxLayoutPositions = new int[2];
private final NestedScrollingChildHelper mScrollingChildHelper;
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private final int[] mNestedOffsets = new int[2];
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
@ -408,6 +422,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
// Create the layoutManager if specified.
mScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
/**
@ -428,6 +446,72 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate);
}
/**
* Instantiate and set a LayoutManager, if specified in the attributes.
*/
private void createLayoutManager(Context context, String className, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
if (className != null) {
className = className.trim();
if (className.length() != 0) { // Can't use isEmpty since it was added in API 9.
className = getFullClassName(context, className);
try {
ClassLoader classLoader;
if (isInEditMode()) {
// Stupid layoutlib cannot handle simple class loaders.
classLoader = this.getClass().getClassLoader();
} else {
classLoader = context.getClassLoader();
}
Class<? extends LayoutManager> layoutManagerClass =
classLoader.loadClass(className).asSubclass(LayoutManager.class);
Constructor<? extends LayoutManager> constructor;
Object[] constructorArgs = null;
try {
constructor = layoutManagerClass
.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
} catch (NoSuchMethodException e) {
try {
constructor = layoutManagerClass.getConstructor();
} catch (NoSuchMethodException e1) {
e1.initCause(e);
throw new IllegalStateException(attrs.getPositionDescription() +
": Error creating LayoutManager " + className, e1);
}
}
constructor.setAccessible(true);
setLayoutManager(constructor.newInstance(constructorArgs));
} catch (ClassNotFoundException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Unable to find LayoutManager " + className, e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Could not instantiate the LayoutManager: " + className, e);
} catch (InstantiationException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Could not instantiate the LayoutManager: " + className, e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Cannot access non-public constructor " + className, e);
} catch (ClassCastException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Class is not a LayoutManager " + className, e);
}
}
}
}
private String getFullClassName(Context context, String className) {
if (className.charAt(0) == '.') {
return context.getPackageName() + className;
}
if (className.contains("")) {
return className;
}
return RecyclerView.class.getPackage().getName() + '.' + className;
}
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
@Override
@ -769,6 +853,46 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
}
/**
* Register a listener that will be notified whenever a child view is attached to or detached
* from RecyclerView.
*
* <p>This listener will be called when a LayoutManager or the RecyclerView decides
* that a child view is no longer needed. If an application associates expensive
* or heavyweight data with item views, this may be a good place to release
* or free those resources.</p>
*
* @param listener Listener to register
*/
public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
mOnChildAttachStateListeners = new ArrayList<OnChildAttachStateChangeListener>();
}
mOnChildAttachStateListeners.add(listener);
}
/**
* Removes the provided listener from child attached state listeners list.
*
* @param listener Listener to unregister
*/
public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
return;
}
mOnChildAttachStateListeners.remove(listener);
}
/**
* Removes all listeners that were added via
* {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}.
*/
public void clearOnChildAttachStateChangeListeners() {
if (mOnChildAttachStateListeners != null) {
mOnChildAttachStateListeners.clear();
}
}
/**
* Set the {@link LayoutManager} that this RecyclerView will use.
*
@ -993,7 +1117,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
+ "layout");
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
@ -1035,7 +1159,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
public void removeItemDecoration(ItemDecoration decor) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or"
+ " layout");
+ "layout");
}
mItemDecorations.remove(decor);
if (mItemDecorations.isEmpty()) {
@ -1045,6 +1169,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
requestLayout();
}
/**
* Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
* <p>
* See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will
* always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be
* true if childDrawingOrderCallback is not null, false otherwise.
* <p>
* Note that child drawing order may be overridden by View's elevation.
*
* @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing
* system.
*/
public void setChildDrawingOrderCallback(ChildDrawingOrderCallback childDrawingOrderCallback) {
if (childDrawingOrderCallback == mChildDrawingOrderCallback) {
return;
}
mChildDrawingOrderCallback = childDrawingOrderCallback;
setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null);
}
/**
* Set a listener that will be notified of any changes in scroll state or position.
*
@ -1153,7 +1297,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if (canScrollHorizontal || canScrollVertical) {
scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, false, 0, 0);
scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
}
}
@ -1176,29 +1320,25 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
*
* @param x The amount of horizontal scroll request
* @param y The amount of vertical scroll request
* @param fromMotionEvent If request is originated from a MotionEvent, this should be set to
* true and motionX/motionY should be provided, false otherwise.
* @param motionX The x coordinate of the MotionEvent which triggered this scroll. Unused if
* fromMotionEvent is false.
* @param motionY The y coordinate of the MotionEvent which triggered this scroll. Unused if
* fromMotionEvent is false.
* @param ev The originating MotionEvent, or null if not from a touch event.
*
* @return Whether any scroll was consumed in either direction.
*/
boolean scrollByInternal(int x, int y, boolean fromMotionEvent, int motionX, int motionY) {
int overscrollX = 0, overscrollY = 0;
int hresult = 0, vresult = 0;
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
if (x != 0) {
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult;
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
overscrollY = y - vresult;
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
if (supportsChangeAnimations()) {
// Fix up shadow views used by changing animations
@ -1227,19 +1367,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (fromMotionEvent) {
pullGlows(motionX, overscrollX, motionY, overscrollY);
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
} else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (ev != null) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
if (hresult != 0 || vresult != 0) {
dispatchOnScrolled(hresult, vresult);
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
if (!awakenScrollBars()) {
invalidate();
}
return hresult != 0 || vresult != 0;
return consumedX != 0 || consumedY != 0;
}
/**
@ -1432,19 +1580,31 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
"Call setLayoutManager with a non-null argument.");
return false;
}
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
velocityX = 0;
}
if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
velocityY = 0;
}
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
if (velocityX != 0 || velocityY != 0) {
mViewFlinger.fling(velocityX, velocityY);
return true;
if (velocityX == 0 && velocityY == 0) {
// If we don't have any velocity, return false
return false;
}
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
dispatchNestedFling(velocityX, velocityY, canScroll);
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
return false;
}
@ -1468,29 +1628,52 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
}
/**
* Returns the minimum velocity to start a fling.
*
* @return The minimum velocity to start a fling
*/
public int getMinFlingVelocity() {
return mMinFlingVelocity;
}
/**
* Returns the maximum fling velocity used by this RecyclerView.
*
* @return The maximum fling velocity used by this RecyclerView.
*/
public int getMaxFlingVelocity() {
return mMaxFlingVelocity;
}
/**
* Apply a pull to relevant overscroll glow effects
*/
private void pullGlows(int x, int overscrollX, int y, int overscrollY) {
private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
boolean invalidate = false;
if (overscrollX < 0) {
ensureLeftGlow();
invalidate = mLeftGlow.onPull(-overscrollX / (float) getWidth(),
1f - y / (float) getHeight()) || invalidate;
if (mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight())) {
invalidate = true;
}
} else if (overscrollX > 0) {
ensureRightGlow();
invalidate = mRightGlow.onPull(overscrollX / (float) getWidth(),
y / (float) getHeight()) || invalidate;
if (mRightGlow.onPull(overscrollX / getWidth(), y / getHeight())) {
invalidate = true;
}
}
if (overscrollY < 0) {
ensureTopGlow();
invalidate = mTopGlow.onPull(-overscrollY / (float) getHeight(),
x / (float) getWidth()) || invalidate;
if (mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth())) {
invalidate = true;
}
} else if (overscrollY > 0) {
ensureBottomGlow();
invalidate = mBottomGlow.onPull(overscrollY / (float) getHeight(),
1f - x / (float) getWidth()) || invalidate;
if (mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth())) {
invalidate = true;
}
}
if (invalidate || overscrollX != 0 || overscrollY != 0) {
@ -1693,6 +1876,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
removeCallbacks(mItemAnimatorRunner);
}
/**
* Returns true if RecyclerView is attached to window.
*/
// @override
public boolean isAttachedToWindow() {
return mIsAttached;
}
/**
* Checks if RecyclerView is in the middle of a layout or scroll and throws an
* {@link IllegalStateException} if it <b>is not</b>.
@ -1739,6 +1930,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* for each incoming MotionEvent until the end of the gesture.</p>
*
* @param listener Listener to add
* @see SimpleOnItemTouchListener
*/
public void addOnItemTouchListener(OnItemTouchListener listener) {
mOnItemTouchListeners.add(listener);
@ -1832,6 +2024,15 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
break;
case MotionEventCompat.ACTION_POINTER_DOWN:
@ -1874,6 +2075,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll();
} break;
case MotionEvent.ACTION_CANCEL: {
@ -1883,6 +2085,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mScrollState == SCROLL_STATE_DRAGGING;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (dispatchOnItemTouch(e)) {
@ -1898,14 +2110,29 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
mVelocityTracker.addMovement(e);
final MotionEvent vtev = MotionEvent.obtain(e);
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
} break;
case MotionEventCompat.ACTION_POINTER_DOWN: {
@ -1924,32 +2151,52 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
if (scrollByInternal(canScrollHorizontally ? -dx : 0,
canScrollVertically ? -dy : 0, true, x, y)) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
mLastTouchX = x;
mLastTouchY = y;
} break;
case MotionEventCompat.ACTION_POINTER_UP: {
@ -1965,6 +2212,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
mVelocityTracker.clear();
releaseGlows();
} break;
@ -1974,6 +2222,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
} break;
}
vtev.recycle();
return true;
}
@ -1981,6 +2231,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
stopNestedScroll();
releaseGlows();
setScrollState(SCROLL_STATE_IDLE);
}
@ -2823,6 +3074,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mLayout.generateLayoutParams(p);
}
/**
* Returns true if RecyclerView is currently running some animations.
* <p>
* If you want to be notified when animations are finished, use
* {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}.
*
* @return True if there are some item animations currently running or waiting to be started.
*/
public boolean isAnimating() {
return mItemAnimator != null && mItemAnimator.isRunning();
}
void saveOldPositions() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
@ -3232,6 +3495,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return null;
}
@Override
public boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(canvas, child, drawingTime);
}
/**
* Offset the bounds of all child views by <code>dy</code> pixels.
* Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
@ -5153,17 +5421,32 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
private void dispatchChildDetached(View child) {
if (mAdapter != null) {
mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
}
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildDetachedFromWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewDetachedFromWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
}
}
}
private void dispatchChildAttached(View child) {
if (mAdapter != null) {
mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child));
}
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildAttachedToWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewAttachedToWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
}
/**
@ -5173,6 +5456,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
* a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
* layout managers are provided for general use.
* <p/>
* If the LayoutManager specifies a default constructor or one with the signature
* ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will
* instantiate and set the LayoutManager when being inflated. Most used properties can
* be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case
* a LayoutManager specifies both constructors, the non-default constructor will take
* precedence.
*
*/
public static abstract class LayoutManager {
ChildHelper mChildHelper;
@ -7259,6 +7550,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
int action, Bundle args) {
return false;
}
/**
* Some general properties that a LayoutManager may want to use.
*/
public static class Properties {
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
public int orientation;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
public int spanCount;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
public boolean reverseLayout;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
public boolean stackFromEnd;
}
}
/**
@ -7359,8 +7664,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
* a touch interaction already in progress even if the RecyclerView is already handling that
* gesture stream itself for the purposes of scrolling.</p>
*
* @see SimpleOnItemTouchListener
*/
public interface OnItemTouchListener {
public static interface OnItemTouchListener {
/**
* Silently observe and/or take over touch events sent to the RecyclerView
* before they are handled by either the RecyclerView itself or its child views.
@ -7385,8 +7692,44 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* the RecyclerView's coordinate system.
*/
public void onTouchEvent(RecyclerView rv, MotionEvent e);
/**
* Called when a child of RecyclerView does not want RecyclerView and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
*
* @param disallowIntercept True if the child does not want the parent to
* intercept touch events.
* @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
*/
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
/**
* An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies and
* default return values.
* <p>
* You may prefer to extend this class if you don't need to override all methods. Another
* benefit of using this class is future compatibility. As the interface may change, we'll
* always provide a default implementation on this class so that your code won't break when
* you update to a new version of the support library.
*/
public class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
/**
* An OnScrollListener can be set on a RecyclerView to receive messages
* when a scrolling event has occurred on that RecyclerView.
@ -7399,7 +7742,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* want your components to be able to easily replace the listener use
* RecyclerView#setOnScrollListener.
*/
abstract static public class OnScrollListener {
public abstract static class OnScrollListener {
/**
* Callback method to be invoked when RecyclerView's scroll state changes.
*
@ -7444,6 +7787,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
public void onViewRecycled(ViewHolder holder);
}
/**
* A Listener interface that can be attached to a RecylcerView to get notified
* whenever a ViewHolder is attached to or detached from RecyclerView.
*/
public interface OnChildAttachStateChangeListener {
/**
* Called when a view is attached to the RecyclerView.
*
* @param view The View which is attached to the RecyclerView
*/
public void onChildViewAttachedToWindow(View view);
/**
* Called when a view is detached from RecyclerView.
*
* @param view The View which is being detached from the RecyclerView
*/
public void onChildViewDetachedFromWindow(View view);
}
/**
* A ViewHolder describes an item view and metadata about its place within the RecyclerView.
*
@ -7862,6 +8226,55 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}
// NestedScrollingChild
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mScrollingChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mScrollingChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mScrollingChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mScrollingChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
/**
* {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
* {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
@ -9318,4 +9731,35 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
this.bottom = bottom;
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (mChildDrawingOrderCallback == null) {
return super.getChildDrawingOrder(childCount, i);
} else {
return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i);
}
}
/**
* A callback interface that can be used to alter the drawing order of RecyclerView children.
* <p>
* It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case
* that applies to that method also applies to this callback. For example, changing the drawing
* order of two views will not have any effect if their elevation values are different since
* elevation overrides the result of this callback.
*/
public static interface ChildDrawingOrderCallback {
/**
* Returns the index of the child to draw for this iteration. Override this
* if you want to change the drawing order of children. By default, it
* returns i.
*
* @param i The current iteration.
* @return The index of the child to draw this iteration.
*
* @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
*/
public int onGetChildDrawingOrder(int childCount, int i);
}
}

View file

@ -537,7 +537,6 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper();
final AnchorInfo anchorInfo = mAnchorInfo;
anchorInfo.reset();
@ -577,21 +576,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
detachAndScrapAttachedViews(recycler);
mLaidOutInvalidFullSpan = false;
updateMeasureSpecs();
updateLayoutState(anchorInfo.mPosition, state);
if (anchorInfo.mLayoutFromEnd) {
// Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state);
setLayoutStateDirection(LAYOUT_START);
fill(recycler, mLayoutState, state);
// Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
setLayoutStateDirection(LAYOUT_END);
mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state);
} else {
// Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
setLayoutStateDirection(LAYOUT_END);
fill(recycler, mLayoutState, state);
// Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
setLayoutStateDirection(LAYOUT_START);
mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state);
}
@ -1254,40 +1254,37 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
}
private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) {
private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
mLayoutState.mAvailable = 0;
mLayoutState.mCurrentPosition = anchorPosition;
int startExtra = 0;
int endExtra = 0;
if (isSmoothScrolling()) {
final int targetPos = state.getTargetScrollPosition();
if (mShouldReverseLayout == targetPos < anchorPosition) {
mLayoutState.mExtra = 0;
} else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
if (targetPos != NO_POSITION) {
if (mShouldReverseLayout == targetPos < anchorPosition) {
endExtra = mPrimaryOrientation.getTotalSpace();
} else {
startExtra = mPrimaryOrientation.getTotalSpace();
}
}
} else {
mLayoutState.mExtra = 0;
}
mLayoutState.mLayoutDirection = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
// Line of the furthest row.
final boolean clipToPadding = getClipToPadding();
if (clipToPadding) {
mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
} else {
mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
mLayoutState.mStartLine = -startExtra;
}
}
private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) {
mLayoutState.mAvailable = 0;
mLayoutState.mCurrentPosition = anchorPosition;
if (isSmoothScrolling()) {
final int targetPos = state.getTargetScrollPosition();
if (mShouldReverseLayout == targetPos > anchorPosition) {
mLayoutState.mExtra = 0;
} else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
}
} else {
mLayoutState.mExtra = 0;
}
mLayoutState.mLayoutDirection = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
: ITEM_DIRECTION_TAIL;
private void setLayoutStateDirection(int direction) {
mLayoutState.mLayoutDirection = direction;
mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LAYOUT_START)) ?
ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
}
@Override
@ -1383,31 +1380,25 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
mRemainingSpans.set(0, mSpanCount, true);
// The target position we are trying to reach.
final int targetLine;
/*
* The line until which we can recycle, as long as we add views.
* Keep in mind, it is still the line in layout direction which means; to calculate the
* actual recycle line, we should subtract/add the size in orientation.
*/
final int recycleLine;
// Line of the furthest row.
if (layoutState.mLayoutDirection == LAYOUT_END) {
// ignore padding for recycler
recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();
targetLine = layoutState.mEndLine + layoutState.mAvailable;
} else { // LAYOUT_START
// ignore padding for recycler
recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
targetLine = recycleLine - mLayoutState.mExtra -
mPrimaryOrientation.getStartAfterPadding();
targetLine = layoutState.mStartLine - layoutState.mAvailable;
}
updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
if (DEBUG) {
Log.d(TAG, "FILLING targetLine: " + targetLine + "," +
"remaining spans:" + mRemainingSpans + ", state: " + layoutState);
}
// the default coordinate to add new view.
final int defaultNewViewLine = mShouldReverseLayout
? mPrimaryOrientation.getEndAfterPadding()
: mPrimaryOrientation.getStartAfterPadding();
boolean added = false;
while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
View view = layoutState.next(recycler);
LayoutParams lp = ((LayoutParams) view.getLayoutParams());
@ -1500,18 +1491,21 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
} else {
updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
}
recycle(recycler, mLayoutState, currentSpan, recycleLine);
recycle(recycler, mLayoutState);
added = true;
}
if (DEBUG) {
Log.d(TAG, "fill, " + getChildCount());
if (!added) {
recycle(recycler, mLayoutState);
}
final int diff;
if (mLayoutState.mLayoutDirection == LAYOUT_START) {
final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart));
diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
} else {
final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (max - recycleLine));
final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
}
return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
}
private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
@ -1548,19 +1542,40 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
}
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState,
Span updatedSpan, int recycleLine) {
if (layoutState.mLayoutDirection == LAYOUT_START) {
// calculate recycle line
int maxStart = getMaxStart(updatedSpan.getStartLine());
recycleFromEnd(recycler, Math.max(recycleLine, maxStart) +
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (layoutState.mAvailable == 0) {
// easy, recycle line is still valid
if (layoutState.mLayoutDirection == LAYOUT_START) {
recycleFromEnd(recycler, layoutState.mEndLine);
} else {
recycleFromStart(recycler, layoutState.mStartLine);
}
} else {
// calculate recycle line
int minEnd = getMinEnd(updatedSpan.getEndLine());
recycleFromStart(recycler, Math.min(recycleLine, minEnd) -
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
// scrolling case, recycle line can be shifted by how much space we could cover
// by adding new views
if (layoutState.mLayoutDirection == LAYOUT_START) {
// calculate recycle line
int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
final int line;
if (scrolled < 0) {
line = layoutState.mEndLine;
} else {
line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
}
recycleFromEnd(recycler, line);
} else {
// calculate recycle line
int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
final int line;
if (scrolled < 0) {
line = layoutState.mStartLine;
} else {
line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
}
recycleFromStart(recycler, line);
}
}
}
private void appendViewToAllSpans(View view) {
@ -1602,12 +1617,12 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
final int deletedSize = span.getDeletedSize();
if (layoutDir == LAYOUT_START) {
final int line = span.getStartLine();
if (line + deletedSize < targetLine) {
if (line + deletedSize <= targetLine) {
mRemainingSpans.set(span.mIndex, false);
}
} else {
final int line = span.getEndLine();
if (line - deletedSize > targetLine) {
if (line - deletedSize >= targetLine) {
mRemainingSpans.set(span.mIndex, false);
}
}
@ -1678,18 +1693,24 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
if (DEBUG) {
Log.d(TAG, "recycling from start for line " + line);
}
while (getChildCount() > 0) {
View child = getChildAt(0);
if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popStart();
}
} else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popStart();
}
removeAndRecycleView(child, recycler);
@ -1704,13 +1725,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int i;
for (i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mPrimaryOrientation.getDecoratedStart(child) > line) {
if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popEnd();
}
} else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popEnd();
}
removeAndRecycleView(child, recycler);
@ -1860,21 +1890,19 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper();
final int referenceChildPosition;
final int layoutDir;
if (dt > 0) { // layout towards end
mLayoutState.mLayoutDirection = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
: ITEM_DIRECTION_TAIL;
layoutDir = LAYOUT_END;
referenceChildPosition = getLastChildPosition();
} else {
mLayoutState.mLayoutDirection = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
layoutDir = LAYOUT_START;
referenceChildPosition = getFirstChildPosition();
}
updateLayoutState(referenceChildPosition, state);
setLayoutStateDirection(layoutDir);
mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
final int absDt = Math.abs(dt);
mLayoutState.mAvailable = absDt;
mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
int consumed = fill(recycler, mLayoutState, state);
final int totalScroll;
if (absDt < consumed) {

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.support.widget.helper;
import android.graphics.Canvas;
import android.view.View;
import org.telegram.android.support.widget.RecyclerView;
/**
* Utility class for {@link ItemTouchHelper} which handles item transformations for different
* API versions.
* <p/>
* This class has methods that map to {@link ItemTouchHelper.Callback}'s drawing methods. Default
* implementations in {@link ItemTouchHelper.Callback} call these methods with
* {@link RecyclerView.ViewHolder#itemView} and {@link ItemTouchUIUtil} makes necessary changes
* on the View depending on the API level. You can access the instance of {@link ItemTouchUIUtil}
* via {@link ItemTouchHelper.Callback#getDefaultUIUtil()} and call its methods with the children
* of ViewHolder that you want to apply default effects.
*
* @see ItemTouchHelper.Callback#getDefaultUIUtil()
*/
public interface ItemTouchUIUtil {
/**
* The default implementation for {@link ItemTouchHelper.Callback#onChildDraw(Canvas,
* RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
*/
void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive);
/**
* The default implementation for {@link ItemTouchHelper.Callback#onChildDrawOver(Canvas,
* RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
*/
void onDrawOver(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive);
/**
* The default implementation for {@link ItemTouchHelper.Callback#clearView(RecyclerView,
* RecyclerView.ViewHolder)}
*/
void clearView(View view);
/**
* The default implementation for {@link ItemTouchHelper.Callback#onSelectedChanged(
* RecyclerView.ViewHolder, int)}
*/
void onSelected(View view);
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.support.widget.helper;
import android.graphics.Canvas;
import android.support.v4.view.ViewCompat;
import org.telegram.android.support.widget.RecyclerView;
import android.view.View;
/**
* Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
* public API, which is not desired in this case.
*/
class ItemTouchUIUtilImpl {
final static int item_touch_helper_previous_elevation = 123;
static class Lollipop extends Honeycomb {
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (isCurrentlyActive) {
Object originalElevation = view.getTag(item_touch_helper_previous_elevation);
if (originalElevation == null) {
originalElevation = ViewCompat.getElevation(view);
float newElevation = 1f + findMaxElevation(recyclerView, view);
ViewCompat.setElevation(view, newElevation);
view.setTag(item_touch_helper_previous_elevation, originalElevation);
}
}
super.onDraw(c, recyclerView, view, dX, dY, actionState, isCurrentlyActive);
}
private float findMaxElevation(RecyclerView recyclerView, View itemView) {
final int childCount = recyclerView.getChildCount();
float max = 0;
for (int i = 0; i < childCount; i++) {
final View child = recyclerView.getChildAt(i);
if (child == itemView) {
continue;
}
final float elevation = ViewCompat.getElevation(child);
if (elevation > max) {
max = elevation;
}
}
return max;
}
@Override
public void clearView(View view) {
final Object tag = view.getTag(item_touch_helper_previous_elevation);
if (tag != null && tag instanceof Float) {
ViewCompat.setElevation(view, (Float) tag);
}
view.setTag(item_touch_helper_previous_elevation, null);
super.clearView(view);
}
}
static class Honeycomb implements ItemTouchUIUtil {
@Override
public void clearView(View view) {
ViewCompat.setTranslationX(view, 0f);
ViewCompat.setTranslationY(view, 0f);
}
@Override
public void onSelected(View view) {
}
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
ViewCompat.setTranslationX(view, dX);
ViewCompat.setTranslationY(view, dY);
}
@Override
public void onDrawOver(Canvas c, RecyclerView recyclerView,
View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
}
}
static class Gingerbread implements ItemTouchUIUtil {
private void draw(Canvas c, RecyclerView parent, View view,
float dX, float dY) {
c.save();
c.translate(dX, dY);
parent.drawChild(c, view, 0);
c.restore();
}
@Override
public void clearView(View view) {
view.setVisibility(View.VISIBLE);
}
@Override
public void onSelected(View view) {
view.setVisibility(View.INVISIBLE);
}
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
draw(c, recyclerView, view, dX, dY);
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView recyclerView,
View view, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
draw(c, recyclerView, view, dX, dY);
}
}
}
}

View file

@ -16,7 +16,7 @@
package org.telegram.android.support.widget.util;
import android.support.v7.util.SortedList;
import org.telegram.android.support.util.SortedList;
import org.telegram.android.support.widget.RecyclerView;
/**

View file

@ -90,7 +90,6 @@ public class ApplicationLoader extends Application {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
int selectedBackground = preferences.getInt("selectedBackground", 1000001);
selectedColor = preferences.getInt("selectedColor", 0);
int cacheColorHint = 0;
if (selectedColor == 0) {
if (selectedBackground == 1000001) {
cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd);

View file

@ -363,7 +363,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
try {
SerializedData data = new SerializedData(configFile);
isTestBackend = data.readInt32(false);
int version = data.readInt32(false);
data.readInt32(false);
sessionsToDestroy.clear();
int count = data.readInt32(false);
for (int a = 0; a < count; a++) {
@ -2700,7 +2700,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
return;
}
int messageLength = data.readInt32(false);
data.readInt32(false);
TLObject message = deserialize(getRequestWithMessageId(messageId), data, true);

View file

@ -28,6 +28,7 @@ public class FileLoadOperation {
private final static int stateFinished = 3;
private final static int downloadChunkSize = 1024 * 32;
private final static int downloadChunkSizeBig = 1024 * 128;
private final static int maxDownloadRequests = 3;
private int datacenter_id;
@ -38,6 +39,7 @@ public class FileLoadOperation {
private FileLoadOperationDelegate delegate;
private byte[] key;
private byte[] iv;
private int currentDownloadChunkSize;
private int nextDownloadOffset = 0;
private ArrayList<RequestInfo> requestInfos = new ArrayList<>(maxDownloadRequests);
@ -165,6 +167,7 @@ public class FileLoadOperation {
if (state != stateIdle) {
return;
}
currentDownloadChunkSize = totalBytesCount >= 1024 * 1024 * 30 ? downloadChunkSizeBig : downloadChunkSize;
state = stateDownloading;
if (location == null) {
Utilities.stageQueue.postRunnable(new Runnable() {
@ -175,7 +178,6 @@ public class FileLoadOperation {
});
return;
}
Long mediaId = null;
String fileNameFinal;
String fileNameTemp;
String fileNameIv = null;
@ -223,7 +225,7 @@ public class FileLoadOperation {
cacheFileTemp = new File(tempPath, fileNameTemp);
if (cacheFileTemp.exists()) {
downloadedBytes = (int)cacheFileTemp.length();
nextDownloadOffset = downloadedBytes = downloadedBytes / 1024 * 1024;
nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize;
}
if (fileNameIv != null) {
cacheIvTemp = new File(tempPath, fileNameIv);
@ -388,10 +390,10 @@ public class FileLoadOperation {
}
}
if (currentBytesSize != downloadChunkSize) {
if (currentBytesSize != currentDownloadChunkSize) {
onFinishLoadingFile();
} else {
if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
if (totalBytesCount != downloadedBytes && downloadedBytes % currentDownloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
startDownloadRequest();
} else {
onFinishLoadingFile();
@ -422,7 +424,7 @@ public class FileLoadOperation {
startDownloadRequest();
}
} else if (error.text.contains("OFFSET_INVALID")) {
if (downloadedBytes % downloadChunkSize == 0) {
if (downloadedBytes % currentDownloadChunkSize == 0) {
try {
onFinishLoadingFile();
} catch (Exception e) {
@ -460,12 +462,12 @@ public class FileLoadOperation {
if (totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount) {
break;
}
boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + downloadChunkSize >= totalBytesCount;
boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + currentDownloadChunkSize >= totalBytesCount;
TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
req.location = location;
req.offset = nextDownloadOffset;
req.limit = downloadChunkSize;
nextDownloadOffset += downloadChunkSize;
req.limit = currentDownloadChunkSize;
nextDownloadOffset += currentDownloadChunkSize;
final RequestInfo requestInfo = new RequestInfo();
requestInfos.add(requestInfo);

View file

@ -675,6 +675,15 @@ public class FileLoader {
return closestObject;
}
public static String getFileExtension(File file) {
String name = file.getName();
try {
return name.substring(name.lastIndexOf(".") + 1);
} catch (Exception e) {
return "";
}
}
public static String getDocumentFileName(TLRPC.Document document) {
if (document != null) {
if (document.file_name != null) {

View file

@ -637,7 +637,7 @@ public class HandshakeAction extends Action implements TcpConnection.TcpConnecti
FileLog.d("tmessages", String.format("===== Duplicate message id %d received, ignoring", messageId));
return;
}
int messageLength = data.readInt32(false);
data.readInt32(false);
int constructor = data.readInt32(false);
TLObject object = TLClassStore.Instance().TLdeserialize(data, constructor, false);

View file

@ -11634,6 +11634,9 @@ public class TLRPC {
case 0x9eddf188:
result = new TL_inputMessagesFilterDocument();
break;
case 0x5afbf764:
result = new TL_inputMessagesFilterAudioDocuments();
break;
case 0x9fc00e65:
result = new TL_inputMessagesFilterVideo();
break;
@ -11672,6 +11675,15 @@ public class TLRPC {
}
}
public static class TL_inputMessagesFilterAudioDocuments extends MessagesFilter {
public static int constructor = 0x5afbf764;
public void serializeToStream(AbsSerializedData stream) {
stream.writeInt32(constructor);
}
}
public static class TL_inputMessagesFilterVideo extends MessagesFilter {
public static int constructor = 0x9fc00e65;

View file

@ -143,7 +143,7 @@ public class TcpConnection extends ConnectionContext {
FileLog.e("tmessages", e2);
}
FileLog.d("tmessages", String.format(TcpConnection.this + " Connecting (%s:%d)", hostAddress, hostPort));
FileLog.d("tmessages", String.format(TcpConnection.this + " Connecting (%s:%d), connection class %d", hostAddress, hostPort, transportRequestClass));
firstPacket = true;
if (restOfTheData != null) {
BuffersStorage.getInstance().reuseFreeBuffer(restOfTheData);

View file

@ -15,7 +15,6 @@ import android.os.Build;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
@ -23,6 +22,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper;
@ -38,7 +38,6 @@ public class ActionBar extends FrameLayout {
}
}
private FrameLayout titleFrameLayout;
private ImageView backButtonImageView;
private TextView titleTextView;
private TextView subTitleTextView;
@ -60,116 +59,6 @@ public class ActionBar extends FrameLayout {
public ActionBar(Context context) {
super(context);
titleFrameLayout = new FrameLayout(context);
addView(titleFrameLayout);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)titleFrameLayout.getLayoutParams();
layoutParams.width = LayoutHelper.WRAP_CONTENT;
layoutParams.height = LayoutHelper.MATCH_PARENT;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
titleFrameLayout.setLayoutParams(layoutParams);
titleFrameLayout.setPadding(0, 0, AndroidUtilities.dp(4), 0);
titleFrameLayout.setEnabled(false);
}
private void positionBackImage(int height) {
if (backButtonImageView != null) {
LayoutParams layoutParams = (LayoutParams)backButtonImageView.getLayoutParams();
layoutParams.width = AndroidUtilities.dp(54);
layoutParams.height = height;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
backButtonImageView.setLayoutParams(layoutParams);
}
}
private void positionTitle(int width, int height) {
int offset = AndroidUtilities.dp(2);
if (!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
offset = AndroidUtilities.dp(1);
}
int maxTextWidth = 0;
LayoutParams layoutParams;
if (titleTextView != null && titleTextView.getVisibility() == VISIBLE) {
if (!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
} else {
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
}
layoutParams = (LayoutParams) titleTextView.getLayoutParams();
layoutParams.width = LayoutHelper.WRAP_CONTENT;
layoutParams.height = LayoutHelper.WRAP_CONTENT;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
titleTextView.setLayoutParams(layoutParams);
titleTextView.measure(width, height);
maxTextWidth = titleTextView.getMeasuredWidth();
}
if (subTitleTextView != null && subTitleTextView.getVisibility() == VISIBLE) {
if (!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
subTitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
} else {
subTitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
}
layoutParams = (LayoutParams) subTitleTextView.getLayoutParams();
layoutParams.width = LayoutHelper.WRAP_CONTENT;
layoutParams.height = LayoutHelper.WRAP_CONTENT;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
subTitleTextView.setLayoutParams(layoutParams);
subTitleTextView.measure(width, height);
maxTextWidth = Math.max(maxTextWidth, subTitleTextView.getMeasuredWidth());
}
int x;
if (backButtonImageView != null && backButtonImageView.getVisibility() == VISIBLE) {
x = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 80 : 72);
} else {
x = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 26 : 18);
}
if (menu != null) {
maxTextWidth = Math.min(maxTextWidth, width - menu.getMeasuredWidth() - AndroidUtilities.dp(16) - x);
}
if (titleTextView != null && titleTextView.getVisibility() == VISIBLE) {
layoutParams = (LayoutParams) titleTextView.getLayoutParams();
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.height = titleTextView.getMeasuredHeight();
int y;
if (subTitleTextView != null && subTitleTextView.getVisibility() == VISIBLE) {
y = (height / 2 - titleTextView.getMeasuredHeight()) / 2 + offset;
} else {
y = (height - titleTextView.getMeasuredHeight()) / 2 - AndroidUtilities.dp(1);
}
layoutParams.setMargins(x, y, 0, 0);
titleTextView.setLayoutParams(layoutParams);
}
if (subTitleTextView != null && subTitleTextView.getVisibility() == VISIBLE) {
layoutParams = (LayoutParams) subTitleTextView.getLayoutParams();
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.height = subTitleTextView.getMeasuredHeight();
layoutParams.setMargins(x, height / 2 + (height / 2 - subTitleTextView.getMeasuredHeight()) / 2 - offset, 0, 0);
subTitleTextView.setLayoutParams(layoutParams);
}
MarginLayoutParams layoutParams1 = (MarginLayoutParams) titleFrameLayout.getLayoutParams();
layoutParams1.width = x + maxTextWidth + (isSearchFieldVisible ? 0 : AndroidUtilities.dp(6));
layoutParams1.topMargin = occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
titleFrameLayout.setLayoutParams(layoutParams1);
}
public void positionMenu(int width, int height) {
if (menu == null) {
return;
}
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)menu.getLayoutParams();
layoutParams.width = isSearchFieldVisible ? LayoutHelper.MATCH_PARENT : LayoutHelper.WRAP_CONTENT;
layoutParams.height = height;
layoutParams.leftMargin = isSearchFieldVisible ? AndroidUtilities.dp(AndroidUtilities.isTablet() ? 74 : 66) : 0;
layoutParams.topMargin = occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
menu.setLayoutParams(layoutParams);
menu.measure(width, height);
}
private void createBackButtonImage() {
@ -177,9 +66,10 @@ public class ActionBar extends FrameLayout {
return;
}
backButtonImageView = new ImageView(getContext());
titleFrameLayout.addView(backButtonImageView);
backButtonImageView.setScaleType(ImageView.ScaleType.CENTER);
backButtonImageView.setBackgroundResource(itemsBackgroundResourceId);
addView(backButtonImageView, LayoutHelper.createFrame(54, 54, Gravity.LEFT | Gravity.TOP));
backButtonImageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -195,31 +85,19 @@ public class ActionBar extends FrameLayout {
}
public void setBackButtonDrawable(Drawable drawable) {
boolean reposition = false;
if (backButtonImageView == null) {
createBackButtonImage();
} else {
reposition = true;
}
backButtonImageView.setVisibility(drawable == null ? GONE : VISIBLE);
backButtonImageView.setImageDrawable(drawable);
if (reposition) {
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
}
public void setBackButtonImage(int resource) {
boolean reposition = false;
if (backButtonImageView == null) {
createBackButtonImage();
} else {
reposition = true;
}
backButtonImageView.setVisibility(resource == 0 ? GONE : VISIBLE);
backButtonImageView.setImageResource(resource);
if (reposition) {
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
}
private void createSubtitleTextView() {
@ -227,13 +105,13 @@ public class ActionBar extends FrameLayout {
return;
}
subTitleTextView = new TextView(getContext());
titleFrameLayout.addView(subTitleTextView);
subTitleTextView.setGravity(Gravity.LEFT);
subTitleTextView.setTextColor(0xffd7e8f7);
subTitleTextView.setSingleLine(true);
subTitleTextView.setLines(1);
subTitleTextView.setMaxLines(1);
subTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
addView(subTitleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP));
}
public void setSubtitle(CharSequence value) {
@ -243,22 +121,6 @@ public class ActionBar extends FrameLayout {
if (subTitleTextView != null) {
subTitleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
subTitleTextView.setText(value);
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
}
public void setSubTitleIcon(int resourceId, Drawable drawable, int padding) {
if ((resourceId != 0 || drawable != null) && subTitleTextView == null) {
createSubtitleTextView();
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
if (subTitleTextView != null) {
if (drawable != null) {
subTitleTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
} else {
subTitleTextView.setCompoundDrawablesWithIntrinsicBounds(resourceId, 0, 0, 0);
}
subTitleTextView.setCompoundDrawablePadding(padding);
}
}
@ -268,39 +130,32 @@ public class ActionBar extends FrameLayout {
}
titleTextView = new TextView(getContext());
titleTextView.setGravity(Gravity.LEFT);
titleTextView.setSingleLine(true);
titleTextView.setLines(1);
titleTextView.setMaxLines(1);
titleTextView.setSingleLine(true);
titleTextView.setEllipsize(TextUtils.TruncateAt.END);
titleFrameLayout.addView(titleTextView);
titleTextView.setTextColor(0xffffffff);
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP));
}
public void setTitle(CharSequence value) {
boolean created = false;
if (value != null && titleTextView == null) {
createTitleTextView();
created = true;
}
if (titleTextView != null) {
lastTitle = value;
titleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
titleTextView.setText(value);
positionTitle(getMeasuredWidth(), getMeasuredHeight());
if (!created) {
titleTextView.setText(value);
}
}
}
public void setTitleIcon(int resourceId, int padding) {
if (resourceId != 0 && titleTextView == null) {
createTitleTextView();
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
titleTextView.setCompoundDrawablesWithIntrinsicBounds(resourceId, 0, 0, 0);
titleTextView.setCompoundDrawablePadding(padding);
public TextView getSubTitleTextView() {
return subTitleTextView;
}
public TextView getTitleTextView() {
return titleTextView;
}
public Drawable getSubTitleIcon() {
@ -332,17 +187,6 @@ public class ActionBar extends FrameLayout {
actionBarMenuOnItemClick = listener;
}
public void setCustomView(int resourceId) {
LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = li.inflate(resourceId, null);
addView(view);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)view.getLayoutParams();
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.height = LayoutHelper.MATCH_PARENT;
layoutParams.topMargin = occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
view.setLayoutParams(layoutParams);
}
public ActionBarMenu createActionMode() {
if (actionMode != null) {
return actionMode;
@ -358,7 +202,7 @@ public class ActionBar extends FrameLayout {
actionMode.setLayoutParams(layoutParams);
actionMode.setVisibility(INVISIBLE);
if (occupyStatusBar) {
if (occupyStatusBar && actionModeTop == null) {
actionModeTop = new View(getContext());
actionModeTop.setBackgroundColor(0x99000000);
addView(actionModeTop);
@ -381,8 +225,14 @@ public class ActionBar extends FrameLayout {
if (occupyStatusBar && actionModeTop != null) {
actionModeTop.setVisibility(VISIBLE);
}
if (titleFrameLayout != null) {
titleFrameLayout.setVisibility(INVISIBLE);
if (titleTextView != null) {
titleTextView.setVisibility(INVISIBLE);
}
if (subTitleTextView != null) {
subTitleTextView.setVisibility(INVISIBLE);
}
if (backButtonImageView != null) {
backButtonImageView.setVisibility(INVISIBLE);
}
if (menu != null) {
menu.setVisibility(INVISIBLE);
@ -397,14 +247,33 @@ public class ActionBar extends FrameLayout {
if (occupyStatusBar && actionModeTop != null) {
actionModeTop.setVisibility(INVISIBLE);
}
if (titleFrameLayout != null) {
titleFrameLayout.setVisibility(VISIBLE);
if (titleTextView != null) {
titleTextView.setVisibility(VISIBLE);
}
if (subTitleTextView != null) {
subTitleTextView.setVisibility(VISIBLE);
}
if (backButtonImageView != null) {
backButtonImageView.setVisibility(VISIBLE);
}
if (menu != null) {
menu.setVisibility(VISIBLE);
}
}
public void showActionModeTop() {
if (occupyStatusBar && actionModeTop == null) {
actionModeTop = new View(getContext());
actionModeTop.setBackgroundColor(0x99000000);
addView(actionModeTop);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) actionModeTop.getLayoutParams();
layoutParams.height = AndroidUtilities.statusBarHeight;
layoutParams.width = LayoutHelper.MATCH_PARENT;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
actionModeTop.setLayoutParams(layoutParams);
}
}
public boolean isActionModeShowed() {
return actionMode != null && actionMode.getVisibility() == VISIBLE;
}
@ -439,12 +308,136 @@ public class ActionBar extends FrameLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int actionBarHeight = AndroidUtilities.getCurrentActionBarHeight();
positionBackImage(actionBarHeight);
positionMenu(MeasureSpec.getSize(widthMeasureSpec), actionBarHeight);
positionTitle(MeasureSpec.getSize(widthMeasureSpec), actionBarHeight);
actionBarHeight += occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(actionBarHeight + extraHeight, MeasureSpec.EXACTLY));
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int actionBarHeight = getCurrentActionBarHeight();
int actionBarHeightSpec = MeasureSpec.makeMeasureSpec(actionBarHeight, MeasureSpec.EXACTLY);
setMeasuredDimension(width, actionBarHeight + extraHeight + (occupyStatusBar ? AndroidUtilities.statusBarHeight : 0));
int textLeft;
if (backButtonImageView != null && backButtonImageView.getVisibility() != GONE) {
backButtonImageView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(54), MeasureSpec.EXACTLY), actionBarHeightSpec);
textLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 80 : 72);
} else {
textLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 26 : 18);
}
if (menu != null && menu.getVisibility() != GONE) {
int menuWidth;
if (isSearchFieldVisible) {
menuWidth = MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(AndroidUtilities.isTablet() ? 74 : 66), MeasureSpec.EXACTLY);
} else {
menuWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
}
menu.measure(menuWidth, actionBarHeightSpec);
}
if (titleTextView != null && titleTextView.getVisibility() != GONE || subTitleTextView != null && subTitleTextView.getVisibility() != GONE) {
int availableWidth = width - (menu != null ? menu.getMeasuredWidth() : 0) - AndroidUtilities.dp(16) - textLeft;
if (titleTextView != null && titleTextView.getVisibility() != GONE) {
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, !AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 18 : 20);
titleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(actionBarHeight, MeasureSpec.AT_MOST));
}
if (subTitleTextView != null && subTitleTextView.getVisibility() != GONE) {
subTitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, !AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 14 : 16);
subTitleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(actionBarHeight, MeasureSpec.AT_MOST));
}
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE || child == titleTextView || child == subTitleTextView || child == menu || child == backButtonImageView) {
continue;
}
measureChildWithMargins(child, widthMeasureSpec, 0, MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY), 0);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int additionalTop = occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
int textLeft;
if (backButtonImageView != null && backButtonImageView.getVisibility() != GONE) {
backButtonImageView.layout(0, additionalTop, backButtonImageView.getMeasuredWidth(), additionalTop + backButtonImageView.getMeasuredHeight());
textLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 80 : 72);
} else {
textLeft = AndroidUtilities.dp(AndroidUtilities.isTablet() ? 26 : 18);
}
if (menu != null && menu.getVisibility() != GONE) {
int menuLeft = isSearchFieldVisible ? AndroidUtilities.dp(AndroidUtilities.isTablet() ? 74 : 66) : (right - left) - menu.getMeasuredWidth();
menu.layout(menuLeft, additionalTop, menuLeft + menu.getMeasuredWidth(), additionalTop + menu.getMeasuredHeight());
}
int offset = AndroidUtilities.dp(!AndroidUtilities.isTablet() && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 1 : 2);
if (titleTextView != null && titleTextView.getVisibility() != GONE) {
int textTop;
if (subTitleTextView != null && subTitleTextView.getVisibility() != GONE) {
textTop = (getCurrentActionBarHeight() / 2 - titleTextView.getMeasuredHeight()) / 2 + offset;
} else {
textTop = (getCurrentActionBarHeight() - titleTextView.getMeasuredHeight()) / 2 - AndroidUtilities.dp(1);
}
titleTextView.layout(textLeft, additionalTop + textTop, textLeft + titleTextView.getMeasuredWidth(), additionalTop + textTop + titleTextView.getMeasuredHeight());
}
if (subTitleTextView != null && subTitleTextView.getVisibility() != GONE) {
int textTop = getCurrentActionBarHeight() / 2 + (getCurrentActionBarHeight() / 2 - subTitleTextView.getMeasuredHeight()) / 2 - offset;
subTitleTextView.layout(textLeft, additionalTop + textTop, textLeft + subTitleTextView.getMeasuredWidth(), additionalTop + textTop + subTitleTextView.getMeasuredHeight());
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE || child == titleTextView || child == subTitleTextView || child == menu || child == backButtonImageView) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = Gravity.TOP | Gravity.LEFT;
}
final int absoluteGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = (right - left - width) / 2 + lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = right - width - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = (bottom - top - height) / 2 + lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = (bottom - top) - height - lp.bottomMargin;
break;
default:
childTop = lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
public void onMenuButtonPressed() {
@ -474,10 +467,13 @@ public class ActionBar extends FrameLayout {
if (titleTextView != null) {
titleTextView.setVisibility(textToSet != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
titleTextView.setText(textToSet);
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
}
public boolean isSearchFieldVisible() {
return isSearchFieldVisible;
}
public void setExtraHeight(int value, boolean layout) {
extraHeight = value;
if (layout) {
@ -520,4 +516,14 @@ public class ActionBar extends FrameLayout {
super.onTouchEvent(event);
return true;
}
public static int getCurrentActionBarHeight() {
if (AndroidUtilities.isTablet()) {
return AndroidUtilities.dp(64);
} else if (ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return AndroidUtilities.dp(48);
} else {
return AndroidUtilities.dp(56);
}
}
}

View file

@ -59,7 +59,7 @@ public class ActionBarLayout extends FrameLayout {
if (child instanceof ActionBar) {
return super.drawChild(canvas, child, drawingTime);
} else {
boolean wasActionBar = false;
//boolean wasActionBar = false;
int actionBarHeight = 0;
int childCount = getChildCount();
for (int a = 0; a < childCount; a++) {
@ -339,7 +339,7 @@ public class ActionBarLayout extends FrameLayout {
BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 2);
View fragmentView = lastFragment.fragmentView;
if (fragmentView == null) {
fragmentView = lastFragment.createView(parentActivity, parentActivity.getLayoutInflater());
fragmentView = lastFragment.createView(parentActivity);
} else {
ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) {
@ -625,7 +625,7 @@ public class ActionBarLayout extends FrameLayout {
fragment.setParentLayout(this);
View fragmentView = fragment.fragmentView;
if (fragmentView == null) {
fragmentView = fragment.createView(parentActivity, parentActivity.getLayoutInflater());
fragmentView = fragment.createView(parentActivity);
} else {
ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) {
@ -829,7 +829,7 @@ public class ActionBarLayout extends FrameLayout {
previousFragment.setParentLayout(this);
View fragmentView = previousFragment.fragmentView;
if (fragmentView == null) {
fragmentView = previousFragment.createView(parentActivity, parentActivity.getLayoutInflater());
fragmentView = previousFragment.createView(parentActivity);
} else {
ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) {
@ -972,7 +972,7 @@ public class ActionBarLayout extends FrameLayout {
previousFragment.setParentLayout(this);
View fragmentView = previousFragment.fragmentView;
if (fragmentView == null) {
fragmentView = previousFragment.createView(parentActivity, parentActivity.getLayoutInflater());
fragmentView = previousFragment.createView(parentActivity);
} else {
ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) {

View file

@ -36,7 +36,7 @@ public class ActionBarMenu extends LinearLayout {
View view = li.inflate(resourceId, null);
view.setTag(id);
addView(view);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)view.getLayoutParams();
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
layoutParams.height = LayoutHelper.MATCH_PARENT;
view.setBackgroundResource(parentActionBar.itemsBackgroundResourceId);
view.setLayoutParams(layoutParams);
@ -74,14 +74,14 @@ public class ActionBarMenu extends LinearLayout {
menuItem.iconView.setImageResource(icon);
}
addView(menuItem);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)menuItem.getLayoutParams();
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) menuItem.getLayoutParams();
layoutParams.height = LayoutHelper.MATCH_PARENT;
layoutParams.width = width;
menuItem.setLayoutParams(layoutParams);
menuItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
ActionBarMenuItem item = (ActionBarMenuItem)view;
ActionBarMenuItem item = (ActionBarMenuItem) view;
if (item.hasSubMenu()) {
if (parentActionBar.actionBarMenuOnItemClick.canOpenMenu()) {
item.toggleSubMenu();
@ -89,7 +89,7 @@ public class ActionBarMenu extends LinearLayout {
} else if (item.isSearchField()) {
parentActionBar.onSearchFieldVisibilityChanged(item.toggleSearch());
} else {
onItemClick((Integer)view.getTag());
onItemClick((Integer) view.getTag());
}
}
});
@ -100,7 +100,7 @@ public class ActionBarMenu extends LinearLayout {
for (int a = 0; a < getChildCount(); a++) {
View view = getChildAt(a);
if (view instanceof ActionBarMenuItem) {
((ActionBarMenuItem)view).closeSubMenu();
((ActionBarMenuItem) view).closeSubMenu();
}
}
}
@ -122,10 +122,16 @@ public class ActionBarMenu extends LinearLayout {
for (int a = 0; a < getChildCount(); a++) {
View view = getChildAt(a);
if (view instanceof ActionBarMenuItem) {
ActionBarMenuItem item = (ActionBarMenuItem)view;
if (item.hasSubMenu() && item.getVisibility() == VISIBLE) {
ActionBarMenuItem item = (ActionBarMenuItem) view;
if (item.getVisibility() != VISIBLE) {
continue;
}
if (item.hasSubMenu()) {
item.toggleSubMenu();
break;
} else if (item.overrideMenuClick) {
onItemClick((Integer) item.getTag());
break;
}
}
}
@ -135,7 +141,7 @@ public class ActionBarMenu extends LinearLayout {
for (int a = 0; a < getChildCount(); a++) {
View view = getChildAt(a);
if (view instanceof ActionBarMenuItem) {
ActionBarMenuItem item = (ActionBarMenuItem)view;
ActionBarMenuItem item = (ActionBarMenuItem) view;
if (item.isSearchField()) {
parentActionBar.onSearchFieldVisibilityChanged(item.toggleSearch());
break;
@ -148,7 +154,7 @@ public class ActionBarMenu extends LinearLayout {
for (int a = 0; a < getChildCount(); a++) {
View view = getChildAt(a);
if (view instanceof ActionBarMenuItem) {
ActionBarMenuItem item = (ActionBarMenuItem)view;
ActionBarMenuItem item = (ActionBarMenuItem) view;
if (item.isSearchField()) {
if (toggle) {
parentActionBar.onSearchFieldVisibilityChanged(item.toggleSearch());
@ -164,7 +170,7 @@ public class ActionBarMenu extends LinearLayout {
public ActionBarMenuItem getItem(int id) {
View v = findViewWithTag(id);
if (v instanceof ActionBarMenuItem) {
return (ActionBarMenuItem)v;
return (ActionBarMenuItem) v;
}
return null;
}

View file

@ -82,6 +82,8 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
private int subMenuOpenSide = 0;
private ActionBarMenuItemDelegate delegate;
private boolean allowCloseAnimation = true;
protected boolean overrideMenuClick;
private boolean processedPopupClick;
public ActionBarMenuItem(Context context, ActionBarMenu menu, int background) {
super(context);
@ -129,21 +131,23 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
x -= location[0];
y -= location[1];
selectedMenuView = null;
for (int a = 0; a < popupLayout.getChildCount(); a++) {
View child = popupLayout.getChildAt(a);
for (int a = 0; a < popupLayout.getItemsCount(); a++) {
View child = popupLayout.getItemAt(a);
child.getHitRect(rect);
if ((Integer) child.getTag() < 100) {
if (!rect.contains((int) x, (int) y)) {
child.setPressed(false);
child.setSelected(false);
if (Build.VERSION.SDK_INT >= 21) {
if (Build.VERSION.SDK_INT == 21) {
child.getBackground().setVisible(false, false);
}
} else {
child.setPressed(true);
child.setSelected(true);
if (Build.VERSION.SDK_INT >= 21) {
child.getBackground().setVisible(true, false);
if (Build.VERSION.SDK_INT == 21) {
child.getBackground().setVisible(true, false);
}
child.drawableHotspotChanged(x, y - child.getTop());
}
selectedMenuView = child;
@ -192,9 +196,6 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
rect = new Rect();
location = new int[2];
popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext());
popupLayout.setOrientation(LinearLayout.VERTICAL);
popupLayout.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
//popupLayout.setBackgroundResource(R.drawable.popup_fixed);
popupLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@ -252,6 +253,10 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
@Override
public void onClick(View view) {
if (popupWindow != null && popupWindow.isShowing()) {
if (processedPopupClick) {
return;
}
processedPopupClick = true;
popupWindow.dismiss(allowCloseAnimation);
}
if (parentMenu != null) {
@ -306,6 +311,7 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
}
});
}
processedPopupClick = false;
popupWindow.setFocusable(true);
if (popupLayout.getMeasuredWidth() == 0) {
updateOrShowPopup(true, true);
@ -367,6 +373,11 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
return setIsSearchField(value, true);
}
public ActionBarMenuItem setOverrideMenuClick(boolean value) {
overrideMenuClick = value;
return this;
}
public ActionBarMenuItem setIsSearchField(boolean value, boolean needClearButton) {
if (parentMenu == null) {
return this;
@ -389,7 +400,8 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
searchField.setSingleLine(true);
searchField.setBackgroundResource(0);
searchField.setPadding(0, 0, 0, 0);
searchField.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
int inputType = searchField.getInputType() | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
searchField.setInputType(inputType);
if (android.os.Build.VERSION.SDK_INT < 11) {
searchField.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
@ -467,7 +479,7 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
layoutParams2.width = LayoutHelper.MATCH_PARENT;
layoutParams2.gravity = Gravity.CENTER_VERTICAL;
layoutParams2.height = AndroidUtilities.dp(36);
layoutParams2.rightMargin = AndroidUtilities.dp(48);
layoutParams2.rightMargin = needClearButton ? AndroidUtilities.dp(48) : 0;
searchField.setLayoutParams(layoutParams2);
if (needClearButton) {
@ -532,6 +544,10 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
}
}
if (show) {
popupLayout.scrollToTop();
}
if (subMenuOpenSide == 0) {
if (showFromBottom) {
if (show) {
@ -574,10 +590,6 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
if (view != null) {
view.setVisibility(GONE);
}
view = popupLayout.findViewWithTag(100 + id);
if (view != null) {
view.setVisibility(GONE);
}
}
public void showSubItem(int id) {
@ -585,9 +597,5 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
if (view != null) {
view.setVisibility(VISIBLE);
}
view = popupLayout.findViewWithTag(100 + id);
if (view != null) {
view.setVisibility(VISIBLE);
}
}
}

View file

@ -19,14 +19,18 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper;
import java.lang.reflect.Field;
import java.util.HashMap;
@ -62,7 +66,7 @@ public class ActionBarPopupWindow extends PopupWindow {
void onDispatchKeyEvent(KeyEvent keyEvent);
}
public static class ActionBarPopupWindowLayout extends LinearLayout {
public static class ActionBarPopupWindowLayout extends FrameLayout {
private OnDispatchKeyEventListener mOnDispatchKeyEventListener;
protected static Drawable backgroundDrawable;
@ -73,13 +77,26 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean showedFromBotton;
private HashMap<View, Integer> positions = new HashMap<>();
private ScrollView scrollView;
private LinearLayout linearLayout;
public ActionBarPopupWindowLayout(Context context) {
super(context);
setWillNotDraw(false);
if (backgroundDrawable == null) {
backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed);
}
setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
setWillNotDraw(false);
scrollView = new ScrollView(context);
scrollView.setVerticalScrollBarEnabled(false);
addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.VERTICAL);
scrollView.addView(linearLayout, new ScrollView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
public void setShowedFromBotton(boolean value) {
@ -106,15 +123,15 @@ public class ActionBarPopupWindow extends PopupWindow {
public void setBackScaleY(float value) {
backScaleY = value;
if (animationEnabled) {
int count = getChildCount();
int count = getItemsCount();
int visibleCount = 0;
for (int a = 0; a < count; a++) {
visibleCount += getChildAt(a).getVisibility() == VISIBLE ? 1 : 0;
visibleCount += getItemAt(a).getVisibility() == VISIBLE ? 1 : 0;
}
int height = getMeasuredHeight() - AndroidUtilities.dp(16);
if (showedFromBotton) {
for (int a = lastStartedChild; a >= 0; a--) {
View child = getChildAt(a);
View child = getItemAt(a);
if (child.getVisibility() != VISIBLE) {
continue;
}
@ -127,7 +144,7 @@ public class ActionBarPopupWindow extends PopupWindow {
}
} else {
for (int a = lastStartedChild; a < count; a++) {
View child = getChildAt(a);
View child = getItemAt(a);
if (child.getVisibility() != VISIBLE) {
continue;
}
@ -155,6 +172,11 @@ public class ActionBarPopupWindow extends PopupWindow {
}
}
@Override
public void addView(View child) {
linearLayout.addView(child);
}
public float getBackScaleX() {
return backScaleX;
}
@ -183,6 +205,18 @@ public class ActionBarPopupWindow extends PopupWindow {
backgroundDrawable.draw(canvas);
}
}
public int getItemsCount() {
return linearLayout.getChildCount();
}
public View getItemAt(int index) {
return linearLayout.getChildAt(index);
}
public void scrollToTop() {
scrollView.scrollTo(0, 0);
}
}
public ActionBarPopupWindow() {
@ -269,11 +303,11 @@ public class ActionBarPopupWindow extends PopupWindow {
content.setAlpha(1.0f);
content.setPivotX(content.getMeasuredWidth());
content.setPivotY(0);
int count = content.getChildCount();
int count = content.getItemsCount();
content.positions.clear();
int visibleCount = 0;
for (int a = 0; a < count; a++) {
View child = content.getChildAt(a);
View child = content.getItemAt(a);
if (child.getVisibility() != View.VISIBLE) {
continue;
}

View file

@ -14,7 +14,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -44,7 +43,7 @@ public class BaseFragment {
classGuid = ConnectionsManager.getInstance().generateClassGuid();
}
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
return null;
}

View file

@ -9,10 +9,18 @@
package org.telegram.ui.ActionBar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@ -45,7 +53,7 @@ import java.util.ArrayList;
public class BottomSheet extends Dialog {
private LinearLayout linearLayout;
private LinearLayout containerView;
private FrameLayout container;
private boolean dismissed;
@ -57,24 +65,73 @@ public class BottomSheet extends Dialog {
private int[] itemIcons;
private View customView;
private CharSequence title;
private boolean overrideTabletWidth = true;
private boolean fullWidth;
private boolean isGrid;
private ColorDrawable backgroundDrawable = new ColorDrawable(0xff000000);
private static Drawable shadowDrawable;
private Paint ciclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static int backgroundPaddingTop;
private static int backgroundPaddingLeft;
private boolean useRevealAnimation;
private float revealRadius;
private int revealX;
private int revealY;
private boolean useRevealAnimation;
private boolean applyTopPaddings = true;
private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();
private AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator();
private ArrayList<BottomSheetCell> itemViews = new ArrayList<>();
private BottomSheetDelegate delegate;
private BottomSheetDelegateInterface delegate;
public interface BottomSheetDelegate {
public interface BottomSheetDelegateInterface {
void onOpenAnimationStart();
void onOpenAnimationEnd();
void onRevealAnimationStart(boolean open);
void onRevealAnimationEnd(boolean open);
void onRevealAnimationProgress(boolean open, float radius, int x, int y);
View getRevealView();
}
public static class BottomSheetDelegate implements BottomSheetDelegateInterface {
@Override
public void onOpenAnimationStart() {
}
@Override
public void onOpenAnimationEnd() {
}
@Override
public void onRevealAnimationStart(boolean open) {
}
@Override
public void onRevealAnimationEnd(boolean open) {
}
@Override
public void onRevealAnimationProgress(boolean open, float radius, int x, int y) {
}
@Override
public View getRevealView() {
return null;
}
}
private static class BottomSheetCell extends FrameLayout {
@ -139,7 +196,40 @@ public class BottomSheet extends Dialog {
public BottomSheet(Context context) {
super(context);
container = new FrameLayout(getContext());
container = new FrameLayout(getContext()) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
boolean isPortrait = width < height;
if (containerView != null) {
int left = useRevealAnimation && Build.VERSION.SDK_INT <= 19 ? 0 : backgroundPaddingLeft;
if (!fullWidth) {
if (AndroidUtilities.isTablet()) {
int side = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.8f);
containerView.measure(MeasureSpec.makeMeasureSpec(side + left * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
} else {
int maxWidth = Math.min(AndroidUtilities.dp(480), width);
containerView.measure(isPortrait ? MeasureSpec.makeMeasureSpec(width + left * 2, MeasureSpec.EXACTLY) : MeasureSpec.makeMeasureSpec((int) Math.max(width * 0.8f, maxWidth) + left * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
} else {
containerView.measure(MeasureSpec.makeMeasureSpec(width + left * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (containerView != null) {
int l = ((right - left) - containerView.getMeasuredWidth()) / 2;
int t = (bottom - top) - containerView.getMeasuredHeight();
containerView.layout(l, t, l + containerView.getMeasuredWidth(), t + getMeasuredHeight());
}
}
};
container.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@ -159,25 +249,37 @@ public class BottomSheet extends Dialog {
window.requestFeature(Window.FEATURE_NO_TITLE);
window.setWindowAnimations(R.style.DialogNoAnimation);
setContentView(container);
linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
if (AndroidUtilities.isTablet() && !overrideTabletWidth) {
container.addView(linearLayout, 0, LayoutHelper.createFrame(320, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL));
} else {
container.addView(linearLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM));
if (shadowDrawable == null) {
Rect padding = new Rect();
shadowDrawable = getContext().getResources().getDrawable(R.drawable.sheet_shadow);
shadowDrawable.getPadding(padding);
backgroundPaddingLeft = padding.left;
backgroundPaddingTop = padding.top;
}
View shadow = new View(getContext());
shadow.setBackgroundResource(R.drawable.header_shadow_reverse);
linearLayout.addView(shadow, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 3));
setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
LinearLayout containerView = new LinearLayout(getContext());
containerView.setBackgroundColor(0xffffffff);
ciclePaint.setColor(0xffffffff);
containerView = new LinearLayout(getContext()) {
@Override
protected void onDraw(Canvas canvas) {
if (useRevealAnimation && Build.VERSION.SDK_INT <= 19) {
canvas.drawCircle(revealX, revealY, revealRadius, ciclePaint);
//shadowDrawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
//shadowDrawable.draw(canvas);
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(canvas, child, drawingTime);
}
};
containerView.setWillNotDraw(false);
containerView.setOrientation(LinearLayout.VERTICAL);
containerView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(isGrid ? 16 : 8));
linearLayout.addView(containerView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
container.addView(containerView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM));
if (title != null) {
TextView titleView = new TextView(getContext());
@ -273,104 +375,179 @@ public class BottomSheet extends Dialog {
setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
if (useRevealAnimation) {
int finalRadius = Math.max(AndroidUtilities.displaySize.x, container.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(container, revealX, revealY, 0, finalRadius);
anim.setDuration(400);
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (delegate != null) {
delegate.onOpenAnimationStart();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (delegate != null) {
delegate.onOpenAnimationEnd();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
} else {
//startLayoutAnimation(true, true);
ViewProxy.setTranslationY(linearLayout, linearLayout.getHeight());
backgroundDrawable.setAlpha(0);
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(linearLayout, "translationY", 0),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 51));
animatorSetProxy.setDuration(200);
animatorSetProxy.setStartDelay(20);
animatorSetProxy.setInterpolator(new DecelerateInterpolator());
animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() {
@Override
public void onAnimationEnd(Object animation) {
if (delegate != null) {
delegate.onOpenAnimationEnd();
}
}
});
animatorSetProxy.start();
if (Build.VERSION.SDK_INT >= 21) {
startOpenAnimation();
}
}
});
}
private float animationProgress;
private long lastFrameTime;
private void startLayoutAnimation(final boolean open, final boolean first) {
if (first) {
animationProgress = 0.0f;
lastFrameTime = System.nanoTime() / 1000000;
if (Build.VERSION.SDK_INT >= 11) {
container.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@Override
public void show() {
super.show();
dismissed = false;
if (Build.VERSION.SDK_INT >= 21 || !useRevealAnimation) {
containerView.setBackgroundDrawable(shadowDrawable);
} else {
containerView.setBackgroundDrawable(null);
}
int left = useRevealAnimation && Build.VERSION.SDK_INT <= 19 ? 0 : backgroundPaddingLeft;
int top = useRevealAnimation && Build.VERSION.SDK_INT <= 19 ? 0 : backgroundPaddingTop;
containerView.setPadding(left, (applyTopPaddings ? AndroidUtilities.dp(8) : 0) + top, left, (applyTopPaddings ? AndroidUtilities.dp(isGrid ? 16 : 8) : 0));
if (Build.VERSION.SDK_INT < 21) {
startOpenAnimation();
}
}
protected void setRevealRadius(float radius) {
revealRadius = radius;
delegate.onRevealAnimationProgress(!dismissed, radius, revealX, revealY);
if (Build.VERSION.SDK_INT <= 19) {
containerView.invalidate();
}
}
protected float getRevealRadius() {
return revealRadius;
}
@SuppressLint("NewApi")
private void startRevealAnimation(final boolean open) {
if (open) {
backgroundDrawable.setAlpha(0);
containerView.setVisibility(View.VISIBLE);
} else {
backgroundDrawable.setAlpha(51);
}
ViewProxy.setTranslationY(containerView, 0);
AnimatorSet animatorSet = new AnimatorSet();
View view = delegate.getRevealView();
if (view.getVisibility() == View.VISIBLE && ((ViewGroup) view.getParent()).getVisibility() == View.VISIBLE) {
final int coords[] = new int[2];
view.getLocationInWindow(coords);
float top;
if (Build.VERSION.SDK_INT <= 19) {
top = AndroidUtilities.displaySize.y - containerView.getMeasuredHeight() - AndroidUtilities.statusBarHeight;
} else {
top = containerView.getY();
}
revealX = coords[0] + view.getMeasuredWidth() / 2;
revealY = (int) (coords[1] + view.getMeasuredHeight() / 2 - top);
if (Build.VERSION.SDK_INT <= 19) {
revealY -= AndroidUtilities.statusBarHeight;
}
} else {
revealX = AndroidUtilities.displaySize.x / 2 + backgroundPaddingLeft;
revealY = (int) (AndroidUtilities.displaySize.y - containerView.getY());
}
int corners[][] = new int[][]{
{0, 0},
{0, containerView.getMeasuredHeight()},
{containerView.getMeasuredWidth(), 0},
{containerView.getMeasuredWidth(), containerView.getMeasuredHeight()}
};
int finalRevealRadius = 0;
for (int a = 0; a < 4; a++) {
finalRevealRadius = Math.max(finalRevealRadius, (int) Math.ceil(Math.sqrt((revealX - corners[a][0]) * (revealX - corners[a][0]) + (revealY - corners[a][1]) * (revealY - corners[a][1]))));
}
ArrayList<Animator> animators = new ArrayList<>(3);
animators.add(ObjectAnimator.ofFloat(this, "revealRadius", open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0));
animators.add(ObjectAnimator.ofInt(backgroundDrawable, "alpha", open ? 51 : 0));
if (Build.VERSION.SDK_INT >= 21) {
containerView.setElevation(AndroidUtilities.dp(10));
animators.add(ViewAnimationUtils.createCircularReveal(containerView, revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth(), revealY, open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0));
animatorSet.setDuration(300);
} else {
if (!open) {
animatorSet.setDuration(200);
containerView.setPivotX(revealX <= containerView.getMeasuredWidth() ? revealX : containerView.getMeasuredWidth());
containerView.setPivotY(revealY);
animators.add(ObjectAnimator.ofFloat(containerView, "scaleX", 0.0f));
animators.add(ObjectAnimator.ofFloat(containerView, "scaleY", 0.0f));
animators.add(ObjectAnimator.ofFloat(containerView, "alpha", 0.0f));
} else {
animatorSet.setDuration(250);
containerView.setScaleX(1);
containerView.setScaleY(1);
containerView.setAlpha(1);
if (Build.VERSION.SDK_INT <= 19) {
animatorSet.setStartDelay(20);
}
}
}
AndroidUtilities.runOnUIThread(new Runnable() {
animatorSet.playTogether(animators);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void run() {
long newTime = System.nanoTime() / 1000000;
long dt = newTime - lastFrameTime;
FileLog.e("tmessages", "dt = " + dt);
if (dt > 16) {
dt = 16;
}
lastFrameTime = newTime;
animationProgress += dt / 200.0f;
if (animationProgress > 1.0f) {
animationProgress = 1.0f;
public void onAnimationStart(Animator animation) {
if (delegate != null) {
delegate.onRevealAnimationStart(open);
}
}
if (open) {
float interpolated = decelerateInterpolator.getInterpolation(animationProgress);
ViewProxy.setTranslationY(linearLayout, linearLayout.getHeight() * (1.0f - interpolated));
backgroundDrawable.setAlpha((int) (51 * interpolated));
} else {
float interpolated = accelerateInterpolator.getInterpolation(animationProgress);
ViewProxy.setTranslationY(linearLayout, linearLayout.getHeight() * interpolated);
backgroundDrawable.setAlpha((int) (51 * (1.0f - interpolated)));
@Override
public void onAnimationEnd(Animator animation) {
if (delegate != null) {
delegate.onRevealAnimationEnd(open);
}
if (animationProgress < 1) {
startLayoutAnimation(open, false);
} else {
if (open && delegate != null) {
delegate.onOpenAnimationEnd();
containerView.invalidate();
if (Build.VERSION.SDK_INT >= 11) {
container.setLayerType(View.LAYER_TYPE_NONE, null);
}
if (!open) {
containerView.setVisibility(View.INVISIBLE);
try {
BottomSheet.super.dismiss();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
}
@Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
});
animatorSet.start();
}
private void startOpenAnimation() {
if (Build.VERSION.SDK_INT >= 20) {
container.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
if (containerView.getMeasuredHeight() == 0) {
containerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, View.MeasureSpec.AT_MOST));
}
if (useRevealAnimation) {
startRevealAnimation(true);
} else {
ViewProxy.setTranslationY(containerView, containerView.getMeasuredHeight());
backgroundDrawable.setAlpha(0);
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 51));
animatorSetProxy.setDuration(200);
animatorSetProxy.setStartDelay(20);
animatorSetProxy.setInterpolator(new DecelerateInterpolator());
animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() {
@Override
public void onAnimationEnd(Object animation) {
if (delegate != null) {
delegate.onOpenAnimationEnd();
}
if (Build.VERSION.SDK_INT >= 11) {
container.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
});
animatorSetProxy.start();
}
}
public void setDelegate(BottomSheetDelegate delegate) {
@ -382,7 +559,7 @@ public class BottomSheet extends Dialog {
}
public LinearLayout getSheetContainer() {
return linearLayout;
return containerView;
}
public int getTag() {
@ -397,13 +574,14 @@ public class BottomSheet extends Dialog {
cell.textView.setText(text);
}
private void dismissWithButtonClick(final int item) {
public void dismissWithButtonClick(final int item) {
if (dismissed) {
return;
}
dismissed = true;
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(linearLayout, "translationY", linearLayout.getHeight() + AndroidUtilities.dp(10)),
ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0)
);
animatorSetProxy.setDuration(180);
@ -440,34 +618,38 @@ public class BottomSheet extends Dialog {
return;
}
dismissed = true;
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(linearLayout, "translationY", linearLayout.getHeight() + AndroidUtilities.dp(10)),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0)
);
animatorSetProxy.setDuration(180);
animatorSetProxy.setInterpolator(new AccelerateInterpolator());
animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() {
@Override
public void onAnimationEnd(Object animation) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
try {
BottomSheet.super.dismiss();
} catch (Exception e) {
FileLog.e("tmessages", e);
if (useRevealAnimation) {
startRevealAnimation(false);
} else {
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0)
);
animatorSetProxy.setDuration(180);
animatorSetProxy.setInterpolator(new AccelerateInterpolator());
animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() {
@Override
public void onAnimationEnd(Object animation) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
try {
BottomSheet.super.dismiss();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
}
});
}
});
}
@Override
public void onAnimationCancel(Object animation) {
onAnimationEnd(animation);
}
});
animatorSetProxy.start();
@Override
public void onAnimationCancel(Object animation) {
onAnimationEnd(animation);
}
});
animatorSetProxy.start();
}
}
public static class Builder {
@ -515,10 +697,10 @@ public class BottomSheet extends Dialog {
return this;
}
public Builder setRevealAnimation(int x, int y) {
bottomSheet.revealX = x;
bottomSheet.revealY = y;
bottomSheet.useRevealAnimation = true;
public Builder setUseRevealAnimation() {
if (Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet()) {
bottomSheet.useRevealAnimation = true;
}
return this;
}
@ -532,8 +714,13 @@ public class BottomSheet extends Dialog {
return this;
}
public BottomSheet setOverrideTabletWidth(boolean value) {
bottomSheet.overrideTabletWidth = value;
public Builder setApplyTopPaddings(boolean value) {
bottomSheet.applyTopPaddings = value;
return this;
}
public BottomSheet setUseFullWidth(boolean value) {
bottomSheet.fullWidth = value;
return bottomSheet;
}
}

View file

@ -383,7 +383,6 @@ public class DrawerLayoutContainer extends FrameLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
inLayout = true;
final int width = r - l;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@ -416,8 +415,6 @@ public class DrawerLayoutContainer extends FrameLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

View file

@ -72,7 +72,6 @@ public class CountrySearchAdapter extends BaseFragmentAdapter {
updateSearchResults(new ArrayList<Country>());
return;
}
long time = System.currentTimeMillis();
ArrayList<Country> resultArray = new ArrayList<>();
String n = query.substring(0, 1);

View file

@ -19,7 +19,7 @@ import org.telegram.ui.Cells.PhotoAttachPhotoCell;
import java.util.HashMap;
public class PhotoAttachAdapter extends RecyclerView.Adapter implements NotificationCenter.NotificationCenterDelegate {
public class PhotoAttachAdapter extends RecyclerView.Adapter {
private Context mContext;
private PhotoAttachAdapterDelegate delegate;
@ -38,14 +38,6 @@ public class PhotoAttachAdapter extends RecyclerView.Adapter implements Notifica
public PhotoAttachAdapter(Context context) {
mContext = context;
NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded);
if (MediaController.allPhotosAlbumEntry == null) {
MediaController.loadGalleryPhotosAlbums(0);
}
}
public void onDestroy() {
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.albumsDidLoaded);
}
public void clearSelectedPhotos() {
@ -64,13 +56,6 @@ public class PhotoAttachAdapter extends RecyclerView.Adapter implements Notifica
delegate = photoAttachAdapterDelegate;
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.albumsDidLoaded) {
notifyDataSetChanged();
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//if (position != 0) {

View file

@ -87,7 +87,7 @@ public class StickersAdapter extends RecyclerView.Adapter implements Notificatio
}
public void loadStikersForEmoji(CharSequence emoji) {
boolean search = emoji != null && emoji.length() != 0 && emoji.length() <= 2;
boolean search = emoji != null && emoji.length() > 0 && emoji.length() <= 4;
if (search) {
lastSticker = emoji.toString();
HashMap<String, ArrayList<TLRPC.Document>> allStickers = StickersQuery.getAllStickers();

View file

@ -0,0 +1,464 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.ImageLoader;
import org.telegram.android.MediaController;
import org.telegram.android.MessageObject;
import org.telegram.android.NotificationCenter;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.LineProgressView;
import java.io.File;
public class AudioPlayerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, MediaController.FileDownloadProgressListener {
private MessageObject lastMessageObject;
private ImageView placeholder;
private ImageView playButton;
private ImageView nextButton;
private ImageView prevButton;
private ImageView shuffleButton;
private LineProgressView progressView;
private ImageView repeatButton;
private ImageView[] buttons = new ImageView[5];
private TextView durationTextView;
private TextView timeTextView;
private SeekBarView seekBarView;
private int TAG;
private String lastTimeString;
private class SeekBarView extends FrameLayout {
private Paint innerPaint1;
private Paint outerPaint1;
private int thumbWidth;
private int thumbHeight;
public int thumbX = 0;
public int thumbDX = 0;
private boolean pressed = false;
public SeekBarView(Context context) {
super(context);
setWillNotDraw(false);
innerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
innerPaint1.setColor(0x19000000);
outerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
outerPaint1.setColor(0xff23afef);
thumbWidth = AndroidUtilities.dp(24);
thumbHeight = AndroidUtilities.dp(24);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return onTouch(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onTouch(event);
}
boolean onTouch(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
getParent().requestDisallowInterceptTouchEvent(true);
int additionWidth = (getMeasuredHeight() - thumbWidth) / 2;
if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) {
pressed = true;
thumbDX = (int)(ev.getX() - thumbX);
invalidate();
return true;
}
} else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
if (pressed) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth));
}
pressed = false;
invalidate();
return true;
}
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (pressed) {
thumbX = (int)(ev.getX() - thumbDX);
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - thumbWidth) {
thumbX = getMeasuredWidth() - thumbWidth;
}
invalidate();
return true;
}
}
return false;
}
public void setProgress(float progress) {
int newThumbX = (int)Math.ceil((getMeasuredWidth() - thumbWidth) * progress);
if (thumbX != newThumbX) {
thumbX = newThumbX;
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - thumbWidth) {
thumbX = getMeasuredWidth() - thumbWidth;
}
invalidate();
}
}
public boolean isDragging() {
return pressed;
}
@Override
protected void onDraw(Canvas canvas) {
int y = (getMeasuredHeight() - thumbHeight) / 2;
canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() - thumbWidth / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), innerPaint1);
canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), thumbWidth / 2 + thumbX, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1);
canvas.drawCircle(thumbX + thumbWidth / 2, y + thumbHeight / 2, AndroidUtilities.dp(pressed ? 8 : 6), outerPaint1);
}
}
@Override
public boolean onFragmentCreate() {
TAG = MediaController.getInstance().generateObserverTag();
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged);
return super.onFragmentCreate();
}
@Override
public void onFragmentDestroy() {
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioProgressDidChanged);
MediaController.getInstance().removeLoadingFileObserver(this);
super.onFragmentDestroy();
}
@Override
public View createView(Context context) {
FrameLayout frameLayout = new FrameLayout(context);
frameLayout.setBackgroundColor(0xfff0f0f0);
frameLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
fragmentView = frameLayout;
actionBar.setBackgroundColor(0xffffffff);
actionBar.setBackButtonImage(R.drawable.pl_back);
actionBar.setItemsBackground(R.drawable.bar_selector_audio);
if (!AndroidUtilities.isTablet()) {
actionBar.showActionModeTop();
}
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
if (id == -1) {
finishFragment();
}
}
});
placeholder = new ImageView(context);
frameLayout.addView(placeholder, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 66));
View shadow = new View(context);
shadow.setBackgroundResource(R.drawable.header_shadow_reverse);
frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 96));
FrameLayout seekBarContainer = new FrameLayout(context);
seekBarContainer.setBackgroundColor(0xe5ffffff);
frameLayout.addView(seekBarContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 66));
timeTextView = new TextView(context);
timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
timeTextView.setTextColor(0xff19a7e8);
timeTextView.setGravity(Gravity.CENTER);
timeTextView.setText("0:00");
seekBarContainer.addView(timeTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT));
durationTextView = new TextView(context);
durationTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
durationTextView.setTextColor(0xff8a8a8a);
durationTextView.setGravity(Gravity.CENTER);
durationTextView.setText("3:00");
seekBarContainer.addView(durationTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT));
seekBarView = new SeekBarView(context);
seekBarContainer.addView(seekBarView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 32, 0, 32, 0));
progressView = new LineProgressView(context);
progressView.setVisibility(View.INVISIBLE);
progressView.setBackgroundColor(0x19000000);
progressView.setProgressColor(0xff23afef);
seekBarContainer.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 2, Gravity.CENTER_VERTICAL | Gravity.LEFT, 44, 0, 44, 0));
FrameLayout bottomView = new FrameLayout(context) {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int dist = ((right - left) - AndroidUtilities.dp(30 + 48 * 5)) / 4;
for (int a = 0; a < 5; a++) {
int l = AndroidUtilities.dp(15 + 48 * a) + dist * a;
int t = AndroidUtilities.dp(9);
buttons[a].layout(l, t, l + buttons[a].getMeasuredWidth(), t + buttons[a].getMeasuredHeight());
}
}
};
bottomView.setBackgroundColor(0xffffffff);
frameLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 66, Gravity.BOTTOM | Gravity.LEFT));
buttons[0] = repeatButton = new ImageView(context);
repeatButton.setScaleType(ImageView.ScaleType.CENTER);
bottomView.addView(repeatButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP));
repeatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaController.getInstance().toggleRepeatMode();
updateRepeatButton();
}
});
buttons[1] = prevButton = new ImageView(context);
prevButton.setScaleType(ImageView.ScaleType.CENTER);
prevButton.setImageResource(R.drawable.player_prev_states);
bottomView.addView(prevButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP));
prevButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaController.getInstance().playPreviousMessage();
}
});
buttons[2] = playButton = new ImageView(context);
playButton.setScaleType(ImageView.ScaleType.CENTER);
playButton.setImageResource(R.drawable.player_play_states);
bottomView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP));
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (MediaController.getInstance().isDownloadingCurrentMessage()) {
return;
}
if (MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
} else {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
}
}
});
buttons[3] = nextButton = new ImageView(context);
nextButton.setScaleType(ImageView.ScaleType.CENTER);
nextButton.setImageResource(R.drawable.player_next_states);
bottomView.addView(nextButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP));
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaController.getInstance().playNextMessage();
}
});
buttons[4] = shuffleButton = new ImageView(context);
shuffleButton.setScaleType(ImageView.ScaleType.CENTER);
bottomView.addView(shuffleButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP));
shuffleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaController.getInstance().toggleShuffleMusic();
updateShuffleButton();
}
});
updateTitle(false);
updateRepeatButton();
updateShuffleButton();
return frameLayout;
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.audioDidStarted || id == NotificationCenter.audioPlayStateChanged || id == NotificationCenter.audioDidReset) {
updateTitle(id == NotificationCenter.audioDidReset && (Boolean) args[1]);
} else if (id == NotificationCenter.audioProgressDidChanged) {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject.isMusic()) {
updateProgress(messageObject);
}
}
}
@Override
public void onFailedDownload(String fileName) {
}
@Override
public void onSuccessDownload(String fileName) {
}
@Override
public void onProgressDownload(String fileName, float progress) {
progressView.setProgress(progress, true);
}
@Override
public void onProgressUpload(String fileName, float progress, boolean isEncrypted) {
}
@Override
public int getObserverTag() {
return TAG;
}
private void onSeekBarDrag(float progress) {
MediaController.getInstance().seekToProgress(MediaController.getInstance().getPlayingMessageObject(), progress);
}
private void updateShuffleButton() {
if (MediaController.getInstance().isShuffleMusic()) {
shuffleButton.setImageResource(R.drawable.pl_shuffle_active);
} else {
shuffleButton.setImageResource(R.drawable.pl_shuffle);
}
}
private void updateRepeatButton() {
int mode = MediaController.getInstance().getRepeatMode();
if (mode == 0) {
repeatButton.setImageResource(R.drawable.pl_repeat);
} else if (mode == 1) {
repeatButton.setImageResource(R.drawable.pl_repeat_active);
} else if (mode == 2) {
repeatButton.setImageResource(R.drawable.pl_repeat1_active);
}
}
private void updateProgress(MessageObject messageObject) {
if (seekBarView != null) {
if (!seekBarView.isDragging()) {
seekBarView.setProgress(messageObject.audioProgress);
}
String timeString = String.format("%d:%02d", messageObject.audioProgressSec / 60, messageObject.audioProgressSec % 60);
if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) {
lastTimeString = timeString;
timeTextView.setText(timeString);
}
}
}
private void checkIfMusicDownloaded(MessageObject messageObject) {
File cacheFile = null;
if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) {
cacheFile = new File(messageObject.messageOwner.attachPath);
if(!cacheFile.exists()) {
cacheFile = null;
}
}
if (cacheFile == null) {
cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner);
}
if (!cacheFile.exists()) {
String fileName = messageObject.getFileName();
MediaController.getInstance().addLoadingFileObserver(fileName, this);
Float progress = ImageLoader.getInstance().getFileProgress(fileName);
progressView.setProgress(progress != null ? progress : 0, false);
progressView.setVisibility(View.VISIBLE);
seekBarView.setVisibility(View.INVISIBLE);
playButton.setEnabled(false);
} else {
MediaController.getInstance().removeLoadingFileObserver(this);
progressView.setVisibility(View.INVISIBLE);
seekBarView.setVisibility(View.VISIBLE);
playButton.setEnabled(true);
}
}
private void updateTitle(boolean shutdown) {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject == null && shutdown || messageObject != null && !messageObject.isMusic()) {
if (!parentLayout.fragmentsStack.isEmpty() && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) == this) {
finishFragment();
} else {
removeSelfFromStack();
}
} else {
if (messageObject == null) {
return;
}
checkIfMusicDownloaded(messageObject);
updateProgress(messageObject);
if (MediaController.getInstance().isAudioPaused()) {
playButton.setImageResource(R.drawable.player_play_states);
} else {
playButton.setImageResource(R.drawable.player_pause_states);
}
if (actionBar != null) {
actionBar.setTitle(messageObject.getMusicTitle());
actionBar.getTitleTextView().setTextColor(0xff212121);
actionBar.setSubtitle(messageObject.getMusicAuthor());
actionBar.getSubTitleTextView().setTextColor(0xff8a8a8a);
}
AudioInfo audioInfo = MediaController.getInstance().getAudioInfo();
if (audioInfo != null && audioInfo.getCover() != null) {
placeholder.setImageBitmap(audioInfo.getCover());
placeholder.setPadding(0, 0, 0, 0);
placeholder.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
placeholder.setImageResource(R.drawable.nocover);
placeholder.setPadding(0, 0, 0, AndroidUtilities.dp(30));
placeholder.setScaleType(ImageView.ScaleType.CENTER);
}
if (durationTextView != null) {
int duration = 0;
for (TLRPC.DocumentAttribute attribute : messageObject.messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
duration = attribute.duration;
break;
}
}
durationTextView.setText(duration != 0 ? String.format("%d:%02d", duration / 60, duration % 60) : "-:--");
}
}
}
}

View file

@ -0,0 +1,344 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.ui;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
import android.provider.MediaStore;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ListView;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.LocaleController;
import org.telegram.android.MediaController;
import org.telegram.android.MessageObject;
import org.telegram.android.NotificationCenter;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.Adapters.BaseFragmentAdapter;
import org.telegram.ui.Cells.AudioCell;
import org.telegram.ui.Components.EmptyTextProgressView;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.PickerBottomLayout;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
public class AudioSelectActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate {
private ListAdapter listViewAdapter;
private EmptyTextProgressView progressView;
private PickerBottomLayout bottomLayout;
private boolean loadingAudio;
private ArrayList<MediaController.AudioEntry> audioEntries = new ArrayList<>();
private HashMap<Long, MediaController.AudioEntry> selectedAudios = new HashMap<>();
private AudioSelectActivityDelegate delegate;
private MessageObject playingAudio;
public interface AudioSelectActivityDelegate {
void didSelectAudio(ArrayList<MessageObject> audios);
}
@Override
public boolean onFragmentCreate() {
super.onFragmentCreate();
NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset);
loadAudio();
return true;
}
@Override
public void onFragmentDestroy() {
super.onFragmentDestroy();
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset);
if (playingAudio != null && MediaController.getInstance().isPlayingAudio(playingAudio)) {
MediaController.getInstance().clenupPlayer(true, true);
}
}
@Override
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("AttachAudio", R.string.AttachAudio));
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
if (id == -1) {
finishFragment();
}
}
});
fragmentView = new FrameLayout(context);
FrameLayout frameLayout = (FrameLayout) fragmentView;
progressView = new EmptyTextProgressView(context);
progressView.setText(LocaleController.getString("NoAudio", R.string.NoAudio));
frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
ListView listView = new ListView(context);
listView.setEmptyView(progressView);
listView.setVerticalScrollBarEnabled(false);
listView.setDivider(null);
listView.setDividerHeight(0);
listView.setAdapter(listViewAdapter = new ListAdapter(context));
if (Build.VERSION.SDK_INT >= 11) {
listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT);
}
frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 48));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
AudioCell audioCell = (AudioCell) view;
MediaController.AudioEntry audioEntry = audioCell.getAudioEntry();
if (selectedAudios.containsKey(audioEntry.id)) {
selectedAudios.remove(audioEntry.id);
audioCell.setChecked(false);
} else {
selectedAudios.put(audioEntry.id, audioEntry);
audioCell.setChecked(true);
}
updateBottomLayoutCount();
}
});
bottomLayout = new PickerBottomLayout(context, false);
frameLayout.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM));
bottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finishFragment();
}
});
bottomLayout.doneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (delegate != null) {
ArrayList<MessageObject> audios = new ArrayList<>();
for (HashMap.Entry<Long, MediaController.AudioEntry> entry : selectedAudios.entrySet()) {
audios.add(entry.getValue().messageObject);
}
delegate.didSelectAudio(audios);
}
finishFragment();
}
});
View shadow = new View(context);
shadow.setBackgroundResource(R.drawable.header_shadow_reverse);
frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48));
if (loadingAudio) {
progressView.showProgress();
} else {
progressView.showTextView();
}
updateBottomLayoutCount();
return fragmentView;
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.closeChats) {
removeSelfFromStack();
} else if (id == NotificationCenter.audioDidReset) {
if (listViewAdapter != null) {
listViewAdapter.notifyDataSetChanged();
}
}
}
private void updateBottomLayoutCount() {
bottomLayout.updateSelectedCount(selectedAudios.size(), true);
}
public void setDelegate(AudioSelectActivityDelegate audioSelectActivityDelegate) {
delegate = audioSelectActivityDelegate;
}
private void loadAudio() {
loadingAudio = true;
if (progressView != null) {
progressView.showProgress();
}
Utilities.globalQueue.postRunnable(new Runnable() {
@Override
public void run() {
String[] projection = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.ALBUM
};
final ArrayList<MediaController.AudioEntry> newAudioEntries = new ArrayList<>();
Cursor cursor = null;
try {
cursor = ApplicationLoader.applicationContext.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, MediaStore.Audio.Media.IS_MUSIC + " != 0", null, null);
int id = -2000000000;
while (cursor.moveToNext()) {
MediaController.AudioEntry audioEntry = new MediaController.AudioEntry();
audioEntry.id = cursor.getInt(0);
audioEntry.author = cursor.getString(1);
audioEntry.title = cursor.getString(2);
audioEntry.path = cursor.getString(3);
audioEntry.duration = (int) (cursor.getLong(4) / 1000);
audioEntry.genre = cursor.getString(5);
File file = new File(audioEntry.path);
TLRPC.TL_message message = new TLRPC.TL_message();
message.flags = TLRPC.MESSAGE_FLAG_OUT;
message.id = id;
message.to_id = new TLRPC.TL_peerUser();
message.to_id.user_id = message.from_id = UserConfig.getClientUserId();
message.date = (int) (System.currentTimeMillis() / 1000);
message.message = "-1";
message.attachPath = audioEntry.path;
message.media = new TLRPC.TL_messageMediaDocument();
message.media.document = new TLRPC.TL_document();
String ext = FileLoader.getFileExtension(file);
message.media.document.id = 0;
message.media.document.access_hash = 0;
message.media.document.date = message.date;
message.media.document.mime_type = "audio/" + (ext.length() > 0 ? ext : "mp3");
message.media.document.size = (int) file.length();
message.media.document.thumb = new TLRPC.TL_photoSizeEmpty();
message.media.document.thumb.type = "s";
message.media.document.dc_id = 0;
TLRPC.TL_documentAttributeAudio attributeAudio = new TLRPC.TL_documentAttributeAudio();
attributeAudio.duration = audioEntry.duration;
attributeAudio.title = audioEntry.title;
attributeAudio.performer = audioEntry.author;
message.media.document.attributes.add(attributeAudio);
TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename();
fileName.file_name = file.getName();
message.media.document.attributes.add(fileName);
audioEntry.messageObject = new MessageObject(message, null, false);
newAudioEntries.add(audioEntry);
id--;
}
} catch (Exception e) {
FileLog.e("tmessages", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
audioEntries = newAudioEntries;
progressView.showTextView();
listViewAdapter.notifyDataSetChanged();
}
});
}
});
}
private class ListAdapter extends BaseFragmentAdapter {
private Context mContext;
public ListAdapter(Context context) {
mContext = context;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int i) {
return true;
}
@Override
public int getCount() {
return audioEntries.size();
}
@Override
public Object getItem(int i) {
return audioEntries.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
int type = getItemViewType(i);
if (view == null) {
view = new AudioCell(mContext);
((AudioCell) view).setDelegate(new AudioCell.AudioCellDelegate() {
@Override
public void startedPlayingAudio(MessageObject messageObject) {
playingAudio = messageObject;
}
});
}
MediaController.AudioEntry audioEntry = audioEntries.get(i);
((AudioCell) view).setAudio(audioEntries.get(i), i != audioEntries.size() - 1, selectedAudios.containsKey(audioEntry.id));
return view;
}
@Override
public int getItemViewType(int i) {
return 0;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean isEmpty() {
return audioEntries.isEmpty();
}
}
}

View file

@ -14,7 +14,6 @@ import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -65,7 +64,7 @@ public class BlockedUsersActivity extends BaseFragment implements NotificationCe
}
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("BlockedUsers", R.string.BlockedUsers));

View file

@ -0,0 +1,169 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.ui.Cells;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.LocaleController;
import org.telegram.android.MediaController;
import org.telegram.android.MessageObject;
import org.telegram.messenger.R;
import org.telegram.ui.Components.CheckBox;
import org.telegram.ui.Components.LayoutHelper;
import java.util.ArrayList;
public class AudioCell extends FrameLayout {
private ImageView playButton;
private TextView titleTextView;
private TextView authorTextView;
private TextView genreTextView;
private TextView timeTextView;
private CheckBox checkBox;
private MediaController.AudioEntry audioEntry;
private boolean needDivider;
private static Paint paint;
private AudioCellDelegate delegate;
public interface AudioCellDelegate {
void startedPlayingAudio(MessageObject messageObject);
}
public AudioCell(Context context) {
super(context);
if (paint == null) {
paint = new Paint();
paint.setColor(0xffd9d9d9);
paint.setStrokeWidth(1);
}
playButton = new ImageView(context);
playButton.setScaleType(ImageView.ScaleType.CENTER);
addView(playButton, LayoutHelper.createFrame(46, 46, ((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP), LocaleController.isRTL ? 0 : 13, 13, LocaleController.isRTL ? 13 : 0, 0));
playButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (audioEntry != null) {
if (MediaController.getInstance().isPlayingAudio(audioEntry.messageObject) && !MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().pauseAudio(audioEntry.messageObject);
playButton.setImageResource(R.drawable.audiosend_play);
} else {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(audioEntry.messageObject);
if (MediaController.getInstance().setPlaylist(arrayList, audioEntry.messageObject)) {
playButton.setImageResource(R.drawable.audiosend_pause);
if (delegate != null) {
delegate.startedPlayingAudio(audioEntry.messageObject);
}
}
}
}
}
});
titleTextView = new TextView(context);
titleTextView.setTextColor(0xff212121);
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleTextView.setLines(1);
titleTextView.setMaxLines(1);
titleTextView.setSingleLine(true);
titleTextView.setEllipsize(TextUtils.TruncateAt.END);
titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 7, LocaleController.isRTL ? 72 : 50, 0));
genreTextView = new TextView(context);
genreTextView.setTextColor(0xff8a8a8a);
genreTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
genreTextView.setLines(1);
genreTextView.setMaxLines(1);
genreTextView.setSingleLine(true);
genreTextView.setEllipsize(TextUtils.TruncateAt.END);
genreTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
addView(genreTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 28, LocaleController.isRTL ? 72 : 50, 0));
authorTextView = new TextView(context);
authorTextView.setTextColor(0xff8a8a8a);
authorTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
authorTextView.setLines(1);
authorTextView.setMaxLines(1);
authorTextView.setSingleLine(true);
authorTextView.setEllipsize(TextUtils.TruncateAt.END);
authorTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
addView(authorTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 44, LocaleController.isRTL ? 72 : 50, 0));
timeTextView = new TextView(context);
timeTextView.setTextColor(0xff999999);
timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
timeTextView.setLines(1);
timeTextView.setMaxLines(1);
timeTextView.setSingleLine(true);
timeTextView.setEllipsize(TextUtils.TruncateAt.END);
timeTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP);
addView(timeTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 18 : 0, 11, LocaleController.isRTL ? 0 : 18, 0));
checkBox = new CheckBox(context, R.drawable.round_check2);
checkBox.setVisibility(VISIBLE);
checkBox.setColor(0xff29b6f7);
addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 18 : 0, 39, LocaleController.isRTL ? 0 : 18, 0));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
}
public void setAudio(MediaController.AudioEntry entry, boolean divider, boolean checked) {
audioEntry = entry;
titleTextView.setText(audioEntry.title);
genreTextView.setText(audioEntry.genre);
authorTextView.setText(audioEntry.author);
timeTextView.setText(String.format("%d:%02d", audioEntry.duration / 60, audioEntry.duration % 60));
playButton.setImageResource(MediaController.getInstance().isPlayingAudio(audioEntry.messageObject) && !MediaController.getInstance().isAudioPaused() ? R.drawable.audiosend_pause : R.drawable.audiosend_play);
needDivider = divider;
setWillNotDraw(!divider);
checkBox.setChecked(checked, false);
}
public void setChecked(boolean value) {
checkBox.setChecked(value, true);
}
public void setDelegate(AudioCellDelegate audioCellDelegate) {
delegate = audioCellDelegate;
}
public MediaController.AudioEntry getAudioEntry() {
return audioEntry;
}
@Override
protected void onDraw(Canvas canvas) {
if (needDivider) {
canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth(), getHeight() - 1, paint);
}
}
}

View file

@ -42,6 +42,7 @@ public class BotHelpCell extends View {
private int height;
private int textX;
private int textY;
private int textXOffset;
private ClickableSpan pressedLink;
private LinkPath urlPath = new LinkPath();
@ -101,7 +102,9 @@ public class BotHelpCell extends View {
width = 0;
height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18);
int count = textLayout.getLineCount();
textXOffset = Integer.MAX_VALUE;
for (int a = 0; a < count; a++) {
textXOffset = (int) Math.ceil(Math.min(textXOffset, textLayout.getLineLeft(a)));
width = (int) Math.ceil(Math.max(width, textLayout.getLineWidth(a) - textLayout.getLineLeft(a)));
}
width += AndroidUtilities.dp(4 + 18);
@ -113,7 +116,6 @@ public class BotHelpCell extends View {
float y = event.getY();
boolean result = false;
int side = AndroidUtilities.dp(48);
if (textLayout != null) {
if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
@ -186,7 +188,7 @@ public class BotHelpCell extends View {
ResourceLoader.backgroundMediaDrawableIn.setBounds(x, y, width + x, height + y);
ResourceLoader.backgroundMediaDrawableIn.draw(canvas);
canvas.save();
canvas.translate(textX = AndroidUtilities.dp(2 + 9) + x, textY = AndroidUtilities.dp(2 + 9) + y);
canvas.translate(textX = AndroidUtilities.dp(2 + 9) + x - textXOffset, textY = AndroidUtilities.dp(2 + 9) + y);
if (pressedLink != null) {
canvas.drawPath(urlPath, urlPaint);
}

View file

@ -174,8 +174,8 @@ public class ChatActionCell extends BaseCell {
final int line = textLayout.getLineForVertical((int)y);
final int off = textLayout.getOffsetForHorizontal(line, x);
final float left = textLayout.getLineLeft(line);
if (left <= x && left + textLayout.getLineWidth(line) >= x) {
Spannable buffer = (Spannable)currentMessageObject.messageText;
if (left <= x && left + textLayout.getLineWidth(line) >= x && currentMessageObject.messageText instanceof Spannable) {
Spannable buffer = (Spannable) currentMessageObject.messageText;
URLSpan[] link = buffer.getSpans(off, off, URLSpan.class);
if (link.length != 0) {
@ -226,7 +226,6 @@ public class ChatActionCell extends BaseCell {
int linesCount = textLayout.getLineCount();
for (int a = 0; a < linesCount; a++) {
float lineWidth;
float lineLeft = 0;
try {
lineWidth = textLayout.getLineWidth(a);
textHeight = (int)Math.max(textHeight, Math.ceil(textLayout.getLineBottom(a)));

View file

@ -21,10 +21,11 @@ import android.view.SoundEffectConstants;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.ImageLoader;
import org.telegram.android.MessagesController;
import org.telegram.android.SendMessagesHelper;
import org.telegram.messenger.FileLoader;
import org.telegram.android.MediaController;
import org.telegram.android.MessageObject;
import org.telegram.ui.Components.ProgressView;
import org.telegram.ui.Components.RadialProgress;
import org.telegram.ui.Components.ResourceLoader;
import org.telegram.ui.Components.SeekBar;
@ -36,10 +37,10 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
private static Paint circlePaint;
private SeekBar seekBar;
private ProgressView progressView;
private int seekBarX;
private int seekBarY;
private RadialProgress radialProgress;
private int buttonState = 0;
private int buttonX;
private int buttonY;
@ -58,7 +59,7 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
seekBar = new SeekBar(context);
seekBar.delegate = this;
progressView = new ProgressView();
radialProgress = new RadialProgress(this);
drawForwardedName = true;
if (timePaint == null) {
@ -78,7 +79,7 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateButtonState();
updateButtonState(false);
}
@Override
@ -131,21 +132,25 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
}
if (result) {
buttonState = 1;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
}
} else if (buttonState == 1) {
boolean result = MediaController.getInstance().pauseAudio(currentMessageObject);
if (result) {
buttonState = 0;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
}
} else if (buttonState == 2) {
FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true);
buttonState = 3;
radialProgress.setBackground(getDrawableForCurrentState(), true, false);
invalidate();
} else if (buttonState == 3) {
FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.audio);
buttonState = 2;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
} else if (buttonState == 4) {
if (currentMessageObject.isOut() && currentMessageObject.isSending()) {
@ -173,6 +178,7 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
}
String timeString = String.format("%02d:%02d", duration / 60, duration % 60);
if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) {
lastTimeString = timeString;
timeWidth = (int)Math.ceil(timePaint.measureText(timeString));
timeLayout = new StaticLayout(timeString, timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
@ -183,16 +189,23 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
if (buttonState == 2) {
FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true);
buttonState = 3;
invalidate();
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
}
}
public void updateButtonState() {
public void updateButtonState(boolean animated) {
if (currentMessageObject == null) {
return;
}
if (currentMessageObject.isOut() && currentMessageObject.isSending()) {
MediaController.getInstance().addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, this);
buttonState = 4;
radialProgress.setBackground(getDrawableForCurrentState(), true, animated);
Float progress = ImageLoader.getInstance().getFileProgress(currentMessageObject.messageOwner.attachPath);
if (progress == null && SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId())) {
progress = 1.0f;
}
radialProgress.setProgress(progress != null ? progress : 0, false);
} else {
File cacheFile = null;
if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) {
@ -212,21 +225,24 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
} else {
buttonState = 1;
}
progressView.setProgress(0);
radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else {
String fileName = currentMessageObject.getFileName();
MediaController.getInstance().addLoadingFileObserver(fileName, this);
if (!FileLoader.getInstance().isLoadingFile(fileName)) {
buttonState = 2;
progressView.setProgress(0);
radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else {
buttonState = 3;
Float progress = ImageLoader.getInstance().getFileProgress(fileName);
if (progress != null) {
progressView.setProgress(progress);
radialProgress.setProgress(progress, animated);
} else {
progressView.setProgress(0);
radialProgress.setProgress(0, animated);
}
radialProgress.setBackground(getDrawableForCurrentState(), true, animated);
}
}
}
@ -235,26 +251,25 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
@Override
public void onFailedDownload(String fileName) {
updateButtonState();
updateButtonState(true);
}
@Override
public void onSuccessDownload(String fileName) {
updateButtonState();
updateButtonState(true);
}
@Override
public void onProgressDownload(String fileName, float progress) {
progressView.setProgress(progress);
radialProgress.setProgress(progress, true);
if (buttonState != 3) {
updateButtonState();
updateButtonState(false);
}
invalidate();
}
@Override
public void onProgressUpload(String fileName, float progress, boolean isEncrypted) {
radialProgress.setProgress(progress, true);
}
@Override
@ -299,17 +314,17 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
seekBar.width = backgroundWidth - AndroidUtilities.dp(70);
seekBar.height = AndroidUtilities.dp(30);
progressView.width = backgroundWidth - AndroidUtilities.dp(94);
progressView.height = AndroidUtilities.dp(30);
seekBarY = AndroidUtilities.dp(11) + namesOffset;
buttonY = AndroidUtilities.dp(13) + namesOffset;
radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(40), buttonY + AndroidUtilities.dp(40));
updateProgress();
}
@Override
public void setMessageObject(MessageObject messageObject) {
if (currentMessageObject != messageObject || isUserDataChanged()) {
boolean dataChanged = currentMessageObject == messageObject && isUserDataChanged();
if (currentMessageObject != messageObject || dataChanged) {
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat ? 102 : 50), AndroidUtilities.dp(300));
} else {
@ -318,15 +333,20 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
if (messageObject.isOut()) {
seekBar.type = 0;
progressView.setProgressColors(0xffb4e396, 0xff6ac453);
radialProgress.setProgressColor(0xff87bf78);
} else {
seekBar.type = 1;
progressView.setProgressColors(0xffd9e2eb, 0xff86c5f8);
radialProgress.setProgressColor(0xffa2b5c7);
}
super.setMessageObject(messageObject);
}
updateButtonState();
updateButtonState(dataChanged);
}
private Drawable getDrawableForCurrentState() {
return ResourceLoader.audioStatesDrawable[currentMessageObject.isOut() ? buttonState : buttonState + 5][0];
//buttonPressed ? 1 :
}
@Override
@ -338,27 +358,18 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
}
canvas.save();
if (buttonState == 0 || buttonState == 1) {
canvas.translate(seekBarX, seekBarY);
seekBar.draw(canvas);
} else {
canvas.translate(seekBarX + AndroidUtilities.dp(12), seekBarY);
progressView.draw(canvas);
}
canvas.translate(seekBarX, seekBarY);
seekBar.draw(canvas);
canvas.restore();
int state = buttonState;
if (currentMessageObject.isOut()) {
timePaint.setColor(0xff70b15c);
circlePaint.setColor(0xff87bf78);
} else {
state += 5;
timePaint.setColor(0xffa1aab3);
circlePaint.setColor(0xff4195e5);
}
Drawable buttonDrawable = ResourceLoader.audioStatesDrawable[state][buttonPressed ? 1 : 0];
setDrawableBounds(buttonDrawable, buttonX, buttonY);
buttonDrawable.draw(canvas);
radialProgress.onDraw(canvas);
canvas.save();
canvas.translate(timeX, AndroidUtilities.dp(42) + namesOffset);

View file

@ -445,7 +445,7 @@ public class ChatBaseCell extends BaseCell {
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
stringFinalText = Emoji.replaceEmoji(mess, replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14));
stringFinalText = Emoji.replaceEmoji(mess, replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false);
stringFinalText = TextUtils.ellipsize(stringFinalText, replyTextPaint, maxWidth - AndroidUtilities.dp(8), TextUtils.TruncateAt.END);
}
}

View file

@ -108,7 +108,6 @@ public class ChatContactCell extends ChatBaseCell {
float y = event.getY();
boolean result = false;
int side = AndroidUtilities.dp(36);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (x >= avatarImage.getImageX() && x <= avatarImage.getImageX() + namesWidth + AndroidUtilities.dp(42) && y >= avatarImage.getImageY() && y <= avatarImage.getImageY() + avatarImage.getImageHeight()) {
avatarPressed = true;

View file

@ -379,7 +379,6 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD
private Drawable getDrawableForCurrentState() {
if (buttonState >= 0 && buttonState < 4) {
Drawable currentButtonDrawable = null;
if (currentMessageObject.type == 9 && gifDrawable == null) {
if (buttonState == 1 && !currentMessageObject.isSending()) {
return ResourceLoader.buttonStatesDrawablesDoc[2][currentMessageObject.isOut() ? 1 : 0];

View file

@ -193,7 +193,7 @@ public class ChatMessageCell extends ChatBaseCell {
pressedLink.onClick(this);
} else {
TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage;
if (Build.VERSION.SDK_INT >= 16 && webPage.embed_url != null && webPage.embed_url.length() != 0) {
if (Build.VERSION.SDK_INT >= 19 && webPage.embed_url != null && webPage.embed_url.length() != 0) {
delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.url, webPage.embed_width, webPage.embed_height);
} else {
Uri uri = Uri.parse(webPage.url);
@ -536,7 +536,7 @@ public class ChatMessageCell extends ChatBaseCell {
if (webPage.photo != null) {
boolean smallImage = webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article"));
if (smallImage && descriptionLayout != null && descriptionLayout.getLineCount() == 1) {
if (smallImage && (descriptionLayout == null || descriptionLayout != null && descriptionLayout.getLineCount() == 1)) {
smallImage = false;
isSmallImage = false;
}

View file

@ -0,0 +1,428 @@
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.ui.Cells;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.ImageLoader;
import org.telegram.android.MediaController;
import org.telegram.android.MessageObject;
import org.telegram.android.SendMessagesHelper;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.TLRPC;
import org.telegram.ui.Components.RadialProgress;
import org.telegram.ui.Components.ResourceLoader;
import org.telegram.ui.Components.SeekBar;
import java.io.File;
public class ChatMusicCell extends ChatBaseCell implements SeekBar.SeekBarDelegate, MediaController.FileDownloadProgressListener {
public interface ChatMusicCellDelegate {
boolean needPlayMusic(MessageObject messageObject);
}
private static TextPaint timePaint;
private static TextPaint titlePaint;
private static TextPaint authorPaint;
private SeekBar seekBar;
private int seekBarX;
private int seekBarY;
private RadialProgress radialProgress;
private int buttonState = 0;
private int buttonX;
private int buttonY;
private boolean buttonPressed = false;
private StaticLayout timeLayout;
private int timeX;
private String lastTimeString = null;
private StaticLayout titleLayout;
private int titleX;
private StaticLayout authorLayout;
private int authorX;
private int TAG;
private ChatMusicCellDelegate musicDelegate;
public ChatMusicCell(Context context) {
super(context);
TAG = MediaController.getInstance().generateObserverTag();
seekBar = new SeekBar(context);
seekBar.delegate = this;
radialProgress = new RadialProgress(this);
drawForwardedName = false;
if (timePaint == null) {
timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
timePaint.setTextSize(AndroidUtilities.dp(13));
titlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
titlePaint.setTextSize(AndroidUtilities.dp(16));
titlePaint.setColor(0xff212121);
titlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
authorPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
authorPaint.setTextSize(AndroidUtilities.dp(15));
authorPaint.setColor(0xff212121);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
MediaController.getInstance().removeLoadingFileObserver(this);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateButtonState(false);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
boolean result = seekBar.onTouch(event.getAction(), event.getX() - seekBarX, event.getY() - seekBarY);
if (result) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
getParent().requestDisallowInterceptTouchEvent(true);
}
invalidate();
} else {
int side = AndroidUtilities.dp(36);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side) {
buttonPressed = true;
invalidate();
result = true;
}
} else if (buttonPressed) {
if (event.getAction() == MotionEvent.ACTION_UP) {
buttonPressed = false;
playSoundEffect(SoundEffectConstants.CLICK);
didPressedButton();
invalidate();
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
buttonPressed = false;
invalidate();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!(x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side)) {
buttonPressed = false;
invalidate();
}
}
}
if (!result) {
result = super.onTouchEvent(event);
}
}
return result;
}
private void didPressedButton() {
if (buttonState == 0) {
if (musicDelegate != null) {
if (musicDelegate.needPlayMusic(currentMessageObject)) {
buttonState = 1;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
}
}
} else if (buttonState == 1) {
boolean result = MediaController.getInstance().pauseAudio(currentMessageObject);
if (result) {
buttonState = 0;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
}
} else if (buttonState == 2) {
FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, true, false);
buttonState = 3;
radialProgress.setBackground(getDrawableForCurrentState(), true, false);
invalidate();
} else if (buttonState == 3) {
FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.document);
buttonState = 2;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate();
} else if (buttonState == 4) {
if (currentMessageObject.isOut() && currentMessageObject.isSending()) {
if (delegate != null) {
delegate.didPressedCancelSendButton(this);
}
}
}
}
public void setMusicDelegate(ChatMusicCellDelegate delegate) {
musicDelegate = delegate;
}
public void updateProgress() {
if (currentMessageObject == null) {
return;
}
if (!seekBar.isDragging()) {
seekBar.setProgress(currentMessageObject.audioProgress);
}
int duration = 0;
int currentProgress = 0;
for (TLRPC.DocumentAttribute attribute : currentMessageObject.messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
duration = attribute.duration;
break;
}
}
if (MediaController.getInstance().isPlayingAudio(currentMessageObject)) {
currentProgress = currentMessageObject.audioProgressSec;
}
String timeString = String.format("%d:%02d / %d:%02d", currentProgress / 60, currentProgress % 60, duration / 60, duration % 60);
if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) {
lastTimeString = timeString;
int timeWidth = (int) Math.ceil(timePaint.measureText(timeString));
timeLayout = new StaticLayout(timeString, timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
invalidate();
}
public void downloadAudioIfNeed() {
//if (buttonState == 2) {
//FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, true, false);
// buttonState = 3;
// invalidate();
//}
}
public void updateButtonState(boolean animated) {
if (currentMessageObject == null) {
return;
}
if (currentMessageObject.isOut() && currentMessageObject.isSending()) {
MediaController.getInstance().addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, this);
buttonState = 4;
radialProgress.setBackground(getDrawableForCurrentState(), true, animated);
Float progress = ImageLoader.getInstance().getFileProgress(currentMessageObject.messageOwner.attachPath);
if (progress == null && SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId())) {
progress = 1.0f;
}
radialProgress.setProgress(progress != null ? progress : 0, false);
} else {
File cacheFile = null;
if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) {
cacheFile = new File(currentMessageObject.messageOwner.attachPath);
if(!cacheFile.exists()) {
cacheFile = null;
}
}
if (cacheFile == null) {
cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner);
}
if (cacheFile.exists()) {
MediaController.getInstance().removeLoadingFileObserver(this);
boolean playing = MediaController.getInstance().isPlayingAudio(currentMessageObject);
if (!playing || playing && MediaController.getInstance().isAudioPaused()) {
buttonState = 0;
} else {
buttonState = 1;
}
radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else {
String fileName = currentMessageObject.getFileName();
MediaController.getInstance().addLoadingFileObserver(fileName, this);
if (!FileLoader.getInstance().isLoadingFile(fileName)) {
buttonState = 2;
radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else {
buttonState = 3;
Float progress = ImageLoader.getInstance().getFileProgress(fileName);
if (progress != null) {
radialProgress.setProgress(progress, animated);
} else {
radialProgress.setProgress(0, animated);
}
radialProgress.setBackground(getDrawableForCurrentState(), true, animated);
}
}
}
updateProgress();
}
@Override
public void onFailedDownload(String fileName) {
updateButtonState(true);
}
@Override
public void onSuccessDownload(String fileName) {
updateButtonState(true);
}
@Override
public void onProgressDownload(String fileName, float progress) {
radialProgress.setProgress(progress, true);
if (buttonState != 3) {
updateButtonState(false);
}
}
@Override
public void onProgressUpload(String fileName, float progress, boolean isEncrypted) {
radialProgress.setProgress(progress, true);
}
@Override
public int getObserverTag() {
return TAG;
}
@Override
public void onSeekBarDrag(float progress) {
if (currentMessageObject == null) {
return;
}
currentMessageObject.audioProgress = progress;
MediaController.getInstance().seekToProgress(currentMessageObject, progress);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, AndroidUtilities.dp(78) + namesOffset);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (currentMessageObject.isOut()) {
seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(52);
buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(13);
timeX = layoutWidth - backgroundWidth + AndroidUtilities.dp(63);
} else {
if (isChat) {
seekBarX = AndroidUtilities.dp(113);
buttonX = AndroidUtilities.dp(74);
timeX = AndroidUtilities.dp(124);
} else {
seekBarX = AndroidUtilities.dp(61);
buttonX = AndroidUtilities.dp(22);
timeX = AndroidUtilities.dp(72);
}
}
seekBar.width = backgroundWidth - AndroidUtilities.dp(67);
seekBar.height = AndroidUtilities.dp(30);
seekBarY = AndroidUtilities.dp(26) + namesOffset;
buttonY = AndroidUtilities.dp(13) + namesOffset;
radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(40), buttonY + AndroidUtilities.dp(40));
updateProgress();
}
@Override
public void setMessageObject(MessageObject messageObject) {
boolean dataChanged = currentMessageObject == messageObject && isUserDataChanged();
if (currentMessageObject != messageObject || dataChanged) {
if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat ? 102 : 50), AndroidUtilities.dp(300));
} else {
backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat ? 102 : 50), AndroidUtilities.dp(300));
}
if (messageObject.isOut()) {
seekBar.type = 0;
radialProgress.setProgressColor(0xff87bf78);
} else {
seekBar.type = 1;
radialProgress.setProgressColor(0xffa2b5c7);
}
int maxWidth = backgroundWidth - AndroidUtilities.dp(86);
CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace("\n", " "), titlePaint, maxWidth, TextUtils.TruncateAt.END);
titleLayout = new StaticLayout(stringFinal, titlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (titleLayout.getLineCount() > 0) {
titleX = (int) Math.ceil(titleLayout.getLineLeft(0));
}
stringFinal = TextUtils.ellipsize(messageObject.getMusicAuthor().replace("\n", " "), authorPaint, maxWidth, TextUtils.TruncateAt.END);
authorLayout = new StaticLayout(stringFinal, authorPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (authorLayout.getLineCount() > 0) {
authorX = (int) Math.ceil(authorLayout.getLineLeft(0));
}
super.setMessageObject(messageObject);
}
updateButtonState(dataChanged);
}
private Drawable getDrawableForCurrentState() {
return ResourceLoader.audioStatesDrawable[currentMessageObject.isOut() ? buttonState : buttonState + 5][0];
//buttonPressed ? 1 :
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (currentMessageObject == null) {
return;
}
if (currentMessageObject.isOut()) {
timePaint.setColor(0xff70b15c);
} else {
timePaint.setColor(0xffa1aab3);
}
radialProgress.onDraw(canvas);
canvas.save();
canvas.translate(timeX + titleX, AndroidUtilities.dp(12) + namesOffset);
titleLayout.draw(canvas);
canvas.restore();
canvas.save();
if (MediaController.getInstance().isPlayingAudio(currentMessageObject)) {
canvas.translate(seekBarX, seekBarY);
seekBar.draw(canvas);
} else {
canvas.translate(timeX + authorX, AndroidUtilities.dp(32) + namesOffset);
authorLayout.draw(canvas);
}
canvas.restore();
canvas.save();
canvas.translate(timeX, AndroidUtilities.dp(52) + namesOffset);
timeLayout.draw(canvas);
canvas.restore();
}
}

View file

@ -356,15 +356,7 @@ public class DialogCell extends BaseCell {
if (message.isOut()) {
name = LocaleController.getString("FromYou", R.string.FromYou);
} else {
if (UserObject.isDeleted(fromUser)) {
name = "Deleted";
} else {
if (fromUser.first_name != null && fromUser.first_name.length() > 0) {
name = fromUser.first_name;
} else {
name = fromUser.last_name;
}
}
name = UserObject.getFirstName(fromUser);
}
checkMessage = false;
if (message.caption != null) {
@ -373,11 +365,11 @@ public class DialogCell extends BaseCell {
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff808080>%s</c>", name, mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20));
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff808080>%s</c>", name, mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
} else {
if (message.messageOwner.media != null && !message.isMediaEmpty()) {
currentMessagePaint = messagePrintingPaint;
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff4d83b3>%s</c>", name, message.messageText), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20));
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff4d83b3>%s</c>", name, message.messageText), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
} else {
if (message.messageOwner.message != null) {
String mess = message.messageOwner.message;
@ -385,7 +377,7 @@ public class DialogCell extends BaseCell {
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff808080>%s</c>", name, mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20));
messageString = Emoji.replaceEmoji(AndroidUtilities.replaceTags(String.format("<c#ff4d83b3>%s:</c> <c#ff808080>%s</c>", name, mess), AndroidUtilities.FLAG_TAG_COLOR), messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
}
}
}
@ -580,7 +572,7 @@ public class DialogCell extends BaseCell {
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
messageString = Emoji.replaceEmoji(mess, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(17));
messageString = Emoji.replaceEmoji(mess, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(17), false);
}
messageWidth = Math.max(AndroidUtilities.dp(12), messageWidth);
CharSequence messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END);

View file

@ -13,7 +13,6 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper;
public class PhotoAttachCameraCell extends FrameLayout {
@ -23,7 +22,7 @@ public class PhotoAttachCameraCell extends FrameLayout {
ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setImageResource(R.drawable.ic_attach_photobig);
//imageView.setImageResource(R.drawable.ic_attach_photobig);
imageView.setBackgroundColor(0xff777777);
addView(imageView, LayoutHelper.createFrame(80, 80));
}

View file

@ -81,5 +81,6 @@ public class PhotoAttachPhotoCell extends FrameLayout {
public void setOnCheckClickLisnener(OnClickListener onCheckClickLisnener) {
checkFrame.setOnClickListener(onCheckClickLisnener);
imageView.setOnClickListener(onCheckClickLisnener);
}
}

View file

@ -100,7 +100,7 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F
});
nameTextView = new TextView(context);
nameTextView.setTextColor(0xff222222);
nameTextView.setTextColor(0xff212121);
nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
nameTextView.setLines(1);
@ -318,6 +318,9 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F
@Override
public void onProgressDownload(String fileName, float progress) {
if (progressView.getVisibility() != VISIBLE) {
updateFileExistIcon();
}
progressView.setProgress(progress, true);
}

View file

@ -28,7 +28,7 @@ public class SharedMediaSectionCell extends FrameLayout {
textView = new TextView(getContext());
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textView.setTextColor(0xff222222);
textView.setTextColor(0xff212121);
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 13, 0, 13, 0));
}

View file

@ -63,14 +63,14 @@ public class StickerEmojiCell extends FrameLayout {
for (TLRPC.DocumentAttribute attribute : document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
if (attribute.alt != null && attribute.alt.length() > 0) {
emojiTextView.setText(Emoji.replaceEmoji(attribute.alt, emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16)));
emojiTextView.setText(Emoji.replaceEmoji(attribute.alt, emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), false));
set = true;
}
break;
}
}
if (!set) {
emojiTextView.setText(Emoji.replaceEmoji(StickersQuery.getEmojiForSticker(sticker.id), emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16)));
emojiTextView.setText(Emoji.replaceEmoji(StickersQuery.getEmojiForSticker(sticker.id), emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), false));
}
emojiTextView.setVisibility(VISIBLE);
} else {

View file

@ -36,7 +36,7 @@ public class TextDetailCell extends FrameLayout {
textView.setMaxLines(1);
textView.setSingleLine(true);
textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 16 : 71, 10, LocaleController.isRTL ? 16 : 71, 0));
addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 16 : 71, 10, LocaleController.isRTL ? 71 : 16, 0));
valueTextView = new TextView(context);
valueTextView.setTextColor(0xff8a8a8a);
@ -45,7 +45,7 @@ public class TextDetailCell extends FrameLayout {
valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true);
valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 16 : 71, 35, LocaleController.isRTL ? 16 : 71, 0));
addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 16 : 71, 35, LocaleController.isRTL ? 71 : 16, 0));
imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER);

View file

@ -16,7 +16,6 @@ import android.text.InputType;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -57,7 +56,7 @@ public class ChangeChatNameActivity extends BaseFragment {
}
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("EditName", R.string.EditName));

View file

@ -15,7 +15,6 @@ import android.text.InputType;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -50,7 +49,7 @@ public class ChangeNameActivity extends BaseFragment {
private final static int done_button = 1;
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("EditName", R.string.EditName));

View file

@ -24,7 +24,6 @@ import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
@ -102,7 +101,7 @@ public class ChangePhoneActivity extends BaseFragment {
}
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setTitle(LocaleController.getString("AppName", R.string.AppName));
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {

View file

@ -13,7 +13,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
@ -36,7 +35,7 @@ import org.telegram.ui.Components.LayoutHelper;
public class ChangePhoneHelpActivity extends BaseFragment {
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);

View file

@ -20,7 +20,6 @@ import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -62,7 +61,7 @@ public class ChangeUsernameActivity extends BaseFragment {
private final static int done_button = 1;
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("Username", R.string.Username));

View file

@ -14,7 +14,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
@ -32,7 +31,6 @@ import android.util.Base64;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -40,6 +38,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -96,6 +95,7 @@ import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu;
import org.telegram.ui.ActionBar.ActionBarMenuItem;
import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.Cells.ChatMusicCell;
import org.telegram.ui.Cells.ChatUnreadCell;
import org.telegram.ui.Components.AlertsCreator;
import org.telegram.ui.Components.AvatarDrawable;
@ -123,7 +123,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, MessagesActivity.MessagesActivityDelegate,
public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.MessagesActivityDelegate,
PhotoViewer.PhotoViewerProvider {
protected TLRPC.Chat currentChat;
@ -177,6 +177,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
private ListView mentionListView;
private AnimatorSetProxy mentionListAnimation;
private ChatAttachView chatAttachView;
private BottomSheet chatAttachViewSheet;
private boolean allowStickersPanel;
private AnimatorSetProxy runningAnimation;
@ -455,6 +456,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
NotificationCenter.getInstance().addObserver(this, NotificationCenter.removeAllMessagesFromDialog);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.screenshotTook);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.blockedUsersDidLoaded);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileNewChunkAvailable);
@ -558,6 +560,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botInfoDidLoaded);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botKeyboardDidLoaded);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatSearchResultsAvailable);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
if (AndroidUtilities.isTablet()) {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true);
@ -578,11 +581,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
chatAttachView.onDestroy();
}
AndroidUtilities.unlockOrientation(getParentActivity());
MediaController.getInstance().stopAudio();
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject != null && !messageObject.isMusic()) {
MediaController.getInstance().stopAudio();
}
}
@Override
public View createView(Context context, LayoutInflater inflater) {
public View createView(Context context) {
for (int a = 0; a < 8; a++) {
chatMessageCellsCache.add(new ChatMessageCell(context));
@ -595,6 +601,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
lastStatus = null;
hasOwnBackground = true;
chatAttachView = null;
chatAttachViewSheet = null;
ResourceLoader.loadRecources(context);
@ -673,7 +680,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
Bundle args = new Bundle();
args.putBoolean("onlySelect", true);
args.putInt("dialogsType", 1);
MessagesActivity fragment = new MessagesActivity(args);
DialogsActivity fragment = new DialogsActivity(args);
fragment.setDelegate(ChatActivity.this);
presentFragment(fragment);
} else if (id == chat_enc_timer) {
@ -770,20 +777,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
selectedMessagesCanCopyIds.clear();
actionBar.hideActionMode();
updateVisibleRows();
}/* else if (id == chat_menu_attach) {
} else if (id == chat_menu_attach) {
if (getParentActivity() == null) {
return;
}
BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity());
if (chatAttachView == null) {
BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity());
chatAttachView = new ChatAttachView(getParentActivity());
chatAttachView.setDelegate(new ChatAttachView.ChatAttachViewDelegate() {
@Override
public void didPressedButton(int button) {
if (visibleDialog != null) {
visibleDialog.dismiss();
}
if (button == 7) {
chatAttachViewSheet.dismiss();
HashMap<Integer, MediaController.PhotoEntry> selectedPhotos = chatAttachView.getSelectedPhotos();
if (!selectedPhotos.isEmpty()) {
ArrayList<String> photos = new ArrayList<>();
@ -796,31 +802,50 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
showReplyPanel(false, null, null, null, false, true);
}
return;
} else {
chatAttachViewSheet.dismissWithButtonClick(button);
}
processSelectedAttach(button);
}
});
builder.setDelegate(new BottomSheet.BottomSheetDelegate() {
@Override
public void onRevealAnimationStart(boolean open) {
chatAttachView.onRevealAnimationStart(open);
}
@Override
public void onRevealAnimationProgress(boolean open, float radius, int x, int y) {
chatAttachView.onRevealAnimationProgress(open, radius, x, y);
}
@Override
public void onRevealAnimationEnd(boolean open) {
chatAttachView.onRevealAnimationEnd(open);
}
@Override
public void onOpenAnimationEnd() {
chatAttachView.onRevealAnimationEnd(true);
}
@Override
public View getRevealView() {
return menuItem;
}
});
builder.setApplyTopPaddings(false);
builder.setUseRevealAnimation();
builder.setCustomView(chatAttachView);
chatAttachViewSheet = builder.create();
}
builder.setCustomView(chatAttachView);
final int coords[] = new int[2];
menuItem.getLocationInWindow(coords);
builder.setRevealAnimation(coords[0] + menuItem.getWidth() / 2, coords[1] + menuItem.getHeight() / 2);
builder.setDelegate(new BottomSheet.BottomSheetDelegate() {
@Override
public void onOpenAnimationStart() {
chatAttachView.startAnimations(coords[1] > AndroidUtilities.displaySize.y - AndroidUtilities.dp(100));
}
@Override
public void onOpenAnimationEnd() {
}
});
chatAttachView.init(ChatActivity.this);
showDialog(builder.create());
}*/ else if (id == attach_gallery || id == attach_video || id == attach_document || id == attach_location || id == attach_photo || id == attach_audio || id == attach_contact) {
showDialog(chatAttachViewSheet);
}/* else if (id == attach_gallery || id == attach_video || id == attach_document || id == attach_location || id == attach_photo || id == attach_audio || id == attach_contact) {
processSelectedAttach(id);
} else if (id == bot_help) {
} */else if (id == bot_help) {
SendMessagesHelper.getInstance().sendMessage("/help", dialog_id, null, null, false);
} else if (id == bot_settings) {
SendMessagesHelper.getInstance().sendMessage("/settings", dialog_id, null, null, false);
@ -922,12 +947,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
ActionBarMenu menu = actionBar.createMenu();
if (currentEncryptedChat == null && !isBroadcast) {
/*searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true, false).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() {
searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true, false).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() {
@Override
public void onSearchCollapse() {
avatarContainer.setVisibility(View.VISIBLE);
headerItem.setVisibility(View.VISIBLE);
if (chatActivityEnterView.hasText()) {
if (headerItem != null) {
headerItem.setVisibility(View.GONE);
}
if (attachItem != null) {
attachItem.setVisibility(View.VISIBLE);
}
} else {
if (headerItem != null) {
headerItem.setVisibility(View.VISIBLE);
}
if (attachItem != null) {
attachItem.setVisibility(View.GONE);
}
}
searchItem.setVisibility(View.GONE);
//chatActivityEnterView.setVisibility(View.VISIBLE);
searchUpItem.clearAnimation();
@ -945,7 +984,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
searchItem.getSearchField().requestFocus();
AndroidUtilities.showKeyboard(searchItem.getSearchField());
}
}, 200); //TODO find a better way to open keyboard
}, 300); //TODO find a better way to open keyboard
}
@Override
@ -960,7 +999,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
searchUpItem = menu.addItem(search_up, R.drawable.search_up);
searchUpItem.setVisibility(View.GONE);
searchDownItem = menu.addItem(search_down, R.drawable.search_down);
searchDownItem.setVisibility(View.GONE);*/
searchDownItem.setVisibility(View.GONE);
}
headerItem = menu.addItem(0, R.drawable.ic_ab_other);
@ -990,22 +1029,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
updateSubtitle();
updateTitleIcons();
attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other).setAllowCloseAnimation(false);
attachItem.addSubItem(attach_photo, LocaleController.getString("ChatTakePhoto", R.string.ChatTakePhoto), R.drawable.ic_attach_photo);
attachItem.addSubItem(attach_gallery, LocaleController.getString("ChatGallery", R.string.ChatGallery), R.drawable.ic_attach_gallery);
attachItem.addSubItem(attach_video, LocaleController.getString("ChatVideo", R.string.ChatVideo), R.drawable.ic_attach_video);
attachItem.addSubItem(attach_document, LocaleController.getString("ChatDocument", R.string.ChatDocument), R.drawable.ic_ab_doc);
attachItem.addSubItem(attach_location, LocaleController.getString("ChatLocation", R.string.ChatLocation), R.drawable.ic_attach_location);
attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other).setOverrideMenuClick(true).setAllowCloseAnimation(false);
attachItem.setVisibility(View.GONE);
menuItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_attach).setAllowCloseAnimation(false);
menuItem.addSubItem(attach_photo, LocaleController.getString("ChatTakePhoto", R.string.ChatTakePhoto), R.drawable.ic_attach_photo);
menuItem.addSubItem(attach_gallery, LocaleController.getString("ChatGallery", R.string.ChatGallery), R.drawable.ic_attach_gallery);
menuItem.addSubItem(attach_video, LocaleController.getString("ChatVideo", R.string.ChatVideo), R.drawable.ic_attach_video);
menuItem.addSubItem(attach_document, LocaleController.getString("ChatDocument", R.string.ChatDocument), R.drawable.ic_ab_doc);
menuItem.addSubItem(attach_location, LocaleController.getString("ChatLocation", R.string.ChatLocation), R.drawable.ic_attach_location);
menuItem.setShowFromBottom(true);
menuItem.setBackgroundDrawable(null);
actionModeViews.clear();
@ -1053,8 +1079,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
@ -1067,14 +1091,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child == chatActivityEnterView) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
inputFieldHeight = child.getMeasuredHeight();
break;
}
}
measureChildWithMargins(chatActivityEnterView, widthMeasureSpec, 0, heightMeasureSpec, 0);
inputFieldHeight = chatActivityEnterView.getMeasuredHeight();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE || child == chatActivityEnterView) {
@ -1660,6 +1680,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override
public void onAttachButtonHidden() {
if (actionBar.isSearchFieldVisible()) {
return;
}
if (attachItem != null) {
attachItem.setVisibility(View.VISIBLE);
}
@ -1670,6 +1693,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override
public void onAttachButtonShow() {
if (actionBar.isSearchFieldVisible()) {
return;
}
if (attachItem != null) {
attachItem.setVisibility(View.GONE);
}
@ -1680,7 +1706,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override
public void onWindowSizeChanged(int size) {
if (size < AndroidUtilities.dp(72) + AndroidUtilities.getCurrentActionBarHeight()) {
if (size < AndroidUtilities.dp(72) + ActionBar.getCurrentActionBarHeight()) {
allowStickersPanel = false;
if (stickersPanel.getVisibility() == View.VISIBLE) {
stickersPanel.clearAnimation();
@ -2089,12 +2115,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
});
presentFragment(fragment);
} else if (which == attach_audio) {
try {
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, 32);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
AudioSelectActivity fragment = new AudioSelectActivity();
fragment.setDelegate(new AudioSelectActivity.AudioSelectActivityDelegate() {
@Override
public void didSelectAudio(ArrayList<MessageObject> audios) {
SendMessagesHelper.prepareSendingAudioDocuments(audios, dialog_id, replyingMessageObject);
showReplyPanel(false, null, null, null, false, true);
}
});
presentFragment(fragment);
} else if (which == attach_contact) {
try {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
@ -2216,7 +2245,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14)));
replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false));
}
} else if (messageObjects != null) {
if (messageObjects.isEmpty()) {
@ -2277,7 +2306,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
mess = mess.substring(0, 150);
}
mess = mess.replace("\n", " ");
replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14)));
replyObjectTextView.setText(Emoji.replaceEmoji(mess, replyObjectTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false));
} else {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjects.size()));
}
@ -2296,7 +2325,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
} else if (type == 12) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedContact", messageObjects.size()));
} else if (type == 2) {
} else if (type == 2 || type == 14) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedAudio", messageObjects.size()));
} else if (type == 13) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjects.size()));
@ -3221,22 +3250,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
FileLog.e("tmessages", e);
}
}
} else if (requestCode == 32) {
if (data == null || data.getData() == null) {
showAttachmentError();
return;
}
Uri uri = data.getData();
String path = AndroidUtilities.getPath(uri);
if (path != null) {
TLRPC.TL_audio audio = new TLRPC.TL_audio();
audio.dc_id = Integer.MIN_VALUE;
audio.id = Integer.MIN_VALUE;
audio.user_id = UserConfig.getClientUserId();
audio.mime_type = "audio/mp3";
SendMessagesHelper.getInstance().sendMessage(audio, path, dialog_id, replyingMessageObject);
showReplyPanel(false, null, null, null, false, true);
}
}
}
}
@ -3587,7 +3600,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (currentEncryptedChat != null && obj.isOut() && obj.messageOwner.action != null && obj.messageOwner.action instanceof TLRPC.TL_messageEncryptedAction &&
obj.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL && getParentActivity() != null) {
TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) obj.messageOwner.action.encryptedAction;
if (AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 17 && currentEncryptedChat.ttl > 0 && currentEncryptedChat.ttl <= 60) {
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity());
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
@ -4005,7 +4017,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
int encId = (Integer) args[0];
if (currentEncryptedChat != null && currentEncryptedChat.id == encId) {
int date = (Integer) args[1];
boolean started = false;
for (MessageObject obj : messages) {
if (!obj.isOut()) {
continue;
@ -4018,7 +4029,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
updateVisibleRows();
}
} else if (id == NotificationCenter.audioDidReset) {
} else if (id == NotificationCenter.audioDidReset || id == NotificationCenter.audioPlayStateChanged) {
Integer mid = (Integer) args[0];
if (chatListView != null) {
int count = chatListView.getChildCount();
@ -4027,7 +4038,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (view instanceof ChatAudioCell) {
ChatAudioCell cell = (ChatAudioCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) {
cell.updateButtonState();
cell.updateButtonState(false);
break;
}
} else if (view instanceof ChatMusicCell) {
ChatMusicCell cell = (ChatMusicCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) {
cell.updateButtonState(false);
break;
}
}
@ -4045,6 +4062,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
cell.updateProgress();
break;
}
} else if (view instanceof ChatMusicCell) {
ChatMusicCell cell = (ChatMusicCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) {
MessageObject playing = cell.getMessageObject();
MessageObject player = MediaController.getInstance().getPlayingMessageObject();
playing.audioProgress = player.audioProgress;
playing.audioProgressSec = player.audioProgressSec;
cell.updateProgress();
break;
}
}
}
}
@ -4121,6 +4148,27 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
} else if (id == NotificationCenter.audioDidStarted) {
MessageObject messageObject = (MessageObject) args[0];
sendSecretMessageRead(messageObject);
int mid = messageObject.getId();
if (chatListView != null) {
int count = chatListView.getChildCount();
for (int a = 0; a < count; a++) {
View view = chatListView.getChildAt(a);
if (view instanceof ChatAudioCell) {
ChatAudioCell cell = (ChatAudioCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) {
cell.updateButtonState(false);
break;
}
} else if (view instanceof ChatMusicCell) {
ChatMusicCell cell = (ChatMusicCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) {
cell.updateButtonState(false);
break;
}
}
}
}
} else if (id == NotificationCenter.updateMessageMedia) {
MessageObject messageObject = (MessageObject) args[0];
MessageObject existMessageObject = messagesDict.get(messageObject.getId());
@ -4555,7 +4603,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
actionBar.setBackButtonImage(R.drawable.ic_close_white);
}
}
int padding = (AndroidUtilities.getCurrentActionBarHeight() - AndroidUtilities.dp(48)) / 2;
int padding = (ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(48)) / 2;
avatarContainer.setPadding(avatarContainer.getPaddingLeft(), padding, avatarContainer.getPaddingRight(), padding);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) avatarContainer.getLayoutParams();
layoutParams.topMargin = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0);
@ -4629,7 +4677,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
options = new int[]{8, 2, 3, 1};
} else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
String saveString;
if (selectedObject.isMusic()) {
saveString = LocaleController.getString("SaveToMusic", R.string.SaveToMusic);
} else {
saveString = LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads);
}
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), saveString, LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{8, 10, 4, 2, 1};
} else {
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
@ -4639,7 +4693,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{8, 5, 4, 2, 1};
} else if (type == 6) {
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
String saveString;
if (selectedObject.isMusic()) {
saveString = LocaleController.getString("SaveToMusic", R.string.SaveToMusic);
} else {
saveString = LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads);
}
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("SaveToGallery", R.string.SaveToGallery), saveString, LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{8, 7, 10, 6, 2, 1};
} else if (type == 7) {
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("AddToStickers", R.string.AddToStickers), LocaleController.getString("Delete", R.string.Delete)};
@ -4654,7 +4714,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
options = new int[]{2, 3, 1};
} else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
items = new CharSequence[]{LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
String saveString;
if (selectedObject.isMusic()) {
saveString = LocaleController.getString("SaveToMusic", R.string.SaveToMusic);
} else {
saveString = LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads);
}
items = new CharSequence[]{saveString, LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{10, 4, 2, 1};
} else {
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
@ -4664,7 +4730,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
items = new CharSequence[]{LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{5, 4, 2, 1};
} else if (type == 6) {
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
String saveString;
if (selectedObject.isMusic()) {
saveString = LocaleController.getString("SaveToMusic", R.string.SaveToMusic);
} else {
saveString = LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads);
}
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), saveString, LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{7, 10, 6, 2, 1};
} else if (type == 7) {
items = new CharSequence[]{LocaleController.getString("Reply", R.string.Reply), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("AddToStickers", R.string.AddToStickers), LocaleController.getString("Delete", R.string.Delete)};
@ -4680,7 +4752,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
options = new int[]{3, 1};
} else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
items = new CharSequence[]{LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Delete", R.string.Delete)};
String saveString;
if (selectedObject.isMusic()) {
saveString = LocaleController.getString("SaveToMusic", R.string.SaveToMusic);
} else {
saveString = LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads);
}
items = new CharSequence[]{saveString, LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Delete", R.string.Delete)};
options = new int[]{10, 4, 1};
} else {
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("Delete", R.string.Delete)};
@ -4770,7 +4848,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
Bundle args = new Bundle();
args.putBoolean("onlySelect", true);
args.putInt("dialogsType", 1);
MessagesActivity fragment = new MessagesActivity(args);
DialogsActivity fragment = new DialogsActivity(args);
fragment.setDelegate(this);
presentFragment(fragment);
} else if (option == 3) {
@ -4787,7 +4865,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
FileLog.e("tmessages", e);
}
} else if (option == 4) {
String fileName = selectedObject.getFileName();
String path = selectedObject.messageOwner.attachPath;
if (path != null && path.length() > 0) {
File temp = new File(path);
@ -4802,7 +4879,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
MediaController.saveFile(path, getParentActivity(), 1, null);
} else if (selectedObject.type == 1) {
MediaController.saveFile(path, getParentActivity(), 0, null);
} else if (selectedObject.type == 8 || selectedObject.type == 9) {
} else if (selectedObject.type == 8 || selectedObject.type == 9 || selectedObject.type == 14) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(selectedObject.messageOwner.media.document.mime_type);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path)));
@ -4837,7 +4914,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
}
} else if (option == 6 || option == 7) {
String fileName = selectedObject.getFileName();
String path = selectedObject.messageOwner.attachPath;
if (path != null && path.length() > 0) {
File temp = new File(path);
@ -4848,7 +4924,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString();
}
if (selectedObject.type == 8 || selectedObject.type == 9) {
if (selectedObject.type == 8 || selectedObject.type == 9 || selectedObject.type == 14) {
if (option == 6) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(selectedObject.messageOwner.media.document.mime_type);
@ -4877,13 +4953,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString();
}
MediaController.saveFile(path, getParentActivity(), 2, fileName);
MediaController.saveFile(path, getParentActivity(), selectedObject.isMusic() ? 3 : 2, fileName);
}
selectedObject = null;
}
@Override
public void didSelectDialog(MessagesActivity activity, long did, boolean param) {
public void didSelectDialog(DialogsActivity activity, long did, boolean param) {
if (dialog_id != 0 && (forwaringMessage != null || !selectedMessagesIds.isEmpty())) {
ArrayList<MessageObject> fmessages = new ArrayList<>();
if (forwaringMessage != null) {
@ -4961,7 +5037,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
public boolean isGoogleMapsInstalled() {
try {
ApplicationInfo info = ApplicationLoader.applicationContext.getPackageManager().getApplicationInfo("com.google.android.apps.maps", 0);
ApplicationLoader.applicationContext.getPackageManager().getApplicationInfo("com.google.android.apps.maps", 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
if (getParentActivity() == null) {
@ -5213,7 +5289,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (url.startsWith("@")) {
MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0);
} else if (url.startsWith("#")) {
MessagesActivity fragment = new MessagesActivity(null);
DialogsActivity fragment = new DialogsActivity(null);
fragment.setSearchString(url);
presentFragment(fragment);
} else if (url.startsWith("/")) {
@ -5221,6 +5297,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}
}
});
} else if (viewType == 8) {
view = new ChatMusicCell(mContext);
}
if (view instanceof ChatBaseCell) {
@ -5261,7 +5339,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (url.startsWith("@")) {
MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0);
} else if (url.startsWith("#")) {
MessagesActivity fragment = new MessagesActivity(null);
DialogsActivity fragment = new DialogsActivity(null);
fragment.setSearchString(url);
presentFragment(fragment);
} else if (url.startsWith("/")) {
@ -5273,7 +5351,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
public void needOpenWebView(String url, String title, String originalUrl, int w, int h) {
BottomSheet.Builder builder = new BottomSheet.Builder(mContext);
builder.setCustomView(new WebFrameLayout(mContext, builder.create(), title, originalUrl, url, w, h));
builder.setOverrideTabletWidth(true);
builder.setUseFullWidth(true);
showDialog(builder.create());
}
@ -5434,6 +5512,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
showDialog(builder.create());
}
});
} else if (view instanceof ChatMusicCell) {
((ChatMusicCell) view).setMusicDelegate(new ChatMusicCell.ChatMusicCellDelegate() {
@Override
public boolean needPlayMusic(MessageObject messageObject) {
return MediaController.getInstance().setPlaylist(messages, messageObject);
}
});
}
} else if (view instanceof ChatActionCell) {
((ChatActionCell) view).setDelegate(new ChatActionCell.ChatActionCellDelegate() {

View file

@ -57,7 +57,7 @@ public class BotKeyboardView extends LinearLayout {
public void setPanelHeight(int height) {
panelHeight = height;
if (isFullSize && botButtons != null) {
if (isFullSize && botButtons != null && botButtons.rows.size() != 0) {
buttonHeight = !isFullSize ? 42 : (int) Math.max(42, (panelHeight - AndroidUtilities.dp(30) - (botButtons.rows.size() - 1) * AndroidUtilities.dp(10)) / botButtons.rows.size() / AndroidUtilities.density);
int count = container.getChildCount();
int newHeight = AndroidUtilities.dp(buttonHeight);
@ -87,7 +87,7 @@ public class BotKeyboardView extends LinearLayout {
container.removeAllViews();
buttonViews.clear();
if (buttons != null) {
if (buttons != null && botButtons.rows.size() != 0) {
isFullSize = (buttons.flags & 1) == 0;
buttonHeight = !isFullSize ? 42 : (int) Math.max(42, (panelHeight - AndroidUtilities.dp(30) - (botButtons.rows.size() - 1) * AndroidUtilities.dp(10)) / botButtons.rows.size() / AndroidUtilities.density);
for (int a = 0; a < buttons.rows.size(); a++) {
@ -106,7 +106,7 @@ public class BotKeyboardView extends LinearLayout {
textView.setGravity(Gravity.CENTER);
textView.setBackgroundResource(R.drawable.bot_keyboard_states);
textView.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0);
textView.setText(Emoji.replaceEmoji(button.text, textView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16)));
textView.setText(Emoji.replaceEmoji(button.text, textView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(16), false));
layout.addView(textView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, weight, 0, 0, b != row.buttons.size() - 1 ? 10 : 0, 0));
textView.setOnClickListener(new OnClickListener() {
@Override

View file

@ -44,6 +44,7 @@ import org.telegram.android.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.android.AnimationCompat.AnimatorListenerAdapterProxy;
import org.telegram.android.AnimationCompat.AnimatorSetProxy;
@ -117,6 +118,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
private BaseFragment parentFragment;
private long dialog_id;
private boolean ignoreTextChange;
private int innerTextChange;
private MessageObject replyingMessageObject;
private MessageObject botMessageObject;
private TLRPC.WebPage messageWebPage;
@ -187,7 +189,16 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
}
});
messageEditText = new EditText(context);
messageEditText = new EditText(context) {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isPopupShowing() && event.getAction() == MotionEvent.ACTION_DOWN) {
showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2, 0);
openKeyboardInternal();
}
return super.onTouchEvent(event);
}
};
messageEditText.setHint(LocaleController.getString("TypeMessage", R.string.TypeMessage));
messageEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
messageEditText.setInputType(messageEditText.getInputType() | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
@ -216,14 +227,6 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
return false;
}
});
messageEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isPopupShowing()) {
showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2, 0);
}
}
});
messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
@ -240,6 +243,8 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
}
});
messageEditText.addTextChangedListener(new TextWatcher() {
boolean processChange = false;
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
@ -247,16 +252,20 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
String message = getTrimmedString(charSequence.toString());
if (innerTextChange == 1) {
return;
}
checkSendButton(true);
String message = getTrimmedString(charSequence.toString());
if (delegate != null) {
if (count > 2 || charSequence == null || charSequence.length() == 0) {
messageWebPageSearch = true;
}
delegate.onTextChanged(charSequence, before > count + 1 || (count - before) > 2);
}
if (innerTextChange != 2 && before != count && (count - before) > 1) {
processChange = true;
}
if (message.length() != 0 && lastTypingTimeSend < System.currentTimeMillis() - 5000 && !ignoreTextChange) {
int currentTime = ConnectionsManager.getInstance().getCurrentTime();
TLRPC.User currentUser = null;
@ -275,19 +284,19 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
@Override
public void afterTextChanged(Editable editable) {
if (innerTextChange != 0) {
return;
}
if (sendByEnter && editable.length() > 0 && editable.charAt(editable.length() - 1) == '\n') {
sendMessage();
}
int i = 0;
ImageSpan[] arrayOfImageSpan = editable.getSpans(0, editable.length(), ImageSpan.class);
int j = arrayOfImageSpan.length;
while (true) {
if (i >= j) {
Emoji.replaceEmoji(editable, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20));
return;
if (processChange) {
ImageSpan[] spans = editable.getSpans(0, editable.length(), ImageSpan.class);
for (int i = 0; i < spans.length; i++) {
editable.removeSpan(spans[i]);
}
editable.removeSpan(arrayOfImageSpan[i]);
i++;
Emoji.replaceEmoji(editable, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false);
processChange = false;
}
}
});
@ -590,7 +599,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
delegate.onWindowSizeChanged(size);
}
if (topView != null) {
if (size < AndroidUtilities.dp(72) + AndroidUtilities.getCurrentActionBarHeight()) {
if (size < AndroidUtilities.dp(72) + ActionBar.getCurrentActionBarHeight()) {
if (allowShowTopView) {
allowShowTopView = false;
if (needShowTopView) {
@ -773,11 +782,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
});
runningAnimation2.start();
if (messageEditText != null) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) messageEditText.getLayoutParams();
layoutParams.rightMargin = AndroidUtilities.dp(0);
messageEditText.setLayoutParams(layoutParams);
}
updateFieldRight(0);
delegate.onAttachButtonHidden();
}
@ -823,9 +828,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
attachButton.setVisibility(View.GONE);
attachButton.clearAnimation();
delegate.onAttachButtonHidden();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) messageEditText.getLayoutParams();
layoutParams.rightMargin = AndroidUtilities.dp(0);
messageEditText.setLayoutParams(layoutParams);
updateFieldRight(0);
}
}
}
@ -854,11 +857,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
runningAnimation2.setDuration(100);
runningAnimation2.start();
if (messageEditText != null) {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) messageEditText.getLayoutParams();
layoutParams.rightMargin = AndroidUtilities.dp(50);
messageEditText.setLayoutParams(layoutParams);
}
updateFieldRight(1);
delegate.onAttachButtonShow();
}
@ -903,14 +902,37 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
if (attachButton != null) {
delegate.onAttachButtonShow();
attachButton.setVisibility(View.VISIBLE);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) messageEditText.getLayoutParams();
layoutParams.rightMargin = AndroidUtilities.dp(50);
messageEditText.setLayoutParams(layoutParams);
updateFieldRight(1);
}
}
}
}
private void updateFieldRight(int attachVisible) {
if (messageEditText == null) {
return;
}
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) messageEditText.getLayoutParams();
if (attachVisible == 1) {
if (botButton != null && botButton.getVisibility() == VISIBLE) {
layoutParams.rightMargin = AndroidUtilities.dp(98);
} else {
layoutParams.rightMargin = AndroidUtilities.dp(50);
}
} else if (attachVisible == 2) {
if (layoutParams.rightMargin != AndroidUtilities.dp(2)) {
if (botButton != null && botButton.getVisibility() == VISIBLE) {
layoutParams.rightMargin = AndroidUtilities.dp(98);
} else {
layoutParams.rightMargin = AndroidUtilities.dp(50);
}
}
} else {
layoutParams.rightMargin = AndroidUtilities.dp(2);
}
messageEditText.setLayoutParams(layoutParams);
}
private void updateAudioRecordIntefrace() {
if (recordingAudio) {
if (audioInterfaceState == 1) {
@ -1108,6 +1130,7 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
} else {
botButton.setVisibility(GONE);
}
updateFieldRight(2);
ViewProxy.setPivotX(attachButton, AndroidUtilities.dp(botButton.getVisibility() == GONE ? 48 : 96));
attachButton.clearAnimation();
}
@ -1209,12 +1232,15 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat
i = 0;
}
try {
CharSequence localCharSequence = Emoji.replaceEmoji(symbol/* + "\uFE0F"*/, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20));
innerTextChange = 2;
CharSequence localCharSequence = Emoji.replaceEmoji(symbol/* + "\uFE0F"*/, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false);
messageEditText.setText(messageEditText.getText().insert(i, localCharSequence));
int j = i + localCharSequence.length();
messageEditText.setSelection(j, j);
} catch (Exception e) {
FileLog.e("tmessages", e);
} finally {
innerTextChange = 0;
}
}

View file

@ -8,8 +8,11 @@
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
@ -18,6 +21,7 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@ -25,14 +29,16 @@ import android.widget.TextView;
import org.telegram.android.AndroidUtilities;
import org.telegram.android.LocaleController;
import org.telegram.android.MediaController;
import org.telegram.android.NotificationCenter;
import org.telegram.android.support.widget.LinearLayoutManager;
import org.telegram.messenger.R;
import org.telegram.ui.Adapters.PhotoAttachAdapter;
import org.telegram.ui.ChatActivity;
import java.util.ArrayList;
import java.util.HashMap;
public class ChatAttachView extends FrameLayout {
public class ChatAttachView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate {
public interface ChatAttachViewDelegate {
void didPressedButton(int button);
@ -42,7 +48,16 @@ public class ChatAttachView extends FrameLayout {
private PhotoAttachAdapter photoAttachAdapter;
private ChatActivity baseFragment;
private AttachButton sendPhotosButton;
private AttachButton buttons[] = new AttachButton[8];
private View views[] = new View[20];
private RecyclerListView attachPhotoRecyclerView;
private View lineView;
private EmptyTextProgressView progressView;
private float[] distCache = new float[20];
private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();
private boolean loading;
private ChatAttachViewDelegate delegate;
@ -56,7 +71,6 @@ public class ChatAttachView extends FrameLayout {
imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER);
//imageView.setColorFilter(0x33000000);
addView(imageView, LayoutHelper.createFrame(64, 64, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
textView = new TextView(context);
@ -83,10 +97,15 @@ public class ChatAttachView extends FrameLayout {
public ChatAttachView(Context context) {
super(context);
RecyclerListView attachPhotoRecyclerView = new RecyclerListView(context);
if (photoAttachAdapter != null) {
photoAttachAdapter.onDestroy();
NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded);
if (MediaController.allPhotosAlbumEntry == null) {
if (Build.VERSION.SDK_INT >= 21) {
MediaController.loadGalleryPhotosAlbums(0);
}
loading = true;
}
views[8] = attachPhotoRecyclerView = new RecyclerListView(context);
attachPhotoRecyclerView.setVerticalScrollBarEnabled(true);
attachPhotoRecyclerView.setAdapter(photoAttachAdapter = new PhotoAttachAdapter(context));
attachPhotoRecyclerView.setClipToPadding(false);
@ -112,11 +131,14 @@ public class ChatAttachView extends FrameLayout {
}
});
View lineView = new View(getContext());
views[9] = progressView = new EmptyTextProgressView(context);
progressView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos));
addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80));
attachPhotoRecyclerView.setEmptyView(progressView);
views[10] = lineView = new View(getContext());
lineView.setBackgroundColor(0xffd2d2d2);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT);
layoutParams.topMargin = AndroidUtilities.dp(88);
addView(lineView, layoutParams);
addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT));
CharSequence[] items = new CharSequence[]{
LocaleController.getString("ChatCamera", R.string.ChatCamera),
LocaleController.getString("ChatGallery", R.string.ChatGallery),
@ -128,23 +150,21 @@ public class ChatAttachView extends FrameLayout {
""
};
int itemIcons[] = new int[] {
R.drawable.ic_attach_photo_big,
R.drawable.ic_attach_gallery_big,
R.drawable.ic_attach_video_big,
R.drawable.ic_attach_music_big,
R.drawable.ic_attach_file_big,
R.drawable.ic_attach_contact_big,
R.drawable.ic_attach_location_big,
R.drawable.ic_attach_hide_big,
R.drawable.attach_camera_states,
R.drawable.attach_gallery_states,
R.drawable.attach_video_states,
R.drawable.attach_audio_states,
R.drawable.attach_file_states,
R.drawable.attach_contact_states,
R.drawable.attach_location_states,
R.drawable.attach_hide_states,
};
for (int a = 0; a < 8; a++) {
AttachButton attachButton = new AttachButton(context);
attachButton.setTextAndIcon(items[a], itemIcons[a]);
int y = 97 + 95 * (a / 4);
int x = 10 + (a % 4) * 85;
addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP, x, y, 0, 0));
addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP));
attachButton.setTag(a);
buttons[a] = attachButton;
views[a] = attachButton;
if (a == 7) {
sendPhotosButton = attachButton;
sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0);
@ -164,24 +184,58 @@ public class ChatAttachView extends FrameLayout {
return true;
}
});
if (loading) {
progressView.showProgress();
} else {
progressView.showTextView();
}
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.albumsDidLoaded) {
if (photoAttachAdapter != null) {
loading = false;
progressView.showTextView();
photoAttachAdapter.notifyDataSetChanged();
}
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(278), MeasureSpec.EXACTLY));
super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(294), MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int width = right - left;
int t = AndroidUtilities.dp(8);
attachPhotoRecyclerView.layout(0, t, width, t + attachPhotoRecyclerView.getMeasuredHeight());
progressView.layout(0, t, width, t + progressView.getMeasuredHeight());
lineView.layout(0, AndroidUtilities.dp(96), width, AndroidUtilities.dp(96) + lineView.getMeasuredHeight());
int diff = (width - AndroidUtilities.dp(85 * 4 + 20)) / 3;
for (int a = 0; a < 8; a++) {
int y = AndroidUtilities.dp(105 + 95 * (a / 4));
int x = AndroidUtilities.dp(10) + (a % 4) * (AndroidUtilities.dp(85) + diff);
views[a].layout(x, y, x + views[a].getMeasuredWidth(), y + views[a].getMeasuredHeight());
}
}
public void updatePhotosButton() {
int count = photoAttachAdapter.getSelectedPhotos().size();
if (count == 0) {
sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.ic_attach_hide_big);
sendPhotosButton.imageView.setImageResource(R.drawable.ic_attach_hide_big_icon);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_hide_states);
sendPhotosButton.imageView.setImageResource(R.drawable.attach_hide2);
sendPhotosButton.textView.setText("");
} else {
sendPhotosButton.imageView.setPadding(AndroidUtilities.dp(2), 0, 0, 0);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.ic_attach_send_big);
sendPhotosButton.imageView.setImageResource(R.drawable.ic_attach_send_big_icon);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_send_states);
sendPhotosButton.imageView.setImageResource(R.drawable.attach_send2);
sendPhotosButton.textView.setText(LocaleController.formatString("SendItems", R.string.SendItems, String.format("(%d)", count)));
}
}
@ -190,22 +244,83 @@ public class ChatAttachView extends FrameLayout {
delegate = chatAttachViewDelegate;
}
public void startAnimations(boolean up) {
for (int a = 0; a < 4; a++) {
//buttons[a].setTranslationY(AndroidUtilities.dp(up ? 20 : -20));
//buttons[a + 4].setTranslationY(AndroidUtilities.dp(up ? 20 : -20));
buttons[a].setScaleX(0.8f);
buttons[a].setScaleY(0.8f);
buttons[a + 4].setScaleX(0.8f);
buttons[a + 4].setScaleY(0.8f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(ObjectAnimator.ofFloat(buttons[a], "scaleX", 1),
ObjectAnimator.ofFloat(buttons[a + 4], "scaleX", 1),
ObjectAnimator.ofFloat(buttons[a], "scaleY", 1),
ObjectAnimator.ofFloat(buttons[a + 4], "scaleY", 1));
animatorSet.setDuration(150);
animatorSet.setStartDelay((3 - a) * 40);
animatorSet.start();
public void onRevealAnimationEnd(boolean open) {
if (open && Build.VERSION.SDK_INT <= 19 && MediaController.allPhotosAlbumEntry == null) {
MediaController.loadGalleryPhotosAlbums(0);
}
}
@SuppressLint("NewApi")
public void onRevealAnimationStart(boolean open) {
if (!open) {
return;
}
int count = Build.VERSION.SDK_INT <= 19 ? 11 : 8;
for (int a = 0; a < count; a++) {
if (Build.VERSION.SDK_INT <= 19) {
if (a < 8) {
views[a].setScaleX(0.1f);
views[a].setScaleY(0.1f);
}
views[a].setAlpha(0.0f);
} else {
views[a].setScaleX(0.7f);
views[a].setScaleY(0.7f);
}
views[a].setTag(R.string.AppName, null);
distCache[a] = 0;
}
}
@SuppressLint("NewApi")
public void onRevealAnimationProgress(boolean open, float radius, int x, int y) {
if (!open) {
return;
}
int count = Build.VERSION.SDK_INT <= 19 ? 11 : 8;
for (int a = 0; a < count; a++) {
if (views[a].getTag(R.string.AppName) == null) {
if (distCache[a] == 0) {
int buttonX = views[a].getLeft() + views[a].getMeasuredWidth() / 2;
int buttonY = views[a].getTop() + views[a].getMeasuredHeight() / 2;
distCache[a] = (float) Math.sqrt((x - buttonX) * (x - buttonX) + (y - buttonY) * (y - buttonY));
float vecX = (x - buttonX) / distCache[a];
float vecY = (y - buttonY) / distCache[a];
views[a].setPivotX(views[a].getMeasuredWidth() / 2 + vecX * AndroidUtilities.dp(20));
views[a].setPivotY(views[a].getMeasuredHeight() / 2 + vecY * AndroidUtilities.dp(20));
}
if (distCache[a] > radius + AndroidUtilities.dp(27)) {
continue;
}
views[a].setTag(R.string.AppName, 1);
final ArrayList<Animator> animators = new ArrayList<>();
final ArrayList<Animator> animators2 = new ArrayList<>();
if (a < 8) {
animators.add(ObjectAnimator.ofFloat(views[a], "scaleX", 0.7f, 1.05f));
animators.add(ObjectAnimator.ofFloat(views[a], "scaleY", 0.7f, 1.05f));
animators2.add(ObjectAnimator.ofFloat(views[a], "scaleX", 1.0f));
animators2.add(ObjectAnimator.ofFloat(views[a], "scaleY", 1.0f));
}
if (Build.VERSION.SDK_INT <= 19) {
animators.add(ObjectAnimator.ofFloat(views[a], "alpha", 1.0f));
}
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animators);
animatorSet.setDuration(150);
animatorSet.setInterpolator(decelerateInterpolator);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animators2);
animatorSet.setDuration(100);
animatorSet.setInterpolator(decelerateInterpolator);
animatorSet.start();
}
});
animatorSet.start();
}
}
}
@ -221,7 +336,7 @@ public class ChatAttachView extends FrameLayout {
}
public void onDestroy() {
photoAttachAdapter.onDestroy();
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.albumsDidLoaded);
baseFragment = null;
}
}

Some files were not shown because too many files have changed in this diff Show more