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 { defaultConfig {
minSdkVersion 8 minSdkVersion 8
targetSdkVersion 22 targetSdkVersion 22
versionCode 572 versionCode 580
versionName "3.0.1" 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.NotificationsService" android:enabled="true"/>
<service android:name="org.telegram.android.NotificationRepeat" android:exported="false"/> <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.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"> <receiver android:name="org.telegram.android.AppStartReceiver" android:enabled="true">
<intent-filter> <intent-filter>

View file

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

View file

@ -542,16 +542,6 @@ public class AndroidUtilities {
return 0; 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() { public static Point getRealScreenSize() {
Point size = new Point(); Point size = new Point();
try { try {

View file

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

View file

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

View file

@ -64,7 +64,8 @@ public class Emoji {
new long[] new long[]
{}, {},
new long[]//189 new long[]//189
{0x00000000D83DDE04L, 0x00000000D83DDE03L, 0x00000000D83DDE00L, 0x00000000D83DDE0AL, 0x000000000000263AL, 0x00000000D83DDE09L, 0x00000000D83DDE0DL, {
0x00000000D83DDE04L, 0x00000000D83DDE03L, 0x00000000D83DDE00L, 0x00000000D83DDE0AL, 0x000000000000263AL, 0x00000000D83DDE09L, 0x00000000D83DDE0DL,
0x00000000D83DDE18L, 0x00000000D83DDE1AL, 0x00000000D83DDE17L, 0x00000000D83DDE19L, 0x00000000D83DDE1CL, 0x00000000D83DDE1DL, 0x00000000D83DDE1BL, 0x00000000D83DDE18L, 0x00000000D83DDE1AL, 0x00000000D83DDE17L, 0x00000000D83DDE19L, 0x00000000D83DDE1CL, 0x00000000D83DDE1DL, 0x00000000D83DDE1BL,
0x00000000D83DDE33L, 0x00000000D83DDE01L, 0x00000000D83DDE14L, 0x00000000D83DDE0CL, 0x00000000D83DDE12L, 0x00000000D83DDE1EL, 0x00000000D83DDE23L, 0x00000000D83DDE33L, 0x00000000D83DDE01L, 0x00000000D83DDE14L, 0x00000000D83DDE0CL, 0x00000000D83DDE12L, 0x00000000D83DDE1EL, 0x00000000D83DDE23L,
0x00000000D83DDE22L, 0x00000000D83DDE02L, 0x00000000D83DDE2DL, 0x00000000D83DDE2AL, 0x00000000D83DDE25L, 0x00000000D83DDE30L, 0x00000000D83DDE05L, 0x00000000D83DDE22L, 0x00000000D83DDE02L, 0x00000000D83DDE2DL, 0x00000000D83DDE2AL, 0x00000000D83DDE25L, 0x00000000D83DDE30L, 0x00000000D83DDE05L,
@ -92,7 +93,8 @@ public class Emoji {
0x00000000D83DDC93L, 0x00000000D83DDC95L, 0x00000000D83DDC96L, 0x00000000D83DDC9EL, 0x00000000D83DDC98L, 0x00000000D83DDC8CL, 0x00000000D83DDC8BL, 0x00000000D83DDC93L, 0x00000000D83DDC95L, 0x00000000D83DDC96L, 0x00000000D83DDC9EL, 0x00000000D83DDC98L, 0x00000000D83DDC8CL, 0x00000000D83DDC8BL,
0x00000000D83DDC8DL, 0x00000000D83DDC8EL, 0x00000000D83DDC64L, 0x00000000D83DDC65L, 0x00000000D83DDCACL, 0x00000000D83DDC63L, 0x00000000D83DDCADL}, 0x00000000D83DDC8DL, 0x00000000D83DDC8EL, 0x00000000D83DDC64L, 0x00000000D83DDC65L, 0x00000000D83DDCACL, 0x00000000D83DDC63L, 0x00000000D83DDCADL},
new long[]//116 new long[]//116
{0x00000000D83DDC36L, 0x00000000D83DDC3AL, 0x00000000D83DDC31L, 0x00000000D83DDC2DL, 0x00000000D83DDC39L, 0x00000000D83DDC30L, 0x00000000D83DDC38L, 0x00000000D83DDC2FL, {
0x00000000D83DDC36L, 0x00000000D83DDC3AL, 0x00000000D83DDC31L, 0x00000000D83DDC2DL, 0x00000000D83DDC39L, 0x00000000D83DDC30L, 0x00000000D83DDC38L, 0x00000000D83DDC2FL,
0x00000000D83DDC28L, 0x00000000D83DDC3BL, 0x00000000D83DDC37L, 0x00000000D83DDC3DL, 0x00000000D83DDC2EL, 0x00000000D83DDC17L, 0x00000000D83DDC35L, 0x00000000D83DDC28L, 0x00000000D83DDC3BL, 0x00000000D83DDC37L, 0x00000000D83DDC3DL, 0x00000000D83DDC2EL, 0x00000000D83DDC17L, 0x00000000D83DDC35L,
0x00000000D83DDC12L, 0x00000000D83DDC34L, 0x00000000D83DDC11L, 0x00000000D83DDC18L, 0x00000000D83DDC3CL, 0x00000000D83DDC27L, 0x00000000D83DDC26L, 0x00000000D83DDC12L, 0x00000000D83DDC34L, 0x00000000D83DDC11L, 0x00000000D83DDC18L, 0x00000000D83DDC3CL, 0x00000000D83DDC27L, 0x00000000D83DDC26L,
0x00000000D83DDC24L, 0x00000000D83DDC25L, 0x00000000D83DDC23L, 0x00000000D83DDC14L, 0x00000000D83DDC0DL, 0x00000000D83DDC22L, 0x00000000D83DDC1BL, 0x00000000D83DDC24L, 0x00000000D83DDC25L, 0x00000000D83DDC23L, 0x00000000D83DDC14L, 0x00000000D83DDC0DL, 0x00000000D83DDC22L, 0x00000000D83DDC1BL,
@ -110,7 +112,8 @@ public class Emoji {
0x00000000000026C5L, 0x0000000000002601L, 0x00000000000026A1L, 0x0000000000002614L, 0x0000000000002744L, 0x00000000000026C4L, 0x00000000D83CDF00L, 0x00000000000026C5L, 0x0000000000002601L, 0x00000000000026A1L, 0x0000000000002614L, 0x0000000000002744L, 0x00000000000026C4L, 0x00000000D83CDF00L,
0x00000000D83CDF01L, 0x00000000D83CDF08L, 0x00000000D83CDF0AL}, 0x00000000D83CDF01L, 0x00000000D83CDF08L, 0x00000000D83CDF0AL},
new long[]//230 new long[]//230
{0x00000000D83CDF8DL, 0x00000000D83DDC9DL, 0x00000000D83CDF8EL, 0x00000000D83CDF92L, 0x00000000D83CDF93L, 0x00000000D83CDF8FL, 0x00000000D83CDF86L, 0x00000000D83CDF87L, {
0x00000000D83CDF8DL, 0x00000000D83DDC9DL, 0x00000000D83CDF8EL, 0x00000000D83CDF92L, 0x00000000D83CDF93L, 0x00000000D83CDF8FL, 0x00000000D83CDF86L, 0x00000000D83CDF87L,
0x00000000D83CDF90L, 0x00000000D83CDF91L, 0x00000000D83CDF83L, 0x00000000D83DDC7BL, 0x00000000D83CDF85L, 0x00000000D83CDF84L, 0x00000000D83CDF81L, 0x00000000D83CDF90L, 0x00000000D83CDF91L, 0x00000000D83CDF83L, 0x00000000D83DDC7BL, 0x00000000D83CDF85L, 0x00000000D83CDF84L, 0x00000000D83CDF81L,
0x00000000D83CDF8BL, 0x00000000D83CDF89L, 0x00000000D83CDF8AL, 0x00000000D83CDF88L, 0x00000000D83CDF8CL, 0x00000000D83DDD2EL, 0x00000000D83CDFA5L, 0x00000000D83CDF8BL, 0x00000000D83CDF89L, 0x00000000D83CDF8AL, 0x00000000D83CDF88L, 0x00000000D83CDF8CL, 0x00000000D83DDD2EL, 0x00000000D83CDFA5L,
0x00000000D83DDCF7L, 0x00000000D83DDCF9L, 0x00000000D83DDCFCL, 0x00000000D83DDCBFL, 0x00000000D83DDCC0L, 0x00000000D83DDCBDL, 0x00000000D83DDCBEL, 0x00000000D83DDCF7L, 0x00000000D83DDCF9L, 0x00000000D83DDCFCL, 0x00000000D83DDCBFL, 0x00000000D83DDCC0L, 0x00000000D83DDCBDL, 0x00000000D83DDCBEL,
@ -144,7 +147,8 @@ public class Emoji {
0x00000000D83CDF49L, 0x00000000D83CDF53L, 0x00000000D83CDF51L, 0x00000000D83CDF48L, 0x00000000D83CDF4CL, 0x00000000D83CDF50L, 0x00000000D83CDF4DL, 0x00000000D83CDF49L, 0x00000000D83CDF53L, 0x00000000D83CDF51L, 0x00000000D83CDF48L, 0x00000000D83CDF4CL, 0x00000000D83CDF50L, 0x00000000D83CDF4DL,
0x00000000D83CDF60L, 0x00000000D83CDF46L, 0x00000000D83CDF45L, 0x00000000D83CDF3DL}, 0x00000000D83CDF60L, 0x00000000D83CDF46L, 0x00000000D83CDF45L, 0x00000000D83CDF3DL},
new long[]//101 new long[]//101
{0x00000000D83CDFE0L, 0x00000000D83CDFE1L, 0x00000000D83CDFEBL, 0x00000000D83CDFE2L, 0x00000000D83CDFE3L, 0x00000000D83CDFE5L, 0x00000000D83CDFE6L, 0x00000000D83CDFEAL, {
0x00000000D83CDFE0L, 0x00000000D83CDFE1L, 0x00000000D83CDFEBL, 0x00000000D83CDFE2L, 0x00000000D83CDFE3L, 0x00000000D83CDFE5L, 0x00000000D83CDFE6L, 0x00000000D83CDFEAL,
0x00000000D83CDFE9L, 0x00000000D83CDFE8L, 0x00000000D83DDC92L, 0x00000000000026EAL, 0x00000000D83CDFECL, 0x00000000D83CDFE4L, 0x00000000D83CDF07L, 0x00000000D83CDFE9L, 0x00000000D83CDFE8L, 0x00000000D83DDC92L, 0x00000000000026EAL, 0x00000000D83CDFECL, 0x00000000D83CDFE4L, 0x00000000D83CDF07L,
0x00000000D83CDF06L, 0x00000000D83CDFEFL, 0x00000000D83CDFF0L, 0x00000000000026FAL, 0x00000000D83CDFEDL, 0x00000000D83DDDFCL, 0x00000000D83DDDFEL, 0x00000000D83CDF06L, 0x00000000D83CDFEFL, 0x00000000D83CDFF0L, 0x00000000000026FAL, 0x00000000D83CDFEDL, 0x00000000D83DDDFCL, 0x00000000D83DDDFEL,
0x00000000D83DDDFBL, 0x00000000D83CDF04L, 0x00000000D83CDF05L, 0x00000000D83CDF03L, 0x00000000D83DDDFDL, 0x00000000D83CDF09L, 0x00000000D83CDFA0L, 0x00000000D83DDDFBL, 0x00000000D83CDF04L, 0x00000000D83CDF05L, 0x00000000D83CDF03L, 0x00000000D83DDDFDL, 0x00000000D83CDF09L, 0x00000000D83CDFA0L,
@ -160,7 +164,8 @@ public class Emoji {
0xD83CDDF0D83CDDF7L, 0xD83CDDE9D83CDDEAL, 0xD83CDDE8D83CDDF3L, 0xD83CDDFAD83CDDF8L, 0xD83CDDEBD83CDDF7L, 0xD83CDDEAD83CDDF8L, 0xD83CDDEED83CDDF9L, 0xD83CDDF0D83CDDF7L, 0xD83CDDE9D83CDDEAL, 0xD83CDDE8D83CDDF3L, 0xD83CDDFAD83CDDF8L, 0xD83CDDEBD83CDDF7L, 0xD83CDDEAD83CDDF8L, 0xD83CDDEED83CDDF9L,
0xD83CDDF7D83CDDFAL, 0xD83CDDECD83CDDE7L}, 0xD83CDDF7D83CDDFAL, 0xD83CDDECD83CDDE7L},
new long[]//209 new long[]//209
{0x00000000003120E3L, 0x00000000003220E3L, 0x00000000003320E3L, 0x00000000003420E3L, 0x00000000003520E3L, 0x00000000003620E3L, 0x00000000003720E3L, {
0x00000000003120E3L, 0x00000000003220E3L, 0x00000000003320E3L, 0x00000000003420E3L, 0x00000000003520E3L, 0x00000000003620E3L, 0x00000000003720E3L,
0x00000000003820E3L, 0x00000000003920E3L, 0x00000000003020E3L, 0x00000000D83DDD1FL, 0x00000000D83DDD22L, 0x00000000002320E3L, 0x00000000D83DDD23L, 0x00000000003820E3L, 0x00000000003920E3L, 0x00000000003020E3L, 0x00000000D83DDD1FL, 0x00000000D83DDD22L, 0x00000000002320E3L, 0x00000000D83DDD23L,
0x0000000000002B06L, 0x0000000000002B07L, 0x0000000000002B05L, 0x00000000000027A1L, 0x00000000D83DDD20L, 0x00000000D83DDD21L, 0x00000000D83DDD24L, 0x0000000000002B06L, 0x0000000000002B07L, 0x0000000000002B05L, 0x00000000000027A1L, 0x00000000D83DDD20L, 0x00000000D83DDD21L, 0x00000000D83DDD24L,
0x0000000000002197L, 0x0000000000002196L, 0x0000000000002198L, 0x0000000000002199L, 0x0000000000002194L, 0x0000000000002195L, 0x00000000D83DDD04L, 0x0000000000002197L, 0x0000000000002196L, 0x0000000000002198L, 0x0000000000002199L, 0x0000000000002194L, 0x0000000000002195L, 0x00000000D83DDD04L,
@ -213,7 +218,7 @@ public class Emoji {
for (int j = 1; j < data.length; j++) { for (int j = 1; j < data.length; j++) {
for (int i = 0; i < data[j].length; i++) { 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); 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))); rects.put(data[j][i], new DrawableInfo(rect, (byte) (j - 1)));
} }
} }
placeholderPaint = new Paint(); placeholderPaint = new Paint();
@ -290,7 +295,7 @@ public class Emoji {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.emojiDidLoaded); NotificationCenter.getInstance().postNotificationName(NotificationCenter.emojiDidLoaded);
} }
}); });
} catch(Throwable x) { } catch (Throwable x) {
FileLog.e("tmessages", "Error loading emoji", x); FileLog.e("tmessages", "Error loading emoji", x);
} }
} }
@ -310,7 +315,7 @@ public class Emoji {
public static void invalidateAll(View view) { public static void invalidateAll(View view) {
if (view instanceof ViewGroup) { if (view instanceof ViewGroup) {
ViewGroup g = (ViewGroup)view; ViewGroup g = (ViewGroup) view;
for (int i = 0; i < g.getChildCount(); i++) { for (int i = 0; i < g.getChildCount(); i++) {
invalidateAll(g.getChildAt(i)); invalidateAll(g.getChildAt(i));
} }
@ -425,18 +430,22 @@ public class Emoji {
return value == 0xd83cdffb || value == 0xd83cdffc || value == 0xd83cdffd || value == 0xd83cdffe || value == 0xd83cdfff; 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) { if (cs == null || cs.length() == 0) {
return cs; return cs;
} }
//SpannableStringLight.isFieldsAvailable();
//SpannableStringLight s = new SpannableStringLight(cs.toString());
Spannable s; Spannable s;
if (cs instanceof Spannable) { if (!createNew && cs instanceof Spannable) {
s = (Spannable)cs; s = (Spannable) cs;
} else { } else {
s = Spannable.Factory.getInstance().newSpannable(cs); s = Spannable.Factory.getInstance().newSpannable(cs.toString());
} }
long buf = 0; long buf = 0;
int emojiCount = 0; int emojiCount = 0;
//s.setSpansCount(emojiCount);
try { try {
for (int i = 0; i < cs.length(); i++) { for (int i = 0; i < cs.length(); i++) {
char c = cs.charAt(i); char c = cs.charAt(i);
@ -450,12 +459,12 @@ public class Emoji {
if (d != null) { if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i); boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics); EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
if (c >= 0xDDE6 && c <= 0xDDFA) { if (c >= 0xDDE6 && c <= 0xDDFA) {
s.setSpan(span, i - 3, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); s.setSpan(span, i - 3, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else { } else {
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
emojiCount++;
if (nextIsSkinTone) { if (nextIsSkinTone) {
i += 2; i += 2;
} }
@ -472,8 +481,8 @@ public class Emoji {
if (d != null) { if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i); boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics); EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); s.setSpan(span, i - 1, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
emojiCount++;
if (nextIsSkinTone) { if (nextIsSkinTone) {
i += 2; i += 2;
} }
@ -486,8 +495,8 @@ public class Emoji {
if (d != null) { if (d != null) {
boolean nextIsSkinTone = isNextCharIsColor(cs, i); boolean nextIsSkinTone = isNextCharIsColor(cs, i);
EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics); EmojiSpan span = new EmojiSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics);
emojiCount++;
s.setSpan(span, i, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); s.setSpan(span, i, i + (nextIsSkinTone ? 3 : 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
emojiCount++;
if (nextIsSkinTone) { if (nextIsSkinTone) {
i += 2; i += 2;
} }

View file

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

View file

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

View file

@ -313,6 +313,8 @@ public class MessageObject {
} else { } else {
messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker); messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker);
} }
} else if (isMusic()) {
messageText = LocaleController.getString("AttachMusic", R.string.AttachMusic);
} else { } else {
String name = FileLoader.getDocumentFileName(message.media.document); String name = FileLoader.getDocumentFileName(message.media.document);
if (name != null && name.length() > 0) { if (name != null && name.length() > 0) {
@ -327,7 +329,9 @@ public class MessageObject {
} else { } else {
messageText = message.message; 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 (message instanceof TLRPC.TL_message || message instanceof TLRPC.TL_messageForwarded_old2) {
if (isMediaEmpty()) { if (isMediaEmpty()) {
@ -355,6 +359,9 @@ public class MessageObject {
type = 8; type = 8;
} else if (message.media.document.mime_type.equals("image/webp") && isSticker()) { } else if (message.media.document.mime_type.equals("image/webp") && isSticker()) {
type = 13; type = 13;
} else if (isMusic()) {
type = 14;
contentType = 8;
} else { } else {
type = 9; type = 9;
} }
@ -600,7 +607,7 @@ public class MessageObject {
return; return;
} }
if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) { 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)) { if (containsUrls(caption)) {
try { try {
Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS); Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS);
@ -950,6 +957,17 @@ public class MessageObject {
return false; 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) { public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.Message message) {
if (message.media != null && message.media.document != null) { if (message.media != null && message.media.document != null) {
for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) { for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
@ -986,6 +1004,8 @@ public class MessageObject {
return AndroidUtilities.dp(100); return AndroidUtilities.dp(100);
} else if (type == 4) { } else if (type == 4) {
return AndroidUtilities.dp(114); return AndroidUtilities.dp(114);
} else if (type == 14) {
return AndroidUtilities.dp(78);
} else if (type == 13) { } else if (type == 13) {
float maxHeight = AndroidUtilities.displaySize.y * 0.4f; float maxHeight = AndroidUtilities.displaySize.y * 0.4f;
float maxWidth; float maxWidth;
@ -1061,6 +1081,39 @@ public class MessageObject {
return isStickerMessage(messageOwner); 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() { public TLRPC.InputStickerSet getInputStickerSet() {
return getInputStickerSet(messageOwner); 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() || !markAsReadEncrypted.isEmpty()) {
if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty()) { if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty()) {
MessagesStorage.getInstance().updateDialogsWithReadedMessages(markAsReadMessagesInbox, true); MessagesStorage.getInstance().updateDialogsWithReadMessages(markAsReadMessagesInbox, true);
} }
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, 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 { try {
HashMap<Long, Integer> dialogsToUpdate = new HashMap<>(); HashMap<Long, Integer> dialogsToUpdate = new HashMap<>();
StringBuilder dialogsToReload = new StringBuilder(); StringBuilder dialogsToReload = new StringBuilder();
@ -1184,15 +1184,14 @@ 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())); 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()) { if (cursor.next()) {
int count = cursor.intValue(0); int count = cursor.intValue(0);
if (count == 0) { if (count != 0) {
continue;
}
dialogsToUpdate.put((long) entry.getKey(), count); dialogsToUpdate.put((long) entry.getKey(), count);
if (dialogsToReload.length() != 0) { if (dialogsToReload.length() != 0) {
dialogsToReload.append(","); dialogsToReload.append(",");
} }
dialogsToReload.append(entry.getKey()); dialogsToReload.append(entry.getKey());
} }
}
cursor.dispose(); 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()) { if (inbox.isEmpty()) {
return; return;
} }
@ -1239,11 +1238,11 @@ public class MessagesStorage {
storageQueue.postRunnable(new Runnable() { storageQueue.postRunnable(new Runnable() {
@Override @Override
public void run() { public void run() {
updateDialogsWithReadedMessagesInternal(null, inbox); updateDialogsWithReadMessagesInternal(null, inbox);
} }
}); });
} else { } else {
updateDialogsWithReadedMessagesInternal(null, inbox); updateDialogsWithReadMessagesInternal(null, inbox);
} }
} }
@ -3473,7 +3472,7 @@ public class MessagesStorage {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids); NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids);
} }
}); });
MessagesStorage.getInstance().updateDialogsWithReadedMessagesInternal(mids, null); MessagesStorage.getInstance().updateDialogsWithReadMessagesInternal(mids, null);
MessagesStorage.getInstance().markMessagesAsDeletedInternal(mids); MessagesStorage.getInstance().markMessagesAsDeletedInternal(mids);
MessagesStorage.getInstance().updateDialogsWithDeletedMessagesInternal(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 botInfoDidLoaded = totalEvents++;
public static final int botKeyboardDidLoaded = totalEvents++; public static final int botKeyboardDidLoaded = totalEvents++;
public static final int chatSearchResultsAvailable = totalEvents++; public static final int chatSearchResultsAvailable = totalEvents++;
public static final int musicDidLoaded = totalEvents++;
public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++;
@ -89,6 +90,7 @@ public class NotificationCenter {
public static final int audioProgressDidChanged = totalEvents++; public static final int audioProgressDidChanged = totalEvents++;
public static final int audioDidReset = totalEvents++; public static final int audioDidReset = totalEvents++;
public static final int audioPlayStateChanged = totalEvents++;
public static final int recordProgressChanged = totalEvents++; public static final int recordProgressChanged = totalEvents++;
public static final int recordStarted = totalEvents++; public static final int recordStarted = totalEvents++;
public static final int recordStartError = 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.NotificationManagerCompat;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.DispatchQueue; import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.FileLog; import org.telegram.messenger.FileLog;
@ -602,11 +600,8 @@ public class NotificationsController {
} }
String lastMessage = null; String lastMessage = null;
String lastMessageFull = null;
if (pushMessages.size() == 1) { if (pushMessages.size() == 1) {
String message = lastMessageFull = getStringForMessage(pushMessages.get(0), false); String message = lastMessage = getStringForMessage(pushMessages.get(0), false);
//lastMessage = getStringForMessage(pushMessages.get(0), true);
lastMessage = lastMessageFull;
if (message == null) { if (message == null) {
return; return;
} }
@ -630,8 +625,7 @@ public class NotificationsController {
continue; continue;
} }
if (i == 0) { if (i == 0) {
lastMessageFull = message; lastMessage = message;
lastMessage = lastMessageFull;
} }
if (pushDialogs.size() == 1) { if (pushDialogs.size() == 1) {
if (replace) { if (replace) {
@ -692,9 +686,6 @@ public class NotificationsController {
showExtraNotifications(mBuilder, notifyAboutLast); showExtraNotifications(mBuilder, notifyAboutLast);
notificationManager.notify(1, mBuilder.build()); notificationManager.notify(1, mBuilder.build());
if (preferences.getBoolean("EnablePebbleNotifications", false)) {
sendAlertToPebble(lastMessageFull);
}
scheduleNotificationRepeat(); scheduleNotificationRepeat();
} catch (Exception e) { } 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) { public void processReadMessages(HashMap<Integer, Integer> inbox, long dialog_id, int max_date, int max_id, boolean isPopup) {
int oldCount = popupMessages.size(); int oldCount = popupMessages.size();
if (inbox != null) { if (inbox != null) {

View file

@ -19,6 +19,7 @@ import android.provider.MediaStore;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog; import org.telegram.messenger.FileLog;
@ -557,6 +558,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
for (int a = 0; a < messages.size(); a++) { for (int a = 0; a < messages.size(); a++) {
MessageObject msgObj = messages.get(a); MessageObject msgObj = messages.get(a);
if (msgObj.getId() <= 0) {
continue;
}
final TLRPC.Message newMsg = new TLRPC.TL_message(); final TLRPC.Message newMsg = new TLRPC.TL_message();
newMsg.flags |= TLRPC.MESSAGE_FLAG_FWD; newMsg.flags |= TLRPC.MESSAGE_FLAG_FWD;
@ -1858,6 +1862,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
return false; return false;
} }
MimeTypeMap myMime = MimeTypeMap.getSingleton(); MimeTypeMap myMime = MimeTypeMap.getSingleton();
TLRPC.TL_documentAttributeAudio attributeAudio = null;
if (uri != null) { if (uri != null) {
String extension = null; String extension = null;
if (mime != null) { if (mime != null) {
@ -1885,9 +1890,32 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (idx != -1) { if (idx != -1) {
ext = path.substring(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) { if (originalPath != null) {
if (attributeAudio != null) {
originalPath += "audio" + f.length();
} else {
originalPath += "" + f.length(); originalPath += "" + f.length();
} }
}
TLRPC.TL_document document = null; TLRPC.TL_document document = null;
if (!isEncrypted) { if (!isEncrypted) {
@ -1905,6 +1933,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
document.attributes.add(fileName); document.attributes.add(fileName);
document.size = (int) f.length(); document.size = (int) f.length();
document.dc_id = 0; document.dc_id = 0;
if (attributeAudio != null) {
document.attributes.add(attributeAudio);
}
if (ext.length() != 0) { if (ext.length() != 0) {
if (ext.toLowerCase().equals("webp")) { if (ext.toLowerCase().equals("webp")) {
document.mime_type = "image/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); 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) { 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()) { if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) {
return; return;

View file

@ -8,6 +8,7 @@
package org.telegram.android; package org.telegram.android;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.R; import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC; import org.telegram.messenger.TLRPC;
@ -29,7 +30,8 @@ public class UserObject {
if (user == null || isDeleted(user)) { if (user == null || isDeleted(user)) {
return LocaleController.getString("HiddenName", R.string.HiddenName); 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) { public static String getFirstName(TLRPC.User user) {
@ -40,6 +42,6 @@ public class UserObject {
if (name == null || name.length() == 0) { if (name == null || name.length() == 0) {
name = user.last_name; 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. * limitations under the License.
*/ */
package android.support.v7.util; package org.telegram.android.support.util;
import java.lang.reflect.Array; 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 * 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++; 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}. * The class that controls the behavior of the {@link SortedList}.
* <p> * <p>

View file

@ -19,6 +19,9 @@ package org.telegram.android.support.widget;
import android.support.v4.util.Pools; import android.support.v4.util.Pools;
import android.util.Log; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v4.view.ViewPropertyAnimatorListener;
import org.telegram.android.support.widget.RecyclerView.ViewHolder; import org.telegram.android.support.widget.RecyclerView.ViewHolder;
import android.view.View; import android.view.View;

View file

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

View file

@ -60,11 +60,14 @@ class LayoutState {
int mLayoutDirection; int mLayoutDirection;
/** /**
* Used if you want to pre-layout items that are not yet visible. * This is the target pixel closest to the start of the layout that we are trying to fill
* The difference with {@link #mAvailable} is that, when recycling, distance rendered for
* {@link #mExtra} is not considered not to recycle visible children.
*/ */
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 * @return true if there are more items in the data adapter
@ -84,4 +87,16 @@ class LayoutState {
mCurrentPosition += mItemDirection; mCurrentPosition += mItemDirection;
return view; 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.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat; 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.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -33,10 +39,11 @@ import java.util.List;
import static org.telegram.android.support.widget.RecyclerView.NO_POSITION; 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}. * 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"; 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. * Re-used variable to keep anchor information on re-layout.
* Anchor position and coordinate defines the reference point for LLM while doing a 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 * 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. * @param reverseLayout When set to true, layouts from end to start.
*/ */
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
mAnchorInfo = new AnchorInfo();
setOrientation(orientation); setOrientation(orientation);
setReverseLayout(reverseLayout); 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. * 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. * 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 * When set to true, If {@link RecyclerView} is LTR, than it will
* layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout * layout from RTL, if {@link RecyclerView}} is RTL, it will layout
* from LTR. * from LTR.
* *
* If you are looking for the exact same behavior of * 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 firstChild = getPosition(getChildAt(0));
final int viewPosition = position - firstChild; final int viewPosition = position - firstChild;
if (viewPosition >= 0 && viewPosition < childCount) { 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 // override layout from end values for consistency
anchorInfo.mLayoutFromEnd = mShouldReverseLayout; anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
// if this changes, we should update prepareForDrop as well
if (mShouldReverseLayout) { if (mShouldReverseLayout) {
anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() -
mPendingScrollPositionOffset; 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. * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
* <p> * <p>
* Note that scroll position change will not be reflected until the next layout call. * Note that scroll position change will not be reflected until the next layout call.
*
* <p> * <p>
* If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. * 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. * 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 * @param dt This can be used to add additional padding to the visible area. This is used
* to * to detect children that will go out of bounds after scrolling, without
* detect children that will go out of bounds after scrolling, without actually * actually moving them.
* moving them.
*/ */
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
if (dt < 0) { 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. * 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 * @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 * to detect children that will go out of bounds after scrolling, without
* actually moving them. * 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 * @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 * we may consider moving it out of this view so passing around as a
* parameter for now, rather than accessing {@link #mLayoutState} * parameter for now, rather than accessing {@link #mLayoutState}
* @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int) * @see #recycleViewsFromStart(RecyclerView.Recycler, int)
* @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int) * @see #recycleViewsFromEnd(RecyclerView.Recycler, int)
* @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
*/ */
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
@ -1788,6 +1797,40 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; 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 * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space. * space.

View file

@ -16,14 +16,14 @@
package org.telegram.android.support.widget; package org.telegram.android.support.widget;
import java.util.List;
import org.telegram.android.support.widget.AdapterHelper.UpdateOp; 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.ADD;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.MOVE; 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.REMOVE;
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.UPDATE; import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.UPDATE;
import java.util.List;
class OpReorderer { class OpReorderer {
final Callback mCallback; final Callback mCallback;
@ -83,7 +83,7 @@ class OpReorderer {
removeOp.positionStart--; removeOp.positionStart--;
} else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) { } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
// move is removed. // move is removed.
removeOp.itemCount --; removeOp.itemCount--;
moveOp.cmd = REMOVE; moveOp.cmd = REMOVE;
moveOp.itemCount = 1; moveOp.itemCount = 1;
if (removeOp.itemCount == 0) { if (removeOp.itemCount == 0) {
@ -228,7 +228,7 @@ class OpReorderer {
return -1; return -1;
} }
static interface Callback { interface Callback {
UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount); UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount);

View file

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

View file

@ -18,6 +18,7 @@
package org.telegram.android.support.widget; package org.telegram.android.support.widget;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable; import android.database.Observable;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.PointF; import android.graphics.PointF;
@ -30,6 +31,8 @@ import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap; import android.support.v4.util.ArrayMap;
import android.support.v4.view.InputDeviceCompat; import android.support.v4.view.InputDeviceCompat;
import android.support.v4.view.MotionEventCompat; 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.ScrollingView;
import android.support.v4.view.VelocityTrackerCompat; import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat; 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.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.widget.EdgeEffectCompat; import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.ScrollerCompat; 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.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
@ -58,12 +58,15 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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. * A flexible view for providing a limited window into a large data set.
* *
@ -128,8 +131,10 @@ import java.util.List;
* <p> * <p>
* When writing a {@link LayoutManager} you almost always want to use layout positions whereas when * 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. * 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"; 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. * >Try increasing your pool size and item cache size.
*/ */
private static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; 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(); private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
@ -283,6 +290,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
private boolean mAdapterUpdateDuringMeasure; private boolean mAdapterUpdateDuringMeasure;
private final boolean mPostUpdatesOnAnimation; private final boolean mPostUpdatesOnAnimation;
private final AccessibilityManager mAccessibilityManager; private final AccessibilityManager mAccessibilityManager;
private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
/** /**
* Set to true when an adapter data set changed notification is received. * 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(); new ItemAnimatorRestoreListener();
private boolean mPostedAnimatorRunner = false; private boolean mPostedAnimatorRunner = false;
private RecyclerViewAccessibilityDelegate mAccessibilityDelegate; private RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
private ChildDrawingOrderCallback mChildDrawingOrderCallback;
// simple array to keep min and max child position during a layout calculation // simple array to keep min and max child position during a layout calculation
// preserved not to create a new one in each layout pass // preserved not to create a new one in each layout pass
private final int[] mMinMaxLayoutPositions = new int[2]; 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() { private Runnable mItemAnimatorRunner = new Runnable() {
@Override @Override
public void run() { public void run() {
@ -408,6 +422,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
mAccessibilityManager = (AccessibilityManager) getContext() mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE); .getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 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); 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() { private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() { mChildHelper = new ChildHelper(new ChildHelper.Callback() {
@Override @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. * 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) { public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) { if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout"); + "layout");
} }
if (mItemDecorations.isEmpty()) { if (mItemDecorations.isEmpty()) {
setWillNotDraw(false); setWillNotDraw(false);
@ -1035,7 +1159,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
public void removeItemDecoration(ItemDecoration decor) { public void removeItemDecoration(ItemDecoration decor) {
if (mLayout != null) { if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or"
+ " layout"); + "layout");
} }
mItemDecorations.remove(decor); mItemDecorations.remove(decor);
if (mItemDecorations.isEmpty()) { if (mItemDecorations.isEmpty()) {
@ -1045,6 +1169,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
requestLayout(); 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. * 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 canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically(); final boolean canScrollVertical = mLayout.canScrollVertically();
if (canScrollHorizontal || canScrollVertical) { 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 x The amount of horizontal scroll request
* @param y The amount of vertical scroll request * @param y The amount of vertical scroll request
* @param fromMotionEvent If request is originated from a MotionEvent, this should be set to * @param ev The originating MotionEvent, or null if not from a touch event.
* 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.
* *
* @return Whether any scroll was consumed in either direction. * @return Whether any scroll was consumed in either direction.
*/ */
boolean scrollByInternal(int x, int y, boolean fromMotionEvent, int motionX, int motionY) { boolean scrollByInternal(int x, int y, MotionEvent ev) {
int overscrollX = 0, overscrollY = 0; int unconsumedX = 0, unconsumedY = 0;
int hresult = 0, vresult = 0; int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations(); consumePendingUpdateOperations();
if (mAdapter != null) { if (mAdapter != null) {
eatRequestLayout(); eatRequestLayout();
onEnterLayoutOrScroll(); onEnterLayoutOrScroll();
if (x != 0) { if (x != 0) {
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult; unconsumedX = x - consumedX;
} }
if (y != 0) { if (y != 0) {
vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
overscrollY = y - vresult; unconsumedY = y - consumedY;
} }
if (supportsChangeAnimations()) { if (supportsChangeAnimations()) {
// Fix up shadow views used by changing animations // Fix up shadow views used by changing animations
@ -1227,19 +1367,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (!mItemDecorations.isEmpty()) { if (!mItemDecorations.isEmpty()) {
invalidate(); invalidate();
} }
if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (fromMotionEvent) { if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
pullGlows(motionX, overscrollX, motionY, overscrollY); // 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); considerReleasingGlowsOnScroll(x, y);
} }
if (hresult != 0 || vresult != 0) { if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(hresult, vresult); dispatchOnScrolled(consumedX, consumedY);
} }
if (!awakenScrollBars()) { if (!awakenScrollBars()) {
invalidate(); invalidate();
} }
return hresult != 0 || vresult != 0; return consumedX != 0 || consumedY != 0;
} }
/** /**
@ -1432,20 +1580,32 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
"Call setLayoutManager with a non-null argument."); "Call setLayoutManager with a non-null argument.");
return false; return false;
} }
final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically(); final boolean canScrollVertical = mLayout.canScrollVertically();
if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
velocityX = 0; velocityX = 0;
} }
if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
velocityY = 0; velocityY = 0;
} }
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)); velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
if (velocityX != 0 || velocityY != 0) {
mViewFlinger.fling(velocityX, velocityY); mViewFlinger.fling(velocityX, velocityY);
return true; return true;
} }
}
return false; 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 * 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; boolean invalidate = false;
if (overscrollX < 0) { if (overscrollX < 0) {
ensureLeftGlow(); ensureLeftGlow();
invalidate = mLeftGlow.onPull(-overscrollX / (float) getWidth(), if (mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight())) {
1f - y / (float) getHeight()) || invalidate; invalidate = true;
}
} else if (overscrollX > 0) { } else if (overscrollX > 0) {
ensureRightGlow(); ensureRightGlow();
invalidate = mRightGlow.onPull(overscrollX / (float) getWidth(), if (mRightGlow.onPull(overscrollX / getWidth(), y / getHeight())) {
y / (float) getHeight()) || invalidate; invalidate = true;
}
} }
if (overscrollY < 0) { if (overscrollY < 0) {
ensureTopGlow(); ensureTopGlow();
invalidate = mTopGlow.onPull(-overscrollY / (float) getHeight(), if (mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth())) {
x / (float) getWidth()) || invalidate; invalidate = true;
}
} else if (overscrollY > 0) { } else if (overscrollY > 0) {
ensureBottomGlow(); ensureBottomGlow();
invalidate = mBottomGlow.onPull(overscrollY / (float) getHeight(), if (mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth())) {
1f - x / (float) getWidth()) || invalidate; invalidate = true;
}
} }
if (invalidate || overscrollX != 0 || overscrollY != 0) { if (invalidate || overscrollX != 0 || overscrollY != 0) {
@ -1693,6 +1876,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
removeCallbacks(mItemAnimatorRunner); 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 * Checks if RecyclerView is in the middle of a layout or scroll and throws an
* {@link IllegalStateException} if it <b>is not</b>. * {@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> * for each incoming MotionEvent until the end of the gesture.</p>
* *
* @param listener Listener to add * @param listener Listener to add
* @see SimpleOnItemTouchListener
*/ */
public void addOnItemTouchListener(OnItemTouchListener listener) { public void addOnItemTouchListener(OnItemTouchListener listener) {
mOnItemTouchListeners.add(listener); mOnItemTouchListeners.add(listener);
@ -1832,6 +2024,15 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
getParent().requestDisallowInterceptTouchEvent(true); getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING); 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; break;
case MotionEventCompat.ACTION_POINTER_DOWN: case MotionEventCompat.ACTION_POINTER_DOWN:
@ -1874,6 +2075,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
case MotionEvent.ACTION_UP: { case MotionEvent.ACTION_UP: {
mVelocityTracker.clear(); mVelocityTracker.clear();
stopNestedScroll();
} break; } break;
case MotionEvent.ACTION_CANCEL: { case MotionEvent.ACTION_CANCEL: {
@ -1883,6 +2085,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mScrollState == SCROLL_STATE_DRAGGING; 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 @Override
public boolean onTouchEvent(MotionEvent e) { public boolean onTouchEvent(MotionEvent e) {
if (dispatchOnItemTouch(e)) { if (dispatchOnItemTouch(e)) {
@ -1898,14 +2110,29 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
} }
mVelocityTracker.addMovement(e); mVelocityTracker.addMovement(e);
final MotionEvent vtev = MotionEvent.obtain(e);
final int action = MotionEventCompat.getActionMasked(e); final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(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) { switch (action) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
mScrollPointerId = MotionEventCompat.getPointerId(e, 0); mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 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; } break;
case MotionEventCompat.ACTION_POINTER_DOWN: { 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 x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(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) { if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false; boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true; startScroll = true;
} }
if (canScrollVertically && Math.abs(dy) > mTouchSlop) { if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true; startScroll = true;
} }
if (startScroll) { if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING); setScrollState(SCROLL_STATE_DRAGGING);
} }
} }
if (mScrollState == SCROLL_STATE_DRAGGING) { if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX; mLastTouchX = x - mScrollOffset[0];
final int dy = y - mLastTouchY; mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(canScrollHorizontally ? -dx : 0,
canScrollVertically ? -dy : 0, true, x, y)) { if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true); getParent().requestDisallowInterceptTouchEvent(true);
} }
} }
mLastTouchX = x;
mLastTouchY = y;
} break; } break;
case MotionEventCompat.ACTION_POINTER_UP: { 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))) { if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE); setScrollState(SCROLL_STATE_IDLE);
} }
mVelocityTracker.clear(); mVelocityTracker.clear();
releaseGlows(); releaseGlows();
} break; } break;
@ -1974,6 +2222,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
} break; } break;
} }
vtev.recycle();
return true; return true;
} }
@ -1981,6 +2231,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (mVelocityTracker != null) { if (mVelocityTracker != null) {
mVelocityTracker.clear(); mVelocityTracker.clear();
} }
stopNestedScroll();
releaseGlows(); releaseGlows();
setScrollState(SCROLL_STATE_IDLE); setScrollState(SCROLL_STATE_IDLE);
} }
@ -2823,6 +3074,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mLayout.generateLayoutParams(p); 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() { void saveOldPositions() {
final int childCount = mChildHelper.getUnfilteredChildCount(); final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
@ -3232,6 +3495,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return null; 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. * Offset the bounds of all child views by <code>dy</code> pixels.
* Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. * 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) { private void dispatchChildDetached(View child) {
if (mAdapter != null) { final ViewHolder viewHolder = getChildViewHolderInt(child);
mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
}
onChildDetachedFromWindow(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) { private void dispatchChildAttached(View child) {
if (mAdapter != null) { final ViewHolder viewHolder = getChildViewHolderInt(child);
mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child));
}
onChildAttachedToWindow(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 <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 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
* layout managers are provided for general use. * 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 { public static abstract class LayoutManager {
ChildHelper mChildHelper; ChildHelper mChildHelper;
@ -7259,6 +7550,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
int action, Bundle args) { int action, Bundle args) {
return false; 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 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
* a touch interaction already in progress even if the RecyclerView is already handling that * a touch interaction already in progress even if the RecyclerView is already handling that
* gesture stream itself for the purposes of scrolling.</p> * 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 * 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. * 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. * the RecyclerView's coordinate system.
*/ */
public void onTouchEvent(RecyclerView rv, MotionEvent e); 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 * An OnScrollListener can be set on a RecyclerView to receive messages
* when a scrolling event has occurred on that RecyclerView. * 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 * want your components to be able to easily replace the listener use
* RecyclerView#setOnScrollListener. * RecyclerView#setOnScrollListener.
*/ */
abstract static public class OnScrollListener { public abstract static class OnScrollListener {
/** /**
* Callback method to be invoked when RecyclerView's scroll state changes. * 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); 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. * 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); 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 android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
* {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
@ -9318,4 +9731,35 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
this.bottom = bottom; 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 @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper(); ensureOrientationHelper();
final AnchorInfo anchorInfo = mAnchorInfo; final AnchorInfo anchorInfo = mAnchorInfo;
anchorInfo.reset(); anchorInfo.reset();
@ -577,21 +576,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
detachAndScrapAttachedViews(recycler); detachAndScrapAttachedViews(recycler);
mLaidOutInvalidFullSpan = false; mLaidOutInvalidFullSpan = false;
updateMeasureSpecs(); updateMeasureSpecs();
updateLayoutState(anchorInfo.mPosition, state);
if (anchorInfo.mLayoutFromEnd) { if (anchorInfo.mLayoutFromEnd) {
// Layout start. // Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state); setLayoutStateDirection(LAYOUT_START);
fill(recycler, mLayoutState, state); fill(recycler, mLayoutState, state);
// Layout end. // Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state); setLayoutStateDirection(LAYOUT_END);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state); fill(recycler, mLayoutState, state);
} else { } else {
// Layout end. // Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state); setLayoutStateDirection(LAYOUT_END);
fill(recycler, mLayoutState, state); fill(recycler, mLayoutState, state);
// Layout start. // Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state); setLayoutStateDirection(LAYOUT_START);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state); 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.mAvailable = 0;
mLayoutState.mCurrentPosition = anchorPosition; mLayoutState.mCurrentPosition = anchorPosition;
int startExtra = 0;
int endExtra = 0;
if (isSmoothScrolling()) { if (isSmoothScrolling()) {
final int targetPos = state.getTargetScrollPosition(); final int targetPos = state.getTargetScrollPosition();
if (targetPos != NO_POSITION) {
if (mShouldReverseLayout == targetPos < anchorPosition) { if (mShouldReverseLayout == targetPos < anchorPosition) {
mLayoutState.mExtra = 0; endExtra = mPrimaryOrientation.getTotalSpace();
} else { } else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace(); startExtra = mPrimaryOrientation.getTotalSpace();
} }
} else {
mLayoutState.mExtra = 0;
} }
mLayoutState.mLayoutDirection = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
} }
private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) { // Line of the furthest row.
mLayoutState.mAvailable = 0; final boolean clipToPadding = getClipToPadding();
mLayoutState.mCurrentPosition = anchorPosition; if (clipToPadding) {
if (isSmoothScrolling()) { mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
final int targetPos = state.getTargetScrollPosition(); mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
if (mShouldReverseLayout == targetPos > anchorPosition) {
mLayoutState.mExtra = 0;
} else { } else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace(); mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
mLayoutState.mStartLine = -startExtra;
} }
} else {
mLayoutState.mExtra = 0;
} }
mLayoutState.mLayoutDirection = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD private void setLayoutStateDirection(int direction) {
: ITEM_DIRECTION_TAIL; mLayoutState.mLayoutDirection = direction;
mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LAYOUT_START)) ?
ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
} }
@Override @Override
@ -1383,31 +1380,25 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
mRemainingSpans.set(0, mSpanCount, true); mRemainingSpans.set(0, mSpanCount, true);
// The target position we are trying to reach. // The target position we are trying to reach.
final int targetLine; 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. // Line of the furthest row.
if (layoutState.mLayoutDirection == LAYOUT_END) { if (layoutState.mLayoutDirection == LAYOUT_END) {
// ignore padding for recycler targetLine = layoutState.mEndLine + layoutState.mAvailable;
recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();
} else { // LAYOUT_START } else { // LAYOUT_START
// ignore padding for recycler targetLine = layoutState.mStartLine - layoutState.mAvailable;
recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
targetLine = recycleLine - mLayoutState.mExtra -
mPrimaryOrientation.getStartAfterPadding();
} }
updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine); updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
if (DEBUG) {
Log.d(TAG, "FILLING targetLine: " + targetLine + "," +
"remaining spans:" + mRemainingSpans + ", state: " + layoutState);
}
// the default coordinate to add new view. // the default coordinate to add new view.
final int defaultNewViewLine = mShouldReverseLayout final int defaultNewViewLine = mShouldReverseLayout
? mPrimaryOrientation.getEndAfterPadding() ? mPrimaryOrientation.getEndAfterPadding()
: mPrimaryOrientation.getStartAfterPadding(); : mPrimaryOrientation.getStartAfterPadding();
boolean added = false;
while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) { while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
View view = layoutState.next(recycler); View view = layoutState.next(recycler);
LayoutParams lp = ((LayoutParams) view.getLayoutParams()); LayoutParams lp = ((LayoutParams) view.getLayoutParams());
@ -1500,18 +1491,21 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
} else { } else {
updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine); updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
} }
recycle(recycler, mLayoutState, currentSpan, recycleLine); recycle(recycler, mLayoutState);
added = true;
} }
if (DEBUG) { if (!added) {
Log.d(TAG, "fill, " + getChildCount()); recycle(recycler, mLayoutState);
} }
final int diff;
if (mLayoutState.mLayoutDirection == LAYOUT_START) { if (mLayoutState.mLayoutDirection == LAYOUT_START) {
final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding()); final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart)); diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
} else { } else {
final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding()); final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (max - recycleLine)); diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
} }
return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
} }
private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) { private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
@ -1548,19 +1542,40 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
} }
} }
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState, private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
Span updatedSpan, int recycleLine) { 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 {
// scrolling case, recycle line can be shifted by how much space we could cover
// by adding new views
if (layoutState.mLayoutDirection == LAYOUT_START) { if (layoutState.mLayoutDirection == LAYOUT_START) {
// calculate recycle line // calculate recycle line
int maxStart = getMaxStart(updatedSpan.getStartLine()); int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
recycleFromEnd(recycler, Math.max(recycleLine, maxStart) + final int line;
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding())); if (scrolled < 0) {
line = layoutState.mEndLine;
} else {
line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
}
recycleFromEnd(recycler, line);
} else { } else {
// calculate recycle line // calculate recycle line
int minEnd = getMinEnd(updatedSpan.getEndLine()); int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
recycleFromStart(recycler, Math.min(recycleLine, minEnd) - final int line;
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding())); if (scrolled < 0) {
line = layoutState.mStartLine;
} else {
line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
} }
recycleFromStart(recycler, line);
}
}
} }
private void appendViewToAllSpans(View view) { private void appendViewToAllSpans(View view) {
@ -1602,12 +1617,12 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
final int deletedSize = span.getDeletedSize(); final int deletedSize = span.getDeletedSize();
if (layoutDir == LAYOUT_START) { if (layoutDir == LAYOUT_START) {
final int line = span.getStartLine(); final int line = span.getStartLine();
if (line + deletedSize < targetLine) { if (line + deletedSize <= targetLine) {
mRemainingSpans.set(span.mIndex, false); mRemainingSpans.set(span.mIndex, false);
} }
} else { } else {
final int line = span.getEndLine(); final int line = span.getEndLine();
if (line - deletedSize > targetLine) { if (line - deletedSize >= targetLine) {
mRemainingSpans.set(span.mIndex, false); mRemainingSpans.set(span.mIndex, false);
} }
} }
@ -1678,18 +1693,24 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
} }
private void recycleFromStart(RecyclerView.Recycler recycler, int line) { private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
if (DEBUG) {
Log.d(TAG, "recycling from start for line " + line);
}
while (getChildCount() > 0) { while (getChildCount() > 0) {
View child = getChildAt(0); View child = getChildAt(0);
if (mPrimaryOrientation.getDecoratedEnd(child) < line) { if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams(); 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) { if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) { for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popStart(); mSpans[j].popStart();
} }
} else { } else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popStart(); lp.mSpan.popStart();
} }
removeAndRecycleView(child, recycler); removeAndRecycleView(child, recycler);
@ -1704,13 +1725,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int i; int i;
for (i = childCount - 1; i >= 0; i--) { for (i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i); View child = getChildAt(i);
if (mPrimaryOrientation.getDecoratedStart(child) > line) { if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams(); 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) { if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) { for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popEnd(); mSpans[j].popEnd();
} }
} else { } else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popEnd(); lp.mSpan.popEnd();
} }
removeAndRecycleView(child, recycler); removeAndRecycleView(child, recycler);
@ -1860,21 +1890,19 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper(); ensureOrientationHelper();
final int referenceChildPosition; final int referenceChildPosition;
final int layoutDir;
if (dt > 0) { // layout towards end if (dt > 0) { // layout towards end
mLayoutState.mLayoutDirection = LAYOUT_END; layoutDir = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
: ITEM_DIRECTION_TAIL;
referenceChildPosition = getLastChildPosition(); referenceChildPosition = getLastChildPosition();
} else { } else {
mLayoutState.mLayoutDirection = LAYOUT_START; layoutDir = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
referenceChildPosition = getFirstChildPosition(); referenceChildPosition = getFirstChildPosition();
} }
updateLayoutState(referenceChildPosition, state);
setLayoutStateDirection(layoutDir);
mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
final int absDt = Math.abs(dt); final int absDt = Math.abs(dt);
mLayoutState.mAvailable = absDt; mLayoutState.mAvailable = absDt;
mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
int consumed = fill(recycler, mLayoutState, state); int consumed = fill(recycler, mLayoutState, state);
final int totalScroll; final int totalScroll;
if (absDt < consumed) { 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; 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; 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); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
int selectedBackground = preferences.getInt("selectedBackground", 1000001); int selectedBackground = preferences.getInt("selectedBackground", 1000001);
selectedColor = preferences.getInt("selectedColor", 0); selectedColor = preferences.getInt("selectedColor", 0);
int cacheColorHint = 0;
if (selectedColor == 0) { if (selectedColor == 0) {
if (selectedBackground == 1000001) { if (selectedBackground == 1000001) {
cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd); cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd);

View file

@ -363,7 +363,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
try { try {
SerializedData data = new SerializedData(configFile); SerializedData data = new SerializedData(configFile);
isTestBackend = data.readInt32(false); isTestBackend = data.readInt32(false);
int version = data.readInt32(false); data.readInt32(false);
sessionsToDestroy.clear(); sessionsToDestroy.clear();
int count = data.readInt32(false); int count = data.readInt32(false);
for (int a = 0; a < count; a++) { for (int a = 0; a < count; a++) {
@ -2700,7 +2700,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection.
return; return;
} }
int messageLength = data.readInt32(false); data.readInt32(false);
TLObject message = deserialize(getRequestWithMessageId(messageId), data, true); 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 stateFinished = 3;
private final static int downloadChunkSize = 1024 * 32; private final static int downloadChunkSize = 1024 * 32;
private final static int downloadChunkSizeBig = 1024 * 128;
private final static int maxDownloadRequests = 3; private final static int maxDownloadRequests = 3;
private int datacenter_id; private int datacenter_id;
@ -38,6 +39,7 @@ public class FileLoadOperation {
private FileLoadOperationDelegate delegate; private FileLoadOperationDelegate delegate;
private byte[] key; private byte[] key;
private byte[] iv; private byte[] iv;
private int currentDownloadChunkSize;
private int nextDownloadOffset = 0; private int nextDownloadOffset = 0;
private ArrayList<RequestInfo> requestInfos = new ArrayList<>(maxDownloadRequests); private ArrayList<RequestInfo> requestInfos = new ArrayList<>(maxDownloadRequests);
@ -165,6 +167,7 @@ public class FileLoadOperation {
if (state != stateIdle) { if (state != stateIdle) {
return; return;
} }
currentDownloadChunkSize = totalBytesCount >= 1024 * 1024 * 30 ? downloadChunkSizeBig : downloadChunkSize;
state = stateDownloading; state = stateDownloading;
if (location == null) { if (location == null) {
Utilities.stageQueue.postRunnable(new Runnable() { Utilities.stageQueue.postRunnable(new Runnable() {
@ -175,7 +178,6 @@ public class FileLoadOperation {
}); });
return; return;
} }
Long mediaId = null;
String fileNameFinal; String fileNameFinal;
String fileNameTemp; String fileNameTemp;
String fileNameIv = null; String fileNameIv = null;
@ -223,7 +225,7 @@ public class FileLoadOperation {
cacheFileTemp = new File(tempPath, fileNameTemp); cacheFileTemp = new File(tempPath, fileNameTemp);
if (cacheFileTemp.exists()) { if (cacheFileTemp.exists()) {
downloadedBytes = (int)cacheFileTemp.length(); downloadedBytes = (int)cacheFileTemp.length();
nextDownloadOffset = downloadedBytes = downloadedBytes / 1024 * 1024; nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize;
} }
if (fileNameIv != null) { if (fileNameIv != null) {
cacheIvTemp = new File(tempPath, fileNameIv); cacheIvTemp = new File(tempPath, fileNameIv);
@ -388,10 +390,10 @@ public class FileLoadOperation {
} }
} }
if (currentBytesSize != downloadChunkSize) { if (currentBytesSize != currentDownloadChunkSize) {
onFinishLoadingFile(); onFinishLoadingFile();
} else { } else {
if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) { if (totalBytesCount != downloadedBytes && downloadedBytes % currentDownloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
startDownloadRequest(); startDownloadRequest();
} else { } else {
onFinishLoadingFile(); onFinishLoadingFile();
@ -422,7 +424,7 @@ public class FileLoadOperation {
startDownloadRequest(); startDownloadRequest();
} }
} else if (error.text.contains("OFFSET_INVALID")) { } else if (error.text.contains("OFFSET_INVALID")) {
if (downloadedBytes % downloadChunkSize == 0) { if (downloadedBytes % currentDownloadChunkSize == 0) {
try { try {
onFinishLoadingFile(); onFinishLoadingFile();
} catch (Exception e) { } catch (Exception e) {
@ -460,12 +462,12 @@ public class FileLoadOperation {
if (totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount) { if (totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount) {
break; 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(); TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
req.location = location; req.location = location;
req.offset = nextDownloadOffset; req.offset = nextDownloadOffset;
req.limit = downloadChunkSize; req.limit = currentDownloadChunkSize;
nextDownloadOffset += downloadChunkSize; nextDownloadOffset += currentDownloadChunkSize;
final RequestInfo requestInfo = new RequestInfo(); final RequestInfo requestInfo = new RequestInfo();
requestInfos.add(requestInfo); requestInfos.add(requestInfo);

View file

@ -675,6 +675,15 @@ public class FileLoader {
return closestObject; 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) { public static String getDocumentFileName(TLRPC.Document document) {
if (document != null) { if (document != null) {
if (document.file_name != 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)); FileLog.d("tmessages", String.format("===== Duplicate message id %d received, ignoring", messageId));
return; return;
} }
int messageLength = data.readInt32(false); data.readInt32(false);
int constructor = data.readInt32(false); int constructor = data.readInt32(false);
TLObject object = TLClassStore.Instance().TLdeserialize(data, constructor, false); TLObject object = TLClassStore.Instance().TLdeserialize(data, constructor, false);

View file

@ -11634,6 +11634,9 @@ public class TLRPC {
case 0x9eddf188: case 0x9eddf188:
result = new TL_inputMessagesFilterDocument(); result = new TL_inputMessagesFilterDocument();
break; break;
case 0x5afbf764:
result = new TL_inputMessagesFilterAudioDocuments();
break;
case 0x9fc00e65: case 0x9fc00e65:
result = new TL_inputMessagesFilterVideo(); result = new TL_inputMessagesFilterVideo();
break; 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 class TL_inputMessagesFilterVideo extends MessagesFilter {
public static int constructor = 0x9fc00e65; public static int constructor = 0x9fc00e65;

View file

@ -143,7 +143,7 @@ public class TcpConnection extends ConnectionContext {
FileLog.e("tmessages", e2); 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; firstPacket = true;
if (restOfTheData != null) { if (restOfTheData != null) {
BuffersStorage.getInstance().reuseFreeBuffer(restOfTheData); BuffersStorage.getInstance().reuseFreeBuffer(restOfTheData);

View file

@ -15,7 +15,6 @@ import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -23,6 +22,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.telegram.android.AndroidUtilities; import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.R; import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LayoutHelper;
@ -38,7 +38,6 @@ public class ActionBar extends FrameLayout {
} }
} }
private FrameLayout titleFrameLayout;
private ImageView backButtonImageView; private ImageView backButtonImageView;
private TextView titleTextView; private TextView titleTextView;
private TextView subTitleTextView; private TextView subTitleTextView;
@ -60,116 +59,6 @@ public class ActionBar extends FrameLayout {
public ActionBar(Context context) { public ActionBar(Context context) {
super(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() { private void createBackButtonImage() {
@ -177,9 +66,10 @@ public class ActionBar extends FrameLayout {
return; return;
} }
backButtonImageView = new ImageView(getContext()); backButtonImageView = new ImageView(getContext());
titleFrameLayout.addView(backButtonImageView);
backButtonImageView.setScaleType(ImageView.ScaleType.CENTER); backButtonImageView.setScaleType(ImageView.ScaleType.CENTER);
backButtonImageView.setBackgroundResource(itemsBackgroundResourceId); backButtonImageView.setBackgroundResource(itemsBackgroundResourceId);
addView(backButtonImageView, LayoutHelper.createFrame(54, 54, Gravity.LEFT | Gravity.TOP));
backButtonImageView.setOnClickListener(new OnClickListener() { backButtonImageView.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -195,31 +85,19 @@ public class ActionBar extends FrameLayout {
} }
public void setBackButtonDrawable(Drawable drawable) { public void setBackButtonDrawable(Drawable drawable) {
boolean reposition = false;
if (backButtonImageView == null) { if (backButtonImageView == null) {
createBackButtonImage(); createBackButtonImage();
} else {
reposition = true;
} }
backButtonImageView.setVisibility(drawable == null ? GONE : VISIBLE); backButtonImageView.setVisibility(drawable == null ? GONE : VISIBLE);
backButtonImageView.setImageDrawable(drawable); backButtonImageView.setImageDrawable(drawable);
if (reposition) {
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
} }
public void setBackButtonImage(int resource) { public void setBackButtonImage(int resource) {
boolean reposition = false;
if (backButtonImageView == null) { if (backButtonImageView == null) {
createBackButtonImage(); createBackButtonImage();
} else {
reposition = true;
} }
backButtonImageView.setVisibility(resource == 0 ? GONE : VISIBLE); backButtonImageView.setVisibility(resource == 0 ? GONE : VISIBLE);
backButtonImageView.setImageResource(resource); backButtonImageView.setImageResource(resource);
if (reposition) {
positionTitle(getMeasuredWidth(), getMeasuredHeight());
}
} }
private void createSubtitleTextView() { private void createSubtitleTextView() {
@ -227,13 +105,13 @@ public class ActionBar extends FrameLayout {
return; return;
} }
subTitleTextView = new TextView(getContext()); subTitleTextView = new TextView(getContext());
titleFrameLayout.addView(subTitleTextView);
subTitleTextView.setGravity(Gravity.LEFT); subTitleTextView.setGravity(Gravity.LEFT);
subTitleTextView.setTextColor(0xffd7e8f7); subTitleTextView.setTextColor(0xffd7e8f7);
subTitleTextView.setSingleLine(true); subTitleTextView.setSingleLine(true);
subTitleTextView.setLines(1); subTitleTextView.setLines(1);
subTitleTextView.setMaxLines(1); subTitleTextView.setMaxLines(1);
subTitleTextView.setEllipsize(TextUtils.TruncateAt.END); subTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
addView(subTitleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP));
} }
public void setSubtitle(CharSequence value) { public void setSubtitle(CharSequence value) {
@ -243,22 +121,6 @@ public class ActionBar extends FrameLayout {
if (subTitleTextView != null) { if (subTitleTextView != null) {
subTitleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE); subTitleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
subTitleTextView.setText(value); 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 = new TextView(getContext());
titleTextView.setGravity(Gravity.LEFT); titleTextView.setGravity(Gravity.LEFT);
titleTextView.setSingleLine(true);
titleTextView.setLines(1); titleTextView.setLines(1);
titleTextView.setMaxLines(1); titleTextView.setMaxLines(1);
titleTextView.setSingleLine(true);
titleTextView.setEllipsize(TextUtils.TruncateAt.END); titleTextView.setEllipsize(TextUtils.TruncateAt.END);
titleFrameLayout.addView(titleTextView);
titleTextView.setTextColor(0xffffffff); titleTextView.setTextColor(0xffffffff);
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); 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) { public void setTitle(CharSequence value) {
boolean created = false;
if (value != null && titleTextView == null) { if (value != null && titleTextView == null) {
createTitleTextView(); createTitleTextView();
created = true;
} }
if (titleTextView != null) { if (titleTextView != null) {
lastTitle = value; lastTitle = value;
titleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE); titleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
titleTextView.setText(value); titleTextView.setText(value);
positionTitle(getMeasuredWidth(), getMeasuredHeight());
if (!created) {
titleTextView.setText(value);
}
} }
} }
public void setTitleIcon(int resourceId, int padding) { public TextView getSubTitleTextView() {
if (resourceId != 0 && titleTextView == null) { return subTitleTextView;
createTitleTextView();
positionTitle(getMeasuredWidth(), getMeasuredHeight());
} }
titleTextView.setCompoundDrawablesWithIntrinsicBounds(resourceId, 0, 0, 0);
titleTextView.setCompoundDrawablePadding(padding); public TextView getTitleTextView() {
return titleTextView;
} }
public Drawable getSubTitleIcon() { public Drawable getSubTitleIcon() {
@ -332,17 +187,6 @@ public class ActionBar extends FrameLayout {
actionBarMenuOnItemClick = listener; 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() { public ActionBarMenu createActionMode() {
if (actionMode != null) { if (actionMode != null) {
return actionMode; return actionMode;
@ -358,7 +202,7 @@ public class ActionBar extends FrameLayout {
actionMode.setLayoutParams(layoutParams); actionMode.setLayoutParams(layoutParams);
actionMode.setVisibility(INVISIBLE); actionMode.setVisibility(INVISIBLE);
if (occupyStatusBar) { if (occupyStatusBar && actionModeTop == null) {
actionModeTop = new View(getContext()); actionModeTop = new View(getContext());
actionModeTop.setBackgroundColor(0x99000000); actionModeTop.setBackgroundColor(0x99000000);
addView(actionModeTop); addView(actionModeTop);
@ -381,8 +225,14 @@ public class ActionBar extends FrameLayout {
if (occupyStatusBar && actionModeTop != null) { if (occupyStatusBar && actionModeTop != null) {
actionModeTop.setVisibility(VISIBLE); actionModeTop.setVisibility(VISIBLE);
} }
if (titleFrameLayout != null) { if (titleTextView != null) {
titleFrameLayout.setVisibility(INVISIBLE); titleTextView.setVisibility(INVISIBLE);
}
if (subTitleTextView != null) {
subTitleTextView.setVisibility(INVISIBLE);
}
if (backButtonImageView != null) {
backButtonImageView.setVisibility(INVISIBLE);
} }
if (menu != null) { if (menu != null) {
menu.setVisibility(INVISIBLE); menu.setVisibility(INVISIBLE);
@ -397,14 +247,33 @@ public class ActionBar extends FrameLayout {
if (occupyStatusBar && actionModeTop != null) { if (occupyStatusBar && actionModeTop != null) {
actionModeTop.setVisibility(INVISIBLE); actionModeTop.setVisibility(INVISIBLE);
} }
if (titleFrameLayout != null) { if (titleTextView != null) {
titleFrameLayout.setVisibility(VISIBLE); titleTextView.setVisibility(VISIBLE);
}
if (subTitleTextView != null) {
subTitleTextView.setVisibility(VISIBLE);
}
if (backButtonImageView != null) {
backButtonImageView.setVisibility(VISIBLE);
} }
if (menu != null) { if (menu != null) {
menu.setVisibility(VISIBLE); 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() { public boolean isActionModeShowed() {
return actionMode != null && actionMode.getVisibility() == VISIBLE; return actionMode != null && actionMode.getVisibility() == VISIBLE;
} }
@ -439,12 +308,136 @@ public class ActionBar extends FrameLayout {
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int actionBarHeight = AndroidUtilities.getCurrentActionBarHeight(); int width = MeasureSpec.getSize(widthMeasureSpec);
positionBackImage(actionBarHeight); int height = MeasureSpec.getSize(heightMeasureSpec);
positionMenu(MeasureSpec.getSize(widthMeasureSpec), actionBarHeight); int actionBarHeight = getCurrentActionBarHeight();
positionTitle(MeasureSpec.getSize(widthMeasureSpec), actionBarHeight); int actionBarHeightSpec = MeasureSpec.makeMeasureSpec(actionBarHeight, MeasureSpec.EXACTLY);
actionBarHeight += occupyStatusBar ? AndroidUtilities.statusBarHeight : 0;
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(actionBarHeight + extraHeight, 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() { public void onMenuButtonPressed() {
@ -474,10 +467,13 @@ public class ActionBar extends FrameLayout {
if (titleTextView != null) { if (titleTextView != null) {
titleTextView.setVisibility(textToSet != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE); titleTextView.setVisibility(textToSet != null && !isSearchFieldVisible ? VISIBLE : INVISIBLE);
titleTextView.setText(textToSet); titleTextView.setText(textToSet);
positionTitle(getMeasuredWidth(), getMeasuredHeight());
} }
} }
public boolean isSearchFieldVisible() {
return isSearchFieldVisible;
}
public void setExtraHeight(int value, boolean layout) { public void setExtraHeight(int value, boolean layout) {
extraHeight = value; extraHeight = value;
if (layout) { if (layout) {
@ -520,4 +516,14 @@ public class ActionBar extends FrameLayout {
super.onTouchEvent(event); super.onTouchEvent(event);
return true; 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) { if (child instanceof ActionBar) {
return super.drawChild(canvas, child, drawingTime); return super.drawChild(canvas, child, drawingTime);
} else { } else {
boolean wasActionBar = false; //boolean wasActionBar = false;
int actionBarHeight = 0; int actionBarHeight = 0;
int childCount = getChildCount(); int childCount = getChildCount();
for (int a = 0; a < childCount; a++) { for (int a = 0; a < childCount; a++) {
@ -339,7 +339,7 @@ public class ActionBarLayout extends FrameLayout {
BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 2); BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 2);
View fragmentView = lastFragment.fragmentView; View fragmentView = lastFragment.fragmentView;
if (fragmentView == null) { if (fragmentView == null) {
fragmentView = lastFragment.createView(parentActivity, parentActivity.getLayoutInflater()); fragmentView = lastFragment.createView(parentActivity);
} else { } else {
ViewGroup parent = (ViewGroup) fragmentView.getParent(); ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) { if (parent != null) {
@ -625,7 +625,7 @@ public class ActionBarLayout extends FrameLayout {
fragment.setParentLayout(this); fragment.setParentLayout(this);
View fragmentView = fragment.fragmentView; View fragmentView = fragment.fragmentView;
if (fragmentView == null) { if (fragmentView == null) {
fragmentView = fragment.createView(parentActivity, parentActivity.getLayoutInflater()); fragmentView = fragment.createView(parentActivity);
} else { } else {
ViewGroup parent = (ViewGroup) fragmentView.getParent(); ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) { if (parent != null) {
@ -829,7 +829,7 @@ public class ActionBarLayout extends FrameLayout {
previousFragment.setParentLayout(this); previousFragment.setParentLayout(this);
View fragmentView = previousFragment.fragmentView; View fragmentView = previousFragment.fragmentView;
if (fragmentView == null) { if (fragmentView == null) {
fragmentView = previousFragment.createView(parentActivity, parentActivity.getLayoutInflater()); fragmentView = previousFragment.createView(parentActivity);
} else { } else {
ViewGroup parent = (ViewGroup) fragmentView.getParent(); ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) { if (parent != null) {
@ -972,7 +972,7 @@ public class ActionBarLayout extends FrameLayout {
previousFragment.setParentLayout(this); previousFragment.setParentLayout(this);
View fragmentView = previousFragment.fragmentView; View fragmentView = previousFragment.fragmentView;
if (fragmentView == null) { if (fragmentView == null) {
fragmentView = previousFragment.createView(parentActivity, parentActivity.getLayoutInflater()); fragmentView = previousFragment.createView(parentActivity);
} else { } else {
ViewGroup parent = (ViewGroup) fragmentView.getParent(); ViewGroup parent = (ViewGroup) fragmentView.getParent();
if (parent != null) { if (parent != null) {

View file

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

View file

@ -82,6 +82,8 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
private int subMenuOpenSide = 0; private int subMenuOpenSide = 0;
private ActionBarMenuItemDelegate delegate; private ActionBarMenuItemDelegate delegate;
private boolean allowCloseAnimation = true; private boolean allowCloseAnimation = true;
protected boolean overrideMenuClick;
private boolean processedPopupClick;
public ActionBarMenuItem(Context context, ActionBarMenu menu, int background) { public ActionBarMenuItem(Context context, ActionBarMenu menu, int background) {
super(context); super(context);
@ -129,21 +131,23 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
x -= location[0]; x -= location[0];
y -= location[1]; y -= location[1];
selectedMenuView = null; selectedMenuView = null;
for (int a = 0; a < popupLayout.getChildCount(); a++) { for (int a = 0; a < popupLayout.getItemsCount(); a++) {
View child = popupLayout.getChildAt(a); View child = popupLayout.getItemAt(a);
child.getHitRect(rect); child.getHitRect(rect);
if ((Integer) child.getTag() < 100) { if ((Integer) child.getTag() < 100) {
if (!rect.contains((int) x, (int) y)) { if (!rect.contains((int) x, (int) y)) {
child.setPressed(false); child.setPressed(false);
child.setSelected(false); child.setSelected(false);
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT == 21) {
child.getBackground().setVisible(false, false); child.getBackground().setVisible(false, false);
} }
} else { } else {
child.setPressed(true); child.setPressed(true);
child.setSelected(true); child.setSelected(true);
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
if (Build.VERSION.SDK_INT == 21) {
child.getBackground().setVisible(true, false); child.getBackground().setVisible(true, false);
}
child.drawableHotspotChanged(x, y - child.getTop()); child.drawableHotspotChanged(x, y - child.getTop());
} }
selectedMenuView = child; selectedMenuView = child;
@ -192,9 +196,6 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
rect = new Rect(); rect = new Rect();
location = new int[2]; location = new int[2];
popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext()); 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() { popupLayout.setOnTouchListener(new OnTouchListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
@ -252,6 +253,10 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (popupWindow != null && popupWindow.isShowing()) { if (popupWindow != null && popupWindow.isShowing()) {
if (processedPopupClick) {
return;
}
processedPopupClick = true;
popupWindow.dismiss(allowCloseAnimation); popupWindow.dismiss(allowCloseAnimation);
} }
if (parentMenu != null) { if (parentMenu != null) {
@ -306,6 +311,7 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
} }
}); });
} }
processedPopupClick = false;
popupWindow.setFocusable(true); popupWindow.setFocusable(true);
if (popupLayout.getMeasuredWidth() == 0) { if (popupLayout.getMeasuredWidth() == 0) {
updateOrShowPopup(true, true); updateOrShowPopup(true, true);
@ -367,6 +373,11 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
return setIsSearchField(value, true); return setIsSearchField(value, true);
} }
public ActionBarMenuItem setOverrideMenuClick(boolean value) {
overrideMenuClick = value;
return this;
}
public ActionBarMenuItem setIsSearchField(boolean value, boolean needClearButton) { public ActionBarMenuItem setIsSearchField(boolean value, boolean needClearButton) {
if (parentMenu == null) { if (parentMenu == null) {
return this; return this;
@ -389,7 +400,8 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
searchField.setSingleLine(true); searchField.setSingleLine(true);
searchField.setBackgroundResource(0); searchField.setBackgroundResource(0);
searchField.setPadding(0, 0, 0, 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) { if (android.os.Build.VERSION.SDK_INT < 11) {
searchField.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { searchField.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { 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.width = LayoutHelper.MATCH_PARENT;
layoutParams2.gravity = Gravity.CENTER_VERTICAL; layoutParams2.gravity = Gravity.CENTER_VERTICAL;
layoutParams2.height = AndroidUtilities.dp(36); layoutParams2.height = AndroidUtilities.dp(36);
layoutParams2.rightMargin = AndroidUtilities.dp(48); layoutParams2.rightMargin = needClearButton ? AndroidUtilities.dp(48) : 0;
searchField.setLayoutParams(layoutParams2); searchField.setLayoutParams(layoutParams2);
if (needClearButton) { if (needClearButton) {
@ -532,6 +544,10 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
} }
} }
if (show) {
popupLayout.scrollToTop();
}
if (subMenuOpenSide == 0) { if (subMenuOpenSide == 0) {
if (showFromBottom) { if (showFromBottom) {
if (show) { if (show) {
@ -574,10 +590,6 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
if (view != null) { if (view != null) {
view.setVisibility(GONE); view.setVisibility(GONE);
} }
view = popupLayout.findViewWithTag(100 + id);
if (view != null) {
view.setVisibility(GONE);
}
} }
public void showSubItem(int id) { public void showSubItem(int id) {
@ -585,9 +597,5 @@ public class ActionBarMenuItem extends FrameLayoutFixed {
if (view != null) { if (view != null) {
view.setVisibility(VISIBLE); 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.os.Build;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import android.widget.ScrollView;
import org.telegram.android.AndroidUtilities; import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.FileLog; import org.telegram.messenger.FileLog;
import org.telegram.messenger.R; import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
@ -62,7 +66,7 @@ public class ActionBarPopupWindow extends PopupWindow {
void onDispatchKeyEvent(KeyEvent keyEvent); void onDispatchKeyEvent(KeyEvent keyEvent);
} }
public static class ActionBarPopupWindowLayout extends LinearLayout { public static class ActionBarPopupWindowLayout extends FrameLayout {
private OnDispatchKeyEventListener mOnDispatchKeyEventListener; private OnDispatchKeyEventListener mOnDispatchKeyEventListener;
protected static Drawable backgroundDrawable; protected static Drawable backgroundDrawable;
@ -73,13 +77,26 @@ public class ActionBarPopupWindow extends PopupWindow {
private boolean showedFromBotton; private boolean showedFromBotton;
private HashMap<View, Integer> positions = new HashMap<>(); private HashMap<View, Integer> positions = new HashMap<>();
private ScrollView scrollView;
private LinearLayout linearLayout;
public ActionBarPopupWindowLayout(Context context) { public ActionBarPopupWindowLayout(Context context) {
super(context); super(context);
setWillNotDraw(false);
if (backgroundDrawable == null) { if (backgroundDrawable == null) {
backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed); 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) { public void setShowedFromBotton(boolean value) {
@ -106,15 +123,15 @@ public class ActionBarPopupWindow extends PopupWindow {
public void setBackScaleY(float value) { public void setBackScaleY(float value) {
backScaleY = value; backScaleY = value;
if (animationEnabled) { if (animationEnabled) {
int count = getChildCount(); int count = getItemsCount();
int visibleCount = 0; int visibleCount = 0;
for (int a = 0; a < count; a++) { 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); int height = getMeasuredHeight() - AndroidUtilities.dp(16);
if (showedFromBotton) { if (showedFromBotton) {
for (int a = lastStartedChild; a >= 0; a--) { for (int a = lastStartedChild; a >= 0; a--) {
View child = getChildAt(a); View child = getItemAt(a);
if (child.getVisibility() != VISIBLE) { if (child.getVisibility() != VISIBLE) {
continue; continue;
} }
@ -127,7 +144,7 @@ public class ActionBarPopupWindow extends PopupWindow {
} }
} else { } else {
for (int a = lastStartedChild; a < count; a++) { for (int a = lastStartedChild; a < count; a++) {
View child = getChildAt(a); View child = getItemAt(a);
if (child.getVisibility() != VISIBLE) { if (child.getVisibility() != VISIBLE) {
continue; continue;
} }
@ -155,6 +172,11 @@ public class ActionBarPopupWindow extends PopupWindow {
} }
} }
@Override
public void addView(View child) {
linearLayout.addView(child);
}
public float getBackScaleX() { public float getBackScaleX() {
return backScaleX; return backScaleX;
} }
@ -183,6 +205,18 @@ public class ActionBarPopupWindow extends PopupWindow {
backgroundDrawable.draw(canvas); 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() { public ActionBarPopupWindow() {
@ -269,11 +303,11 @@ public class ActionBarPopupWindow extends PopupWindow {
content.setAlpha(1.0f); content.setAlpha(1.0f);
content.setPivotX(content.getMeasuredWidth()); content.setPivotX(content.getMeasuredWidth());
content.setPivotY(0); content.setPivotY(0);
int count = content.getChildCount(); int count = content.getItemsCount();
content.positions.clear(); content.positions.clear();
int visibleCount = 0; int visibleCount = 0;
for (int a = 0; a < count; a++) { for (int a = 0; a < count; a++) {
View child = content.getChildAt(a); View child = content.getItemAt(a);
if (child.getVisibility() != View.VISIBLE) { if (child.getVisibility() != View.VISIBLE) {
continue; continue;
} }

View file

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

View file

@ -9,10 +9,18 @@
package org.telegram.ui.ActionBar; package org.telegram.ui.ActionBar;
import android.animation.Animator; 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.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; 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.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
@ -45,7 +53,7 @@ import java.util.ArrayList;
public class BottomSheet extends Dialog { public class BottomSheet extends Dialog {
private LinearLayout linearLayout; private LinearLayout containerView;
private FrameLayout container; private FrameLayout container;
private boolean dismissed; private boolean dismissed;
@ -57,24 +65,73 @@ public class BottomSheet extends Dialog {
private int[] itemIcons; private int[] itemIcons;
private View customView; private View customView;
private CharSequence title; private CharSequence title;
private boolean overrideTabletWidth = true; private boolean fullWidth;
private boolean isGrid; private boolean isGrid;
private ColorDrawable backgroundDrawable = new ColorDrawable(0xff000000); 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 revealX;
private int revealY; private int revealY;
private boolean useRevealAnimation; private boolean applyTopPaddings = true;
private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();
private AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator(); private AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator();
private ArrayList<BottomSheetCell> itemViews = new ArrayList<>(); private ArrayList<BottomSheetCell> itemViews = new ArrayList<>();
private BottomSheetDelegate delegate; private BottomSheetDelegateInterface delegate;
public interface BottomSheetDelegate { public interface BottomSheetDelegateInterface {
void onOpenAnimationStart(); void onOpenAnimationStart();
void onOpenAnimationEnd(); 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 { private static class BottomSheetCell extends FrameLayout {
@ -139,7 +196,40 @@ public class BottomSheet extends Dialog {
public BottomSheet(Context context) { public BottomSheet(Context context) {
super(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() { container.setOnTouchListener(new View.OnTouchListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
@ -159,25 +249,37 @@ public class BottomSheet extends Dialog {
window.requestFeature(Window.FEATURE_NO_TITLE); window.requestFeature(Window.FEATURE_NO_TITLE);
window.setWindowAnimations(R.style.DialogNoAnimation); window.setWindowAnimations(R.style.DialogNoAnimation);
setContentView(container); if (shadowDrawable == null) {
Rect padding = new Rect();
linearLayout = new LinearLayout(getContext()); shadowDrawable = getContext().getResources().getDrawable(R.drawable.sheet_shadow);
linearLayout.setOrientation(LinearLayout.VERTICAL); shadowDrawable.getPadding(padding);
if (AndroidUtilities.isTablet() && !overrideTabletWidth) { backgroundPaddingLeft = padding.left;
container.addView(linearLayout, 0, LayoutHelper.createFrame(320, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL)); backgroundPaddingTop = padding.top;
} else {
container.addView(linearLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM));
} }
View shadow = new View(getContext()); setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
shadow.setBackgroundResource(R.drawable.header_shadow_reverse);
linearLayout.addView(shadow, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 3));
LinearLayout containerView = new LinearLayout(getContext()); ciclePaint.setColor(0xffffffff);
containerView.setBackgroundColor(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.setOrientation(LinearLayout.VERTICAL);
containerView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(isGrid ? 16 : 8)); container.addView(containerView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM));
linearLayout.addView(containerView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
if (title != null) { if (title != null) {
TextView titleView = new TextView(getContext()); TextView titleView = new TextView(getContext());
@ -273,43 +375,162 @@ public class BottomSheet extends Dialog {
setOnShowListener(new OnShowListener() { setOnShowListener(new OnShowListener() {
@Override @Override
public void onShow(DialogInterface dialog) { public void onShow(DialogInterface dialog) {
if (useRevealAnimation) { if (Build.VERSION.SDK_INT >= 21) {
int finalRadius = Math.max(AndroidUtilities.displaySize.x, container.getHeight()); startOpenAnimation();
Animator anim = ViewAnimationUtils.createCircularReveal(container, revealX, revealY, 0, finalRadius); }
anim.setDuration(400); }
anim.addListener(new Animator.AnimatorListener() { });
}
@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);
}
}
}
animatorSet.playTogether(animators);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationStart(Animator animation) { public void onAnimationStart(Animator animation) {
if (delegate != null) { if (delegate != null) {
delegate.onOpenAnimationStart(); delegate.onRevealAnimationStart(open);
} }
} }
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (delegate != null) { if (delegate != null) {
delegate.onOpenAnimationEnd(); delegate.onRevealAnimationEnd(open);
}
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 @Override
public void onAnimationCancel(Animator animation) { public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
} }
}); });
anim.start(); 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 { } else {
//startLayoutAnimation(true, true); ViewProxy.setTranslationY(containerView, containerView.getMeasuredHeight());
ViewProxy.setTranslationY(linearLayout, linearLayout.getHeight());
backgroundDrawable.setAlpha(0); backgroundDrawable.setAlpha(0);
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether( animatorSetProxy.playTogether(
ObjectAnimatorProxy.ofFloat(linearLayout, "translationY", 0), ObjectAnimatorProxy.ofFloat(containerView, "translationY", 0),
ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 51)); ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 51));
animatorSetProxy.setDuration(200); animatorSetProxy.setDuration(200);
animatorSetProxy.setStartDelay(20); animatorSetProxy.setStartDelay(20);
@ -320,58 +541,14 @@ public class BottomSheet extends Dialog {
if (delegate != null) { if (delegate != null) {
delegate.onOpenAnimationEnd(); delegate.onOpenAnimationEnd();
} }
if (Build.VERSION.SDK_INT >= 11) {
container.setLayerType(View.LAYER_TYPE_NONE, null);
}
} }
}); });
animatorSetProxy.start(); animatorSetProxy.start();
} }
} }
});
}
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);
}
}
AndroidUtilities.runOnUIThread(new Runnable() {
@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;
}
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)));
}
if (animationProgress < 1) {
startLayoutAnimation(open, false);
} else {
if (open && delegate != null) {
delegate.onOpenAnimationEnd();
}
}
}
});
}
public void setDelegate(BottomSheetDelegate delegate) { public void setDelegate(BottomSheetDelegate delegate) {
this.delegate = delegate; this.delegate = delegate;
@ -382,7 +559,7 @@ public class BottomSheet extends Dialog {
} }
public LinearLayout getSheetContainer() { public LinearLayout getSheetContainer() {
return linearLayout; return containerView;
} }
public int getTag() { public int getTag() {
@ -397,13 +574,14 @@ public class BottomSheet extends Dialog {
cell.textView.setText(text); cell.textView.setText(text);
} }
private void dismissWithButtonClick(final int item) { public void dismissWithButtonClick(final int item) {
if (dismissed) { if (dismissed) {
return; return;
} }
dismissed = true;
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether( 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) ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0)
); );
animatorSetProxy.setDuration(180); animatorSetProxy.setDuration(180);
@ -440,9 +618,12 @@ public class BottomSheet extends Dialog {
return; return;
} }
dismissed = true; dismissed = true;
if (useRevealAnimation) {
startRevealAnimation(false);
} else {
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy(); AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether( 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) ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0)
); );
animatorSetProxy.setDuration(180); animatorSetProxy.setDuration(180);
@ -469,6 +650,7 @@ public class BottomSheet extends Dialog {
}); });
animatorSetProxy.start(); animatorSetProxy.start();
} }
}
public static class Builder { public static class Builder {
@ -515,10 +697,10 @@ public class BottomSheet extends Dialog {
return this; return this;
} }
public Builder setRevealAnimation(int x, int y) { public Builder setUseRevealAnimation() {
bottomSheet.revealX = x; if (Build.VERSION.SDK_INT >= 18 && !AndroidUtilities.isTablet()) {
bottomSheet.revealY = y;
bottomSheet.useRevealAnimation = true; bottomSheet.useRevealAnimation = true;
}
return this; return this;
} }
@ -532,8 +714,13 @@ public class BottomSheet extends Dialog {
return this; return this;
} }
public BottomSheet setOverrideTabletWidth(boolean value) { public Builder setApplyTopPaddings(boolean value) {
bottomSheet.overrideTabletWidth = value; bottomSheet.applyTopPaddings = value;
return this;
}
public BottomSheet setUseFullWidth(boolean value) {
bottomSheet.fullWidth = value;
return bottomSheet; return bottomSheet;
} }
} }

View file

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

View file

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

View file

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

View file

@ -87,7 +87,7 @@ public class StickersAdapter extends RecyclerView.Adapter implements Notificatio
} }
public void loadStikersForEmoji(CharSequence emoji) { 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) { if (search) {
lastSticker = emoji.toString(); lastSticker = emoji.toString();
HashMap<String, ArrayList<TLRPC.Document>> allStickers = StickersQuery.getAllStickers(); 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.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -65,7 +64,7 @@ public class BlockedUsersActivity extends BaseFragment implements NotificationCe
} }
@Override @Override
public View createView(Context context, LayoutInflater inflater) { public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true); actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("BlockedUsers", R.string.BlockedUsers)); 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 height;
private int textX; private int textX;
private int textY; private int textY;
private int textXOffset;
private ClickableSpan pressedLink; private ClickableSpan pressedLink;
private LinkPath urlPath = new LinkPath(); private LinkPath urlPath = new LinkPath();
@ -101,7 +102,9 @@ public class BotHelpCell extends View {
width = 0; width = 0;
height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18); height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18);
int count = textLayout.getLineCount(); int count = textLayout.getLineCount();
textXOffset = Integer.MAX_VALUE;
for (int a = 0; a < count; a++) { 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 = (int) Math.ceil(Math.max(width, textLayout.getLineWidth(a) - textLayout.getLineLeft(a)));
} }
width += AndroidUtilities.dp(4 + 18); width += AndroidUtilities.dp(4 + 18);
@ -113,7 +116,6 @@ public class BotHelpCell extends View {
float y = event.getY(); float y = event.getY();
boolean result = false; boolean result = false;
int side = AndroidUtilities.dp(48);
if (textLayout != null) { if (textLayout != null) {
if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) { if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) {
if (event.getAction() == MotionEvent.ACTION_DOWN) { 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.setBounds(x, y, width + x, height + y);
ResourceLoader.backgroundMediaDrawableIn.draw(canvas); ResourceLoader.backgroundMediaDrawableIn.draw(canvas);
canvas.save(); 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) { if (pressedLink != null) {
canvas.drawPath(urlPath, urlPaint); canvas.drawPath(urlPath, urlPaint);
} }

View file

@ -174,8 +174,8 @@ public class ChatActionCell extends BaseCell {
final int line = textLayout.getLineForVertical((int)y); final int line = textLayout.getLineForVertical((int)y);
final int off = textLayout.getOffsetForHorizontal(line, x); final int off = textLayout.getOffsetForHorizontal(line, x);
final float left = textLayout.getLineLeft(line); final float left = textLayout.getLineLeft(line);
if (left <= x && left + textLayout.getLineWidth(line) >= x) { if (left <= x && left + textLayout.getLineWidth(line) >= x && currentMessageObject.messageText instanceof Spannable) {
Spannable buffer = (Spannable)currentMessageObject.messageText; Spannable buffer = (Spannable) currentMessageObject.messageText;
URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); URLSpan[] link = buffer.getSpans(off, off, URLSpan.class);
if (link.length != 0) { if (link.length != 0) {
@ -226,7 +226,6 @@ public class ChatActionCell extends BaseCell {
int linesCount = textLayout.getLineCount(); int linesCount = textLayout.getLineCount();
for (int a = 0; a < linesCount; a++) { for (int a = 0; a < linesCount; a++) {
float lineWidth; float lineWidth;
float lineLeft = 0;
try { try {
lineWidth = textLayout.getLineWidth(a); lineWidth = textLayout.getLineWidth(a);
textHeight = (int)Math.max(textHeight, Math.ceil(textLayout.getLineBottom(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.AndroidUtilities;
import org.telegram.android.ImageLoader; import org.telegram.android.ImageLoader;
import org.telegram.android.MessagesController; import org.telegram.android.MessagesController;
import org.telegram.android.SendMessagesHelper;
import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLoader;
import org.telegram.android.MediaController; import org.telegram.android.MediaController;
import org.telegram.android.MessageObject; 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.ResourceLoader;
import org.telegram.ui.Components.SeekBar; import org.telegram.ui.Components.SeekBar;
@ -36,10 +37,10 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
private static Paint circlePaint; private static Paint circlePaint;
private SeekBar seekBar; private SeekBar seekBar;
private ProgressView progressView;
private int seekBarX; private int seekBarX;
private int seekBarY; private int seekBarY;
private RadialProgress radialProgress;
private int buttonState = 0; private int buttonState = 0;
private int buttonX; private int buttonX;
private int buttonY; private int buttonY;
@ -58,7 +59,7 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
seekBar = new SeekBar(context); seekBar = new SeekBar(context);
seekBar.delegate = this; seekBar.delegate = this;
progressView = new ProgressView(); radialProgress = new RadialProgress(this);
drawForwardedName = true; drawForwardedName = true;
if (timePaint == null) { if (timePaint == null) {
@ -78,7 +79,7 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
@Override @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
updateButtonState(); updateButtonState(false);
} }
@Override @Override
@ -131,21 +132,25 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
} }
if (result) { if (result) {
buttonState = 1; buttonState = 1;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate(); invalidate();
} }
} else if (buttonState == 1) { } else if (buttonState == 1) {
boolean result = MediaController.getInstance().pauseAudio(currentMessageObject); boolean result = MediaController.getInstance().pauseAudio(currentMessageObject);
if (result) { if (result) {
buttonState = 0; buttonState = 0;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate(); invalidate();
} }
} else if (buttonState == 2) { } else if (buttonState == 2) {
FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true); FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true);
buttonState = 3; buttonState = 3;
radialProgress.setBackground(getDrawableForCurrentState(), true, false);
invalidate(); invalidate();
} else if (buttonState == 3) { } else if (buttonState == 3) {
FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.audio); FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.audio);
buttonState = 2; buttonState = 2;
radialProgress.setBackground(getDrawableForCurrentState(), false, false);
invalidate(); invalidate();
} else if (buttonState == 4) { } else if (buttonState == 4) {
if (currentMessageObject.isOut() && currentMessageObject.isSending()) { 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); String timeString = String.format("%02d:%02d", duration / 60, duration % 60);
if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) {
lastTimeString = timeString;
timeWidth = (int)Math.ceil(timePaint.measureText(timeString)); timeWidth = (int)Math.ceil(timePaint.measureText(timeString));
timeLayout = new StaticLayout(timeString, timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); 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) { if (buttonState == 2) {
FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true); FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.audio, true);
buttonState = 3; buttonState = 3;
invalidate(); radialProgress.setBackground(getDrawableForCurrentState(), false, false);
} }
} }
public void updateButtonState() { public void updateButtonState(boolean animated) {
if (currentMessageObject == null) { if (currentMessageObject == null) {
return; return;
} }
if (currentMessageObject.isOut() && currentMessageObject.isSending()) { if (currentMessageObject.isOut() && currentMessageObject.isSending()) {
MediaController.getInstance().addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, this);
buttonState = 4; 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 { } else {
File cacheFile = null; File cacheFile = null;
if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) { if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) {
@ -212,21 +225,24 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
} else { } else {
buttonState = 1; buttonState = 1;
} }
progressView.setProgress(0); radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else { } else {
String fileName = currentMessageObject.getFileName(); String fileName = currentMessageObject.getFileName();
MediaController.getInstance().addLoadingFileObserver(fileName, this); MediaController.getInstance().addLoadingFileObserver(fileName, this);
if (!FileLoader.getInstance().isLoadingFile(fileName)) { if (!FileLoader.getInstance().isLoadingFile(fileName)) {
buttonState = 2; buttonState = 2;
progressView.setProgress(0); radialProgress.setProgress(0, animated);
radialProgress.setBackground(getDrawableForCurrentState(), false, animated);
} else { } else {
buttonState = 3; buttonState = 3;
Float progress = ImageLoader.getInstance().getFileProgress(fileName); Float progress = ImageLoader.getInstance().getFileProgress(fileName);
if (progress != null) { if (progress != null) {
progressView.setProgress(progress); radialProgress.setProgress(progress, animated);
} else { } 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 @Override
public void onFailedDownload(String fileName) { public void onFailedDownload(String fileName) {
updateButtonState(); updateButtonState(true);
} }
@Override @Override
public void onSuccessDownload(String fileName) { public void onSuccessDownload(String fileName) {
updateButtonState(); updateButtonState(true);
} }
@Override @Override
public void onProgressDownload(String fileName, float progress) { public void onProgressDownload(String fileName, float progress) {
progressView.setProgress(progress); radialProgress.setProgress(progress, true);
if (buttonState != 3) { if (buttonState != 3) {
updateButtonState(); updateButtonState(false);
} }
invalidate();
} }
@Override @Override
public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { public void onProgressUpload(String fileName, float progress, boolean isEncrypted) {
radialProgress.setProgress(progress, true);
} }
@Override @Override
@ -299,17 +314,17 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
seekBar.width = backgroundWidth - AndroidUtilities.dp(70); seekBar.width = backgroundWidth - AndroidUtilities.dp(70);
seekBar.height = AndroidUtilities.dp(30); seekBar.height = AndroidUtilities.dp(30);
progressView.width = backgroundWidth - AndroidUtilities.dp(94);
progressView.height = AndroidUtilities.dp(30);
seekBarY = AndroidUtilities.dp(11) + namesOffset; seekBarY = AndroidUtilities.dp(11) + namesOffset;
buttonY = AndroidUtilities.dp(13) + namesOffset; buttonY = AndroidUtilities.dp(13) + namesOffset;
radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(40), buttonY + AndroidUtilities.dp(40));
updateProgress(); updateProgress();
} }
@Override @Override
public void setMessageObject(MessageObject messageObject) { public void setMessageObject(MessageObject messageObject) {
if (currentMessageObject != messageObject || isUserDataChanged()) { boolean dataChanged = currentMessageObject == messageObject && isUserDataChanged();
if (currentMessageObject != messageObject || dataChanged) {
if (AndroidUtilities.isTablet()) { if (AndroidUtilities.isTablet()) {
backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat ? 102 : 50), AndroidUtilities.dp(300)); backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat ? 102 : 50), AndroidUtilities.dp(300));
} else { } else {
@ -318,15 +333,20 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
if (messageObject.isOut()) { if (messageObject.isOut()) {
seekBar.type = 0; seekBar.type = 0;
progressView.setProgressColors(0xffb4e396, 0xff6ac453); radialProgress.setProgressColor(0xff87bf78);
} else { } else {
seekBar.type = 1; seekBar.type = 1;
progressView.setProgressColors(0xffd9e2eb, 0xff86c5f8); radialProgress.setProgressColor(0xffa2b5c7);
} }
super.setMessageObject(messageObject); super.setMessageObject(messageObject);
} }
updateButtonState(); updateButtonState(dataChanged);
}
private Drawable getDrawableForCurrentState() {
return ResourceLoader.audioStatesDrawable[currentMessageObject.isOut() ? buttonState : buttonState + 5][0];
//buttonPressed ? 1 :
} }
@Override @Override
@ -338,27 +358,18 @@ public class ChatAudioCell extends ChatBaseCell implements SeekBar.SeekBarDelega
} }
canvas.save(); canvas.save();
if (buttonState == 0 || buttonState == 1) {
canvas.translate(seekBarX, seekBarY); canvas.translate(seekBarX, seekBarY);
seekBar.draw(canvas); seekBar.draw(canvas);
} else {
canvas.translate(seekBarX + AndroidUtilities.dp(12), seekBarY);
progressView.draw(canvas);
}
canvas.restore(); canvas.restore();
int state = buttonState;
if (currentMessageObject.isOut()) { if (currentMessageObject.isOut()) {
timePaint.setColor(0xff70b15c); timePaint.setColor(0xff70b15c);
circlePaint.setColor(0xff87bf78); circlePaint.setColor(0xff87bf78);
} else { } else {
state += 5;
timePaint.setColor(0xffa1aab3); timePaint.setColor(0xffa1aab3);
circlePaint.setColor(0xff4195e5); circlePaint.setColor(0xff4195e5);
} }
Drawable buttonDrawable = ResourceLoader.audioStatesDrawable[state][buttonPressed ? 1 : 0]; radialProgress.onDraw(canvas);
setDrawableBounds(buttonDrawable, buttonX, buttonY);
buttonDrawable.draw(canvas);
canvas.save(); canvas.save();
canvas.translate(timeX, AndroidUtilities.dp(42) + namesOffset); 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.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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); 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(); float y = event.getY();
boolean result = false; boolean result = false;
int side = AndroidUtilities.dp(36);
if (event.getAction() == MotionEvent.ACTION_DOWN) { 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()) { if (x >= avatarImage.getImageX() && x <= avatarImage.getImageX() + namesWidth + AndroidUtilities.dp(42) && y >= avatarImage.getImageY() && y <= avatarImage.getImageY() + avatarImage.getImageHeight()) {
avatarPressed = true; avatarPressed = true;

View file

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

View file

@ -193,7 +193,7 @@ public class ChatMessageCell extends ChatBaseCell {
pressedLink.onClick(this); pressedLink.onClick(this);
} else { } else {
TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; 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); delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.url, webPage.embed_width, webPage.embed_height);
} else { } else {
Uri uri = Uri.parse(webPage.url); Uri uri = Uri.parse(webPage.url);
@ -536,7 +536,7 @@ public class ChatMessageCell extends ChatBaseCell {
if (webPage.photo != null) { if (webPage.photo != null) {
boolean smallImage = webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article")); 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; smallImage = false;
isSmallImage = 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()) { if (message.isOut()) {
name = LocaleController.getString("FromYou", R.string.FromYou); name = LocaleController.getString("FromYou", R.string.FromYou);
} else { } else {
if (UserObject.isDeleted(fromUser)) { name = UserObject.getFirstName(fromUser);
name = "Deleted";
} else {
if (fromUser.first_name != null && fromUser.first_name.length() > 0) {
name = fromUser.first_name;
} else {
name = fromUser.last_name;
}
}
} }
checkMessage = false; checkMessage = false;
if (message.caption != null) { if (message.caption != null) {
@ -373,11 +365,11 @@ public class DialogCell extends BaseCell {
mess = mess.substring(0, 150); mess = mess.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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 { } else {
if (message.messageOwner.media != null && !message.isMediaEmpty()) { if (message.messageOwner.media != null && !message.isMediaEmpty()) {
currentMessagePaint = messagePrintingPaint; 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 { } else {
if (message.messageOwner.message != null) { if (message.messageOwner.message != null) {
String mess = message.messageOwner.message; String mess = message.messageOwner.message;
@ -385,7 +377,7 @@ public class DialogCell extends BaseCell {
mess = mess.substring(0, 150); mess = mess.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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); messageWidth = Math.max(AndroidUtilities.dp(12), messageWidth);
CharSequence messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); 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 android.widget.ImageView;
import org.telegram.android.AndroidUtilities; import org.telegram.android.AndroidUtilities;
import org.telegram.messenger.R;
import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LayoutHelper;
public class PhotoAttachCameraCell extends FrameLayout { public class PhotoAttachCameraCell extends FrameLayout {
@ -23,7 +22,7 @@ public class PhotoAttachCameraCell extends FrameLayout {
ImageView imageView = new ImageView(context); ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setImageResource(R.drawable.ic_attach_photobig); //imageView.setImageResource(R.drawable.ic_attach_photobig);
imageView.setBackgroundColor(0xff777777); imageView.setBackgroundColor(0xff777777);
addView(imageView, LayoutHelper.createFrame(80, 80)); addView(imageView, LayoutHelper.createFrame(80, 80));
} }

View file

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

View file

@ -28,7 +28,7 @@ public class SharedMediaSectionCell extends FrameLayout {
textView = new TextView(getContext()); textView = new TextView(getContext());
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
textView.setTextColor(0xff222222); textView.setTextColor(0xff212121);
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); 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)); 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) { for (TLRPC.DocumentAttribute attribute : document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeSticker) { if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
if (attribute.alt != null && attribute.alt.length() > 0) { 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; set = true;
} }
break; break;
} }
} }
if (!set) { 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); emojiTextView.setVisibility(VISIBLE);
} else { } else {

View file

@ -36,7 +36,7 @@ public class TextDetailCell extends FrameLayout {
textView.setMaxLines(1); textView.setMaxLines(1);
textView.setSingleLine(true); textView.setSingleLine(true);
textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); 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 = new TextView(context);
valueTextView.setTextColor(0xff8a8a8a); valueTextView.setTextColor(0xff8a8a8a);
@ -45,7 +45,7 @@ public class TextDetailCell extends FrameLayout {
valueTextView.setMaxLines(1); valueTextView.setMaxLines(1);
valueTextView.setSingleLine(true); valueTextView.setSingleLine(true);
valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); 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 = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,6 @@ import android.text.TextWatcher;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -62,7 +61,7 @@ public class ChangeUsernameActivity extends BaseFragment {
private final static int done_button = 1; private final static int done_button = 1;
@Override @Override
public View createView(Context context, LayoutInflater inflater) { public View createView(Context context) {
actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setBackButtonImage(R.drawable.ic_ab_back);
actionBar.setAllowOverlayTitle(true); actionBar.setAllowOverlayTitle(true);
actionBar.setTitle(LocaleController.getString("Username", R.string.Username)); 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.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.Cursor; import android.database.Cursor;
@ -32,7 +31,6 @@ import android.util.Base64;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -40,6 +38,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; 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.ActionBarMenu;
import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.ActionBarMenuItem;
import org.telegram.ui.Cells.ChatMessageCell; import org.telegram.ui.Cells.ChatMessageCell;
import org.telegram.ui.Cells.ChatMusicCell;
import org.telegram.ui.Cells.ChatUnreadCell; import org.telegram.ui.Cells.ChatUnreadCell;
import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AlertsCreator;
import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarDrawable;
@ -123,7 +123,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.Semaphore; 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 { PhotoViewer.PhotoViewerProvider {
protected TLRPC.Chat currentChat; protected TLRPC.Chat currentChat;
@ -177,6 +177,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
private ListView mentionListView; private ListView mentionListView;
private AnimatorSetProxy mentionListAnimation; private AnimatorSetProxy mentionListAnimation;
private ChatAttachView chatAttachView; private ChatAttachView chatAttachView;
private BottomSheet chatAttachViewSheet;
private boolean allowStickersPanel; private boolean allowStickersPanel;
private AnimatorSetProxy runningAnimation; 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.removeAllMessagesFromDialog);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged); NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset); NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.screenshotTook); NotificationCenter.getInstance().addObserver(this, NotificationCenter.screenshotTook);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.blockedUsersDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.blockedUsersDidLoaded);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileNewChunkAvailable); 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.botInfoDidLoaded);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botKeyboardDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botKeyboardDidLoaded);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatSearchResultsAvailable); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatSearchResultsAvailable);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
if (AndroidUtilities.isTablet()) { if (AndroidUtilities.isTablet()) {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true);
@ -578,11 +581,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
chatAttachView.onDestroy(); chatAttachView.onDestroy();
} }
AndroidUtilities.unlockOrientation(getParentActivity()); AndroidUtilities.unlockOrientation(getParentActivity());
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject != null && !messageObject.isMusic()) {
MediaController.getInstance().stopAudio(); MediaController.getInstance().stopAudio();
} }
}
@Override @Override
public View createView(Context context, LayoutInflater inflater) { public View createView(Context context) {
for (int a = 0; a < 8; a++) { for (int a = 0; a < 8; a++) {
chatMessageCellsCache.add(new ChatMessageCell(context)); chatMessageCellsCache.add(new ChatMessageCell(context));
@ -595,6 +601,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
lastStatus = null; lastStatus = null;
hasOwnBackground = true; hasOwnBackground = true;
chatAttachView = null; chatAttachView = null;
chatAttachViewSheet = null;
ResourceLoader.loadRecources(context); ResourceLoader.loadRecources(context);
@ -673,7 +680,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean("onlySelect", true); args.putBoolean("onlySelect", true);
args.putInt("dialogsType", 1); args.putInt("dialogsType", 1);
MessagesActivity fragment = new MessagesActivity(args); DialogsActivity fragment = new DialogsActivity(args);
fragment.setDelegate(ChatActivity.this); fragment.setDelegate(ChatActivity.this);
presentFragment(fragment); presentFragment(fragment);
} else if (id == chat_enc_timer) { } else if (id == chat_enc_timer) {
@ -770,20 +777,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
selectedMessagesCanCopyIds.clear(); selectedMessagesCanCopyIds.clear();
actionBar.hideActionMode(); actionBar.hideActionMode();
updateVisibleRows(); updateVisibleRows();
}/* else if (id == chat_menu_attach) { } else if (id == chat_menu_attach) {
if (getParentActivity() == null) { if (getParentActivity() == null) {
return; return;
} }
BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity());
if (chatAttachView == null) { if (chatAttachView == null) {
BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity());
chatAttachView = new ChatAttachView(getParentActivity()); chatAttachView = new ChatAttachView(getParentActivity());
chatAttachView.setDelegate(new ChatAttachView.ChatAttachViewDelegate() { chatAttachView.setDelegate(new ChatAttachView.ChatAttachViewDelegate() {
@Override @Override
public void didPressedButton(int button) { public void didPressedButton(int button) {
if (visibleDialog != null) {
visibleDialog.dismiss();
}
if (button == 7) { if (button == 7) {
chatAttachViewSheet.dismiss();
HashMap<Integer, MediaController.PhotoEntry> selectedPhotos = chatAttachView.getSelectedPhotos(); HashMap<Integer, MediaController.PhotoEntry> selectedPhotos = chatAttachView.getSelectedPhotos();
if (!selectedPhotos.isEmpty()) { if (!selectedPhotos.isEmpty()) {
ArrayList<String> photos = new ArrayList<>(); ArrayList<String> photos = new ArrayList<>();
@ -796,31 +802,50 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
showReplyPanel(false, null, null, null, false, true); showReplyPanel(false, null, null, null, false, true);
} }
return; return;
} else {
chatAttachViewSheet.dismissWithButtonClick(button);
} }
processSelectedAttach(button); processSelectedAttach(button);
} }
}); });
}
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() { builder.setDelegate(new BottomSheet.BottomSheetDelegate() {
@Override @Override
public void onOpenAnimationStart() { public void onRevealAnimationStart(boolean open) {
chatAttachView.startAnimations(coords[1] > AndroidUtilities.displaySize.y - AndroidUtilities.dp(100)); 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 @Override
public void onOpenAnimationEnd() { public void onOpenAnimationEnd() {
chatAttachView.onRevealAnimationEnd(true);
}
@Override
public View getRevealView() {
return menuItem;
} }
}); });
builder.setApplyTopPaddings(false);
builder.setUseRevealAnimation();
builder.setCustomView(chatAttachView);
chatAttachViewSheet = builder.create();
}
chatAttachView.init(ChatActivity.this); chatAttachView.init(ChatActivity.this);
showDialog(builder.create()); 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) { }/* 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); processSelectedAttach(id);
} else if (id == bot_help) { } */else if (id == bot_help) {
SendMessagesHelper.getInstance().sendMessage("/help", dialog_id, null, null, false); SendMessagesHelper.getInstance().sendMessage("/help", dialog_id, null, null, false);
} else if (id == bot_settings) { } else if (id == bot_settings) {
SendMessagesHelper.getInstance().sendMessage("/settings", dialog_id, null, null, false); 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(); ActionBarMenu menu = actionBar.createMenu();
if (currentEncryptedChat == null && !isBroadcast) { 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 @Override
public void onSearchCollapse() { public void onSearchCollapse() {
avatarContainer.setVisibility(View.VISIBLE); avatarContainer.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); headerItem.setVisibility(View.VISIBLE);
}
if (attachItem != null) {
attachItem.setVisibility(View.GONE);
}
}
searchItem.setVisibility(View.GONE); searchItem.setVisibility(View.GONE);
//chatActivityEnterView.setVisibility(View.VISIBLE); //chatActivityEnterView.setVisibility(View.VISIBLE);
searchUpItem.clearAnimation(); searchUpItem.clearAnimation();
@ -945,7 +984,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
searchItem.getSearchField().requestFocus(); searchItem.getSearchField().requestFocus();
AndroidUtilities.showKeyboard(searchItem.getSearchField()); AndroidUtilities.showKeyboard(searchItem.getSearchField());
} }
}, 200); //TODO find a better way to open keyboard }, 300); //TODO find a better way to open keyboard
} }
@Override @Override
@ -960,7 +999,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
searchUpItem = menu.addItem(search_up, R.drawable.search_up); searchUpItem = menu.addItem(search_up, R.drawable.search_up);
searchUpItem.setVisibility(View.GONE); searchUpItem.setVisibility(View.GONE);
searchDownItem = menu.addItem(search_down, R.drawable.search_down); 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); headerItem = menu.addItem(0, R.drawable.ic_ab_other);
@ -990,22 +1029,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
updateSubtitle(); updateSubtitle();
updateTitleIcons(); updateTitleIcons();
attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other).setAllowCloseAnimation(false); attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other).setOverrideMenuClick(true).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.setVisibility(View.GONE); attachItem.setVisibility(View.GONE);
menuItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_attach).setAllowCloseAnimation(false); 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); menuItem.setBackgroundDrawable(null);
actionModeViews.clear(); actionModeViews.clear();
@ -1053,8 +1079,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
@ -1067,14 +1091,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
} }
int childCount = getChildCount(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i); measureChildWithMargins(chatActivityEnterView, widthMeasureSpec, 0, heightMeasureSpec, 0);
if (child == chatActivityEnterView) { inputFieldHeight = chatActivityEnterView.getMeasuredHeight();
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
inputFieldHeight = child.getMeasuredHeight();
break;
}
}
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
View child = getChildAt(i); View child = getChildAt(i);
if (child.getVisibility() == GONE || child == chatActivityEnterView) { if (child.getVisibility() == GONE || child == chatActivityEnterView) {
@ -1660,6 +1680,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override @Override
public void onAttachButtonHidden() { public void onAttachButtonHidden() {
if (actionBar.isSearchFieldVisible()) {
return;
}
if (attachItem != null) { if (attachItem != null) {
attachItem.setVisibility(View.VISIBLE); attachItem.setVisibility(View.VISIBLE);
} }
@ -1670,6 +1693,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override @Override
public void onAttachButtonShow() { public void onAttachButtonShow() {
if (actionBar.isSearchFieldVisible()) {
return;
}
if (attachItem != null) { if (attachItem != null) {
attachItem.setVisibility(View.GONE); attachItem.setVisibility(View.GONE);
} }
@ -1680,7 +1706,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
@Override @Override
public void onWindowSizeChanged(int size) { public void onWindowSizeChanged(int size) {
if (size < AndroidUtilities.dp(72) + AndroidUtilities.getCurrentActionBarHeight()) { if (size < AndroidUtilities.dp(72) + ActionBar.getCurrentActionBarHeight()) {
allowStickersPanel = false; allowStickersPanel = false;
if (stickersPanel.getVisibility() == View.VISIBLE) { if (stickersPanel.getVisibility() == View.VISIBLE) {
stickersPanel.clearAnimation(); stickersPanel.clearAnimation();
@ -2089,12 +2115,15 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
}); });
presentFragment(fragment); presentFragment(fragment);
} else if (which == attach_audio) { } else if (which == attach_audio) {
try { AudioSelectActivity fragment = new AudioSelectActivity();
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); fragment.setDelegate(new AudioSelectActivity.AudioSelectActivityDelegate() {
startActivityForResult(intent, 32); @Override
} catch (Exception e) { public void didSelectAudio(ArrayList<MessageObject> audios) {
FileLog.e("tmessages", e); SendMessagesHelper.prepareSendingAudioDocuments(audios, dialog_id, replyingMessageObject);
showReplyPanel(false, null, null, null, false, true);
} }
});
presentFragment(fragment);
} else if (which == attach_contact) { } else if (which == attach_contact) {
try { try {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); 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.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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) { } else if (messageObjects != null) {
if (messageObjects.isEmpty()) { if (messageObjects.isEmpty()) {
@ -2277,7 +2306,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
mess = mess.substring(0, 150); mess = mess.substring(0, 150);
} }
mess = mess.replace("\n", " "); 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 { } else {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjects.size())); replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedMessage", messageObjects.size()));
} }
@ -2296,7 +2325,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
} }
} else if (type == 12) { } else if (type == 12) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedContact", messageObjects.size())); replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedContact", messageObjects.size()));
} else if (type == 2) { } else if (type == 2 || type == 14) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedAudio", messageObjects.size())); replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedAudio", messageObjects.size()));
} else if (type == 13) { } else if (type == 13) {
replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjects.size())); replyObjectTextView.setText(LocaleController.formatPluralString("ForwardedSticker", messageObjects.size()));
@ -3221,22 +3250,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
FileLog.e("tmessages", e); 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 && 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) { 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) { if (AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 17 && currentEncryptedChat.ttl > 0 && currentEncryptedChat.ttl <= 60) {
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity());
builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); 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]; int encId = (Integer) args[0];
if (currentEncryptedChat != null && currentEncryptedChat.id == encId) { if (currentEncryptedChat != null && currentEncryptedChat.id == encId) {
int date = (Integer) args[1]; int date = (Integer) args[1];
boolean started = false;
for (MessageObject obj : messages) { for (MessageObject obj : messages) {
if (!obj.isOut()) { if (!obj.isOut()) {
continue; continue;
@ -4018,7 +4029,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
} }
updateVisibleRows(); updateVisibleRows();
} }
} else if (id == NotificationCenter.audioDidReset) { } else if (id == NotificationCenter.audioDidReset || id == NotificationCenter.audioPlayStateChanged) {
Integer mid = (Integer) args[0]; Integer mid = (Integer) args[0];
if (chatListView != null) { if (chatListView != null) {
int count = chatListView.getChildCount(); int count = chatListView.getChildCount();
@ -4027,7 +4038,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (view instanceof ChatAudioCell) { if (view instanceof ChatAudioCell) {
ChatAudioCell cell = (ChatAudioCell) view; ChatAudioCell cell = (ChatAudioCell) view;
if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) { 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; break;
} }
} }
@ -4045,6 +4062,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
cell.updateProgress(); cell.updateProgress();
break; 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) { } else if (id == NotificationCenter.audioDidStarted) {
MessageObject messageObject = (MessageObject) args[0]; MessageObject messageObject = (MessageObject) args[0];
sendSecretMessageRead(messageObject); 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) { } else if (id == NotificationCenter.updateMessageMedia) {
MessageObject messageObject = (MessageObject) args[0]; MessageObject messageObject = (MessageObject) args[0];
MessageObject existMessageObject = messagesDict.get(messageObject.getId()); 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); 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); avatarContainer.setPadding(avatarContainer.getPaddingLeft(), padding, avatarContainer.getPaddingRight(), padding);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) avatarContainer.getLayoutParams(); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) avatarContainer.getLayoutParams();
layoutParams.topMargin = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); 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}; options = new int[]{8, 2, 3, 1};
} else if (type == 4) { } else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { 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}; options = new int[]{8, 10, 4, 2, 1};
} else { } 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)}; 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)}; 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}; options = new int[]{8, 5, 4, 2, 1};
} else if (type == 6) { } 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}; options = new int[]{8, 7, 10, 6, 2, 1};
} else if (type == 7) { } 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)}; 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}; options = new int[]{2, 3, 1};
} else if (type == 4) { } else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { 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}; options = new int[]{10, 4, 2, 1};
} else { } else {
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)}; 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)}; 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}; options = new int[]{5, 4, 2, 1};
} else if (type == 6) { } 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}; options = new int[]{7, 10, 6, 2, 1};
} else if (type == 7) { } 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)}; 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}; options = new int[]{3, 1};
} else if (type == 4) { } else if (type == 4) {
if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { 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}; options = new int[]{10, 4, 1};
} else { } else {
items = new CharSequence[]{LocaleController.getString("SaveToGallery", R.string.SaveToGallery), LocaleController.getString("Delete", R.string.Delete)}; 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(); Bundle args = new Bundle();
args.putBoolean("onlySelect", true); args.putBoolean("onlySelect", true);
args.putInt("dialogsType", 1); args.putInt("dialogsType", 1);
MessagesActivity fragment = new MessagesActivity(args); DialogsActivity fragment = new DialogsActivity(args);
fragment.setDelegate(this); fragment.setDelegate(this);
presentFragment(fragment); presentFragment(fragment);
} else if (option == 3) { } else if (option == 3) {
@ -4787,7 +4865,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
FileLog.e("tmessages", e); FileLog.e("tmessages", e);
} }
} else if (option == 4) { } else if (option == 4) {
String fileName = selectedObject.getFileName();
String path = selectedObject.messageOwner.attachPath; String path = selectedObject.messageOwner.attachPath;
if (path != null && path.length() > 0) { if (path != null && path.length() > 0) {
File temp = new File(path); File temp = new File(path);
@ -4802,7 +4879,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
MediaController.saveFile(path, getParentActivity(), 1, null); MediaController.saveFile(path, getParentActivity(), 1, null);
} else if (selectedObject.type == 1) { } else if (selectedObject.type == 1) {
MediaController.saveFile(path, getParentActivity(), 0, null); 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 intent = new Intent(Intent.ACTION_SEND);
intent.setType(selectedObject.messageOwner.media.document.mime_type); intent.setType(selectedObject.messageOwner.media.document.mime_type);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); 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) { } else if (option == 6 || option == 7) {
String fileName = selectedObject.getFileName();
String path = selectedObject.messageOwner.attachPath; String path = selectedObject.messageOwner.attachPath;
if (path != null && path.length() > 0) { if (path != null && path.length() > 0) {
File temp = new File(path); File temp = new File(path);
@ -4848,7 +4924,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (path == null || path.length() == 0) { if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString(); 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) { if (option == 6) {
Intent intent = new Intent(Intent.ACTION_SEND); Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(selectedObject.messageOwner.media.document.mime_type); 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) { if (path == null || path.length() == 0) {
path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString(); path = FileLoader.getPathToMessage(selectedObject.messageOwner).toString();
} }
MediaController.saveFile(path, getParentActivity(), 2, fileName); MediaController.saveFile(path, getParentActivity(), selectedObject.isMusic() ? 3 : 2, fileName);
} }
selectedObject = null; selectedObject = null;
} }
@Override @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())) { if (dialog_id != 0 && (forwaringMessage != null || !selectedMessagesIds.isEmpty())) {
ArrayList<MessageObject> fmessages = new ArrayList<>(); ArrayList<MessageObject> fmessages = new ArrayList<>();
if (forwaringMessage != null) { if (forwaringMessage != null) {
@ -4961,7 +5037,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
public boolean isGoogleMapsInstalled() { public boolean isGoogleMapsInstalled() {
try { 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; return true;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
if (getParentActivity() == null) { if (getParentActivity() == null) {
@ -5213,7 +5289,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (url.startsWith("@")) { if (url.startsWith("@")) {
MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0); MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0);
} else if (url.startsWith("#")) { } else if (url.startsWith("#")) {
MessagesActivity fragment = new MessagesActivity(null); DialogsActivity fragment = new DialogsActivity(null);
fragment.setSearchString(url); fragment.setSearchString(url);
presentFragment(fragment); presentFragment(fragment);
} else if (url.startsWith("/")) { } 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) { if (view instanceof ChatBaseCell) {
@ -5261,7 +5339,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
if (url.startsWith("@")) { if (url.startsWith("@")) {
MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0); MessagesController.openByUserName(url.substring(1), ChatActivity.this, 0);
} else if (url.startsWith("#")) { } else if (url.startsWith("#")) {
MessagesActivity fragment = new MessagesActivity(null); DialogsActivity fragment = new DialogsActivity(null);
fragment.setSearchString(url); fragment.setSearchString(url);
presentFragment(fragment); presentFragment(fragment);
} else if (url.startsWith("/")) { } 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) { public void needOpenWebView(String url, String title, String originalUrl, int w, int h) {
BottomSheet.Builder builder = new BottomSheet.Builder(mContext); BottomSheet.Builder builder = new BottomSheet.Builder(mContext);
builder.setCustomView(new WebFrameLayout(mContext, builder.create(), title, originalUrl, url, w, h)); builder.setCustomView(new WebFrameLayout(mContext, builder.create(), title, originalUrl, url, w, h));
builder.setOverrideTabletWidth(true); builder.setUseFullWidth(true);
showDialog(builder.create()); showDialog(builder.create());
} }
@ -5434,6 +5512,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not
showDialog(builder.create()); 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) { } else if (view instanceof ChatActionCell) {
((ChatActionCell) view).setDelegate(new ChatActionCell.ChatActionCellDelegate() { ((ChatActionCell) view).setDelegate(new ChatActionCell.ChatActionCellDelegate() {

View file

@ -57,7 +57,7 @@ public class BotKeyboardView extends LinearLayout {
public void setPanelHeight(int height) { public void setPanelHeight(int height) {
panelHeight = 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); 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 count = container.getChildCount();
int newHeight = AndroidUtilities.dp(buttonHeight); int newHeight = AndroidUtilities.dp(buttonHeight);
@ -87,7 +87,7 @@ public class BotKeyboardView extends LinearLayout {
container.removeAllViews(); container.removeAllViews();
buttonViews.clear(); buttonViews.clear();
if (buttons != null) { if (buttons != null && botButtons.rows.size() != 0) {
isFullSize = (buttons.flags & 1) == 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); 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++) { for (int a = 0; a < buttons.rows.size(); a++) {
@ -106,7 +106,7 @@ public class BotKeyboardView extends LinearLayout {
textView.setGravity(Gravity.CENTER); textView.setGravity(Gravity.CENTER);
textView.setBackgroundResource(R.drawable.bot_keyboard_states); textView.setBackgroundResource(R.drawable.bot_keyboard_states);
textView.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); 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)); layout.addView(textView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, weight, 0, 0, b != row.buttons.size() - 1 ? 10 : 0, 0));
textView.setOnClickListener(new OnClickListener() { textView.setOnClickListener(new OnClickListener() {
@Override @Override

View file

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

View file

@ -8,8 +8,11 @@
package org.telegram.ui.Components; package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
@ -18,6 +21,7 @@ import android.view.Gravity;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -25,14 +29,16 @@ import android.widget.TextView;
import org.telegram.android.AndroidUtilities; import org.telegram.android.AndroidUtilities;
import org.telegram.android.LocaleController; import org.telegram.android.LocaleController;
import org.telegram.android.MediaController; import org.telegram.android.MediaController;
import org.telegram.android.NotificationCenter;
import org.telegram.android.support.widget.LinearLayoutManager; import org.telegram.android.support.widget.LinearLayoutManager;
import org.telegram.messenger.R; import org.telegram.messenger.R;
import org.telegram.ui.Adapters.PhotoAttachAdapter; import org.telegram.ui.Adapters.PhotoAttachAdapter;
import org.telegram.ui.ChatActivity; import org.telegram.ui.ChatActivity;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public class ChatAttachView extends FrameLayout { public class ChatAttachView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate {
public interface ChatAttachViewDelegate { public interface ChatAttachViewDelegate {
void didPressedButton(int button); void didPressedButton(int button);
@ -42,7 +48,16 @@ public class ChatAttachView extends FrameLayout {
private PhotoAttachAdapter photoAttachAdapter; private PhotoAttachAdapter photoAttachAdapter;
private ChatActivity baseFragment; private ChatActivity baseFragment;
private AttachButton sendPhotosButton; 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; private ChatAttachViewDelegate delegate;
@ -56,7 +71,6 @@ public class ChatAttachView extends FrameLayout {
imageView = new ImageView(context); imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);
//imageView.setColorFilter(0x33000000);
addView(imageView, LayoutHelper.createFrame(64, 64, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); addView(imageView, LayoutHelper.createFrame(64, 64, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
textView = new TextView(context); textView = new TextView(context);
@ -83,10 +97,15 @@ public class ChatAttachView extends FrameLayout {
public ChatAttachView(Context context) { public ChatAttachView(Context context) {
super(context); super(context);
RecyclerListView attachPhotoRecyclerView = new RecyclerListView(context); NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded);
if (photoAttachAdapter != null) { if (MediaController.allPhotosAlbumEntry == null) {
photoAttachAdapter.onDestroy(); if (Build.VERSION.SDK_INT >= 21) {
MediaController.loadGalleryPhotosAlbums(0);
} }
loading = true;
}
views[8] = attachPhotoRecyclerView = new RecyclerListView(context);
attachPhotoRecyclerView.setVerticalScrollBarEnabled(true); attachPhotoRecyclerView.setVerticalScrollBarEnabled(true);
attachPhotoRecyclerView.setAdapter(photoAttachAdapter = new PhotoAttachAdapter(context)); attachPhotoRecyclerView.setAdapter(photoAttachAdapter = new PhotoAttachAdapter(context));
attachPhotoRecyclerView.setClipToPadding(false); 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); lineView.setBackgroundColor(0xffd2d2d2);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT); addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT));
layoutParams.topMargin = AndroidUtilities.dp(88);
addView(lineView, layoutParams);
CharSequence[] items = new CharSequence[]{ CharSequence[] items = new CharSequence[]{
LocaleController.getString("ChatCamera", R.string.ChatCamera), LocaleController.getString("ChatCamera", R.string.ChatCamera),
LocaleController.getString("ChatGallery", R.string.ChatGallery), LocaleController.getString("ChatGallery", R.string.ChatGallery),
@ -128,23 +150,21 @@ public class ChatAttachView extends FrameLayout {
"" ""
}; };
int itemIcons[] = new int[] { int itemIcons[] = new int[] {
R.drawable.ic_attach_photo_big, R.drawable.attach_camera_states,
R.drawable.ic_attach_gallery_big, R.drawable.attach_gallery_states,
R.drawable.ic_attach_video_big, R.drawable.attach_video_states,
R.drawable.ic_attach_music_big, R.drawable.attach_audio_states,
R.drawable.ic_attach_file_big, R.drawable.attach_file_states,
R.drawable.ic_attach_contact_big, R.drawable.attach_contact_states,
R.drawable.ic_attach_location_big, R.drawable.attach_location_states,
R.drawable.ic_attach_hide_big, R.drawable.attach_hide_states,
}; };
for (int a = 0; a < 8; a++) { for (int a = 0; a < 8; a++) {
AttachButton attachButton = new AttachButton(context); AttachButton attachButton = new AttachButton(context);
attachButton.setTextAndIcon(items[a], itemIcons[a]); attachButton.setTextAndIcon(items[a], itemIcons[a]);
int y = 97 + 95 * (a / 4); addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP));
int x = 10 + (a % 4) * 85;
addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP, x, y, 0, 0));
attachButton.setTag(a); attachButton.setTag(a);
buttons[a] = attachButton; views[a] = attachButton;
if (a == 7) { if (a == 7) {
sendPhotosButton = attachButton; sendPhotosButton = attachButton;
sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0);
@ -164,24 +184,58 @@ public class ChatAttachView extends FrameLayout {
return true; 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 @Override
protected void onMeasure(int widthSpec, int heightSpec) { 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() { public void updatePhotosButton() {
int count = photoAttachAdapter.getSelectedPhotos().size(); int count = photoAttachAdapter.getSelectedPhotos().size();
if (count == 0) { if (count == 0) {
sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0); sendPhotosButton.imageView.setPadding(0, AndroidUtilities.dp(4), 0, 0);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.ic_attach_hide_big); sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_hide_states);
sendPhotosButton.imageView.setImageResource(R.drawable.ic_attach_hide_big_icon); sendPhotosButton.imageView.setImageResource(R.drawable.attach_hide2);
sendPhotosButton.textView.setText(""); sendPhotosButton.textView.setText("");
} else { } else {
sendPhotosButton.imageView.setPadding(AndroidUtilities.dp(2), 0, 0, 0); sendPhotosButton.imageView.setPadding(AndroidUtilities.dp(2), 0, 0, 0);
sendPhotosButton.imageView.setBackgroundResource(R.drawable.ic_attach_send_big); sendPhotosButton.imageView.setBackgroundResource(R.drawable.attach_send_states);
sendPhotosButton.imageView.setImageResource(R.drawable.ic_attach_send_big_icon); sendPhotosButton.imageView.setImageResource(R.drawable.attach_send2);
sendPhotosButton.textView.setText(LocaleController.formatString("SendItems", R.string.SendItems, String.format("(%d)", count))); sendPhotosButton.textView.setText(LocaleController.formatString("SendItems", R.string.SendItems, String.format("(%d)", count)));
} }
} }
@ -190,23 +244,84 @@ public class ChatAttachView extends FrameLayout {
delegate = chatAttachViewDelegate; delegate = chatAttachViewDelegate;
} }
public void startAnimations(boolean up) { public void onRevealAnimationEnd(boolean open) {
for (int a = 0; a < 4; a++) { if (open && Build.VERSION.SDK_INT <= 19 && MediaController.allPhotosAlbumEntry == null) {
//buttons[a].setTranslationY(AndroidUtilities.dp(up ? 20 : -20)); MediaController.loadGalleryPhotosAlbums(0);
//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); @SuppressLint("NewApi")
buttons[a + 4].setScaleY(0.8f); 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 animatorSet = new AnimatorSet();
animatorSet.playTogether(ObjectAnimator.ofFloat(buttons[a], "scaleX", 1), animatorSet.playTogether(animators);
ObjectAnimator.ofFloat(buttons[a + 4], "scaleX", 1),
ObjectAnimator.ofFloat(buttons[a], "scaleY", 1),
ObjectAnimator.ofFloat(buttons[a + 4], "scaleY", 1));
animatorSet.setDuration(150); animatorSet.setDuration(150);
animatorSet.setStartDelay((3 - a) * 40); 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();
} }
});
animatorSet.start();
}
}
} }
public void init(ChatActivity parentFragment) { public void init(ChatActivity parentFragment) {
@ -221,7 +336,7 @@ public class ChatAttachView extends FrameLayout {
} }
public void onDestroy() { public void onDestroy() {
photoAttachAdapter.onDestroy(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.albumsDidLoaded);
baseFragment = null; baseFragment = null;
} }
} }

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