mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-08 07:48:45 +01:00
added multiple dns resolvers support
implementing clear notifications
This commit is contained in:
parent
101d63acec
commit
629f0ae0f6
@ -118,6 +118,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
||||
String AUTHORITY_RETWEETS_OF_ME = "retweets_of_me";
|
||||
String AUTHORITY_MUTES_USERS = "mutes_users";
|
||||
String AUTHORITY_INTERACTIONS = "interactions";
|
||||
String AUTHORITY_NOTIFICATIONS = "notifications";
|
||||
String AUTHORITY_ACCOUNTS = "accounts";
|
||||
String AUTHORITY_DRAFTS = "drafts";
|
||||
String AUTHORITY_FILTERS = "filters";
|
||||
@ -155,7 +156,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
||||
String QUERY_PARAM_FINISH_ONLY = "finish_only";
|
||||
String QUERY_PARAM_NEW_ITEMS_COUNT = "new_items_count";
|
||||
String QUERY_PARAM_CONVERSATION_ID = "conversation_id";
|
||||
String QUERY_PARAM_READ_POSITION = "param_read_position";
|
||||
String QUERY_PARAM_READ_POSITION = "read_position";
|
||||
String QUERY_PARAM_LIMIT = "limit";
|
||||
String QUERY_PARAM_EXTRA = "extra";
|
||||
String QUERY_PARAM_TIMESTAMP = "timestamp";
|
||||
|
@ -55,7 +55,7 @@ public class SystemHosts {
|
||||
final String host = scanner.next();
|
||||
if (host.startsWith("#")) break;
|
||||
if (TextUtils.equals(hostToResolve, host)) {
|
||||
final InetAddress resolved = TwidereDns.getResolvedIPAddress(host, address);
|
||||
final InetAddress resolved = TwidereDns.Companion.getResolvedIPAddress(host, address);
|
||||
if (resolved != null) return Collections.singletonList(resolved);
|
||||
}
|
||||
}
|
||||
|
@ -1,320 +0,0 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.TimingLogger;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.xbill.DNS.AAAARecord;
|
||||
import org.xbill.DNS.ARecord;
|
||||
import org.xbill.DNS.Address;
|
||||
import org.xbill.DNS.Lookup;
|
||||
import org.xbill.DNS.Record;
|
||||
import org.xbill.DNS.Resolver;
|
||||
import org.xbill.DNS.SimpleResolver;
|
||||
import org.xbill.DNS.TextParseException;
|
||||
import org.xbill.DNS.Type;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import okhttp3.Dns;
|
||||
|
||||
import static org.mariotaku.twidere.TwidereConstants.HOST_MAPPING_PREFERENCES_NAME;
|
||||
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_BUILTIN_DNS_RESOLVER;
|
||||
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_DNS_SERVER;
|
||||
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_TCP_DNS_QUERY;
|
||||
|
||||
@Singleton
|
||||
public class TwidereDns implements Dns {
|
||||
|
||||
private static final String RESOLVER_LOGTAG = "TwidereDns";
|
||||
|
||||
private final SharedPreferences hostMapping;
|
||||
private final SharedPreferencesWrapper preferences;
|
||||
private final SystemHosts systemHosts;
|
||||
|
||||
private Resolver mResolver;
|
||||
private boolean mUseResolver;
|
||||
|
||||
public TwidereDns(final Context context, SharedPreferencesWrapper preferences) {
|
||||
this.preferences = preferences;
|
||||
hostMapping = SharedPreferencesWrapper.getInstance(context, HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
systemHosts = new SystemHosts();
|
||||
reloadDnsSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
|
||||
try {
|
||||
return resolveInternal(hostname, hostname, 0, mUseResolver);
|
||||
} catch (IOException e) {
|
||||
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
|
||||
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
throw new UnknownHostException("Security exception" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public List<InetAddress> lookupResolver(String hostname) throws UnknownHostException {
|
||||
try {
|
||||
return resolveInternal(hostname, hostname, 0, true);
|
||||
} catch (IOException e) {
|
||||
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
|
||||
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
throw new UnknownHostException("Security exception" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadDnsSettings() {
|
||||
mResolver = null;
|
||||
mUseResolver = preferences.getBoolean(KEY_BUILTIN_DNS_RESOLVER);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<InetAddress> resolveInternal(final String originalHost, final String host, final int depth,
|
||||
final boolean useResolver) throws IOException, SecurityException {
|
||||
final TimingLogger logger = new TimingLogger(RESOLVER_LOGTAG, "resolve");
|
||||
// Return if host is an address
|
||||
final List<InetAddress> fromAddressString = fromAddressString(originalHost, host);
|
||||
if (fromAddressString != null) {
|
||||
addLogSplit(logger, host, "valid ip address", depth);
|
||||
dumpLog(logger, fromAddressString);
|
||||
return fromAddressString;
|
||||
}
|
||||
// Load from custom mapping
|
||||
addLogSplit(logger, host, "start custom mapping resolve", depth);
|
||||
final List<InetAddress> fromMapping = getFromMapping(host);
|
||||
addLogSplit(logger, host, "end custom mapping resolve", depth);
|
||||
if (fromMapping != null) {
|
||||
dumpLog(logger, fromMapping);
|
||||
return fromMapping;
|
||||
}
|
||||
if (useResolver) {
|
||||
// Load from /etc/hosts, since Dnsjava doesn't support hosts entry lookup
|
||||
addLogSplit(logger, host, "start /etc/hosts resolve", depth);
|
||||
final List<InetAddress> fromSystemHosts = fromSystemHosts(host);
|
||||
addLogSplit(logger, host, "end /etc/hosts resolve", depth);
|
||||
if (fromSystemHosts != null) {
|
||||
dumpLog(logger, fromSystemHosts);
|
||||
return fromSystemHosts;
|
||||
}
|
||||
|
||||
// Use DNS resolver
|
||||
addLogSplit(logger, host, "start resolver resolve", depth);
|
||||
final List<InetAddress> fromResolver = fromResolver(originalHost, host);
|
||||
addLogSplit(logger, host, "end resolver resolve", depth);
|
||||
if (fromResolver != null) {
|
||||
dumpLog(logger, fromResolver);
|
||||
return fromResolver;
|
||||
}
|
||||
}
|
||||
addLogSplit(logger, host, "start system default resolve", depth);
|
||||
final List<InetAddress> fromDefault = Arrays.asList(InetAddress.getAllByName(host));
|
||||
addLogSplit(logger, host, "end system default resolve", depth);
|
||||
dumpLog(logger, fromDefault);
|
||||
return fromDefault;
|
||||
}
|
||||
|
||||
private void dumpLog(final TimingLogger logger, @NonNull List<InetAddress> addresses) {
|
||||
if (BuildConfig.DEBUG) return;
|
||||
Log.v(RESOLVER_LOGTAG, "Resolved " + addresses);
|
||||
logger.dumpToLog();
|
||||
}
|
||||
|
||||
|
||||
private void addLogSplit(final TimingLogger logger, String host, String message, int depth) {
|
||||
if (BuildConfig.DEBUG) return;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < depth; i++) {
|
||||
sb.append(">");
|
||||
}
|
||||
sb.append(" ");
|
||||
sb.append(host);
|
||||
sb.append(": ");
|
||||
sb.append(message);
|
||||
logger.addSplit(sb.toString());
|
||||
}
|
||||
|
||||
private List<InetAddress> fromSystemHosts(String host) {
|
||||
try {
|
||||
return systemHosts.resolve(host);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<InetAddress> fromResolver(String originalHost, String host) throws IOException {
|
||||
final Resolver resolver = getResolver();
|
||||
final Record[] records = lookupHostName(resolver, host, true);
|
||||
final List<InetAddress> addrs = new ArrayList<>(records.length);
|
||||
for (Record record : records) {
|
||||
addrs.add(addrFromRecord(originalHost, record));
|
||||
}
|
||||
if (addrs.isEmpty()) return null;
|
||||
return addrs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<InetAddress> getFromMapping(final String host) throws UnknownHostException {
|
||||
return getFromMappingInternal(host, host, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<InetAddress> getFromMappingInternal(String host, String origHost, boolean checkRecursive) throws UnknownHostException {
|
||||
if (checkRecursive && hostMatches(host, origHost)) {
|
||||
// Recursive resolution, stop this call
|
||||
return null;
|
||||
}
|
||||
for (final Entry<String, ?> entry : hostMapping.getAll().entrySet()) {
|
||||
if (hostMatches(host, entry.getKey())) {
|
||||
final String value = (String) entry.getValue();
|
||||
final InetAddress resolved = getResolvedIPAddress(origHost, value);
|
||||
if (resolved == null) {
|
||||
// Maybe another hostname
|
||||
return getFromMappingInternal(value, origHost, true);
|
||||
}
|
||||
return Collections.singletonList(resolved);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Resolver getResolver() throws IOException {
|
||||
if (mResolver != null) return mResolver;
|
||||
final boolean tcp = preferences.getBoolean(KEY_TCP_DNS_QUERY, false);
|
||||
final String address = preferences.getString(KEY_DNS_SERVER, null);
|
||||
final SimpleResolver resolver;
|
||||
if (!TextUtils.isEmpty(address) && isValidIpAddress(address)) {
|
||||
resolver = new SimpleResolver(address);
|
||||
} else {
|
||||
resolver = new SimpleResolver();
|
||||
}
|
||||
resolver.setTCP(tcp);
|
||||
return mResolver = resolver;
|
||||
}
|
||||
|
||||
|
||||
private static boolean hostMatches(final String host, final String rule) {
|
||||
if (rule == null || host == null) return false;
|
||||
if (rule.startsWith(".")) return StringUtils.endsWithIgnoreCase(host, rule);
|
||||
return host.equalsIgnoreCase(rule);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private List<InetAddress> fromAddressString(String host, String address)
|
||||
throws UnknownHostException {
|
||||
final InetAddress resolved = getResolvedIPAddress(host, address);
|
||||
if (resolved == null) return null;
|
||||
return Collections.singletonList(resolved);
|
||||
}
|
||||
|
||||
public static InetAddress getResolvedIPAddress(@NonNull final String host,
|
||||
@NonNull final String address)
|
||||
throws UnknownHostException {
|
||||
byte[] bytes;
|
||||
bytes = Address.toByteArray(address, Address.IPv4);
|
||||
if (bytes != null)
|
||||
return InetAddress.getByAddress(host, bytes);
|
||||
bytes = Address.toByteArray(address, Address.IPv6);
|
||||
if (bytes != null)
|
||||
return InetAddress.getByAddress(host, bytes);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int getInetAddressType(@NonNull final String address) {
|
||||
byte[] bytes;
|
||||
bytes = Address.toByteArray(address, Address.IPv4);
|
||||
if (bytes != null)
|
||||
return Address.IPv4;
|
||||
bytes = Address.toByteArray(address, Address.IPv6);
|
||||
if (bytes != null)
|
||||
return Address.IPv6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean isValidIpAddress(@NonNull final String address) {
|
||||
return getInetAddressType(address) != 0;
|
||||
}
|
||||
|
||||
private static Record[] lookupHostName(Resolver resolver, String name, boolean all) throws UnknownHostException {
|
||||
try {
|
||||
Lookup lookup = newLookup(resolver, name, Type.A);
|
||||
Record[] a = lookup.run();
|
||||
if (a == null) {
|
||||
if (lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
|
||||
Record[] aaaa = newLookup(resolver, name, Type.AAAA).run();
|
||||
if (aaaa != null)
|
||||
return aaaa;
|
||||
}
|
||||
throw new UnknownHostException("unknown host");
|
||||
}
|
||||
if (!all)
|
||||
return a;
|
||||
Record[] aaaa = newLookup(resolver, name, Type.AAAA).run();
|
||||
if (aaaa == null)
|
||||
return a;
|
||||
Record[] merged = new Record[a.length + aaaa.length];
|
||||
System.arraycopy(a, 0, merged, 0, a.length);
|
||||
System.arraycopy(aaaa, 0, merged, a.length, aaaa.length);
|
||||
return merged;
|
||||
} catch (TextParseException e) {
|
||||
throw new UnknownHostException("invalid name");
|
||||
}
|
||||
}
|
||||
|
||||
private static Lookup newLookup(Resolver resolver, String name, int type) throws TextParseException {
|
||||
final Lookup lookup = new Lookup(name, type);
|
||||
lookup.setResolver(resolver);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private static InetAddress addrFromRecord(String name, Record r) throws UnknownHostException {
|
||||
InetAddress addr;
|
||||
if (r instanceof ARecord) {
|
||||
addr = ((ARecord) r).getAddress();
|
||||
} else {
|
||||
addr = ((AAAARecord) r).getAddress();
|
||||
}
|
||||
return InetAddress.getByAddress(name, addr.getAddress());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -67,4 +67,23 @@ inline fun <R> Cursor.useCursor(block: (Cursor) -> R): R {
|
||||
}
|
||||
|
||||
val Cursor.isEmpty: Boolean
|
||||
get() = count == 0
|
||||
get() = count == 0
|
||||
|
||||
|
||||
/**
|
||||
* @param limit -1 for no limit
|
||||
* @return Remaining count, -1 if no rows present
|
||||
*/
|
||||
inline fun Cursor.forEachRow(limit: Int = -1, action: (cur: Cursor, pos: Int) -> Boolean): Int {
|
||||
moveToFirst()
|
||||
var current = 0
|
||||
while (!isAfterLast) {
|
||||
@Suppress("ConvertTwoComparisonsToRangeCheck")
|
||||
if (limit >= 0 && current >= limit) break
|
||||
if (action(this, current)) {
|
||||
current++
|
||||
}
|
||||
moveToNext()
|
||||
}
|
||||
return count - position
|
||||
}
|
||||
|
@ -341,7 +341,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
|
||||
setSignInButton()
|
||||
if (result.alreadyLoggedIn) {
|
||||
result.updateAccount(am)
|
||||
deleteAccountData(contentResolver, result.user.key)
|
||||
contentResolver.deleteAccountData(result.user.key)
|
||||
Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
result.addAccount(am, preferences[randomizeAccountNameKey])
|
||||
|
@ -381,11 +381,6 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
||||
val status = adapter.getStatus(statusPosition)
|
||||
IntentUtils.openMedia(activity, status, current, preferences[newDocumentApiKey],
|
||||
preferences[displaySensitiveContentsKey])
|
||||
// BEGIN HotMobi
|
||||
val event = MediaEvent.create(activity, status, current, timelineType,
|
||||
adapter.mediaPreviewEnabled)
|
||||
HotMobiLogger.getInstance(activity).log(status.account_key, event)
|
||||
// END HotMobi
|
||||
}
|
||||
|
||||
override fun onQuotedMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia,
|
||||
@ -394,11 +389,6 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
||||
val quotedMedia = status.quoted_media ?: return
|
||||
IntentUtils.openMedia(activity, status.account_key, status.is_possibly_sensitive, status,
|
||||
current, quotedMedia, preferences[newDocumentApiKey], preferences[displaySensitiveContentsKey])
|
||||
// BEGIN HotMobi
|
||||
val event = MediaEvent.create(activity, status, current, timelineType,
|
||||
adapter.mediaPreviewEnabled)
|
||||
HotMobiLogger.getInstance(activity).log(status.account_key, event)
|
||||
// END HotMobi
|
||||
}
|
||||
|
||||
override fun onItemActionClick(holder: RecyclerView.ViewHolder, id: Int, position: Int) {
|
||||
|
@ -220,7 +220,7 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
val accountKey = account.getAccountKey(am)
|
||||
deleteAccountData(resolver, accountKey)
|
||||
resolver.deleteAccountData(accountKey)
|
||||
am.removeAccountSupport(account)
|
||||
}
|
||||
}
|
||||
|
@ -308,10 +308,14 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
|
||||
val status = adapter.getStatus(statusPosition)
|
||||
IntentUtils.openMedia(activity, status, current, preferences[newDocumentApiKey],
|
||||
preferences[displaySensitiveContentsKey])
|
||||
}
|
||||
|
||||
val event = MediaEvent.create(activity, status, current, TimelineType.DETAILS,
|
||||
adapter.mediaPreviewEnabled)
|
||||
HotMobiLogger.getInstance(activity).log(status.account_key, event)
|
||||
override fun onQuotedMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia, statusPosition: Int) {
|
||||
val status = adapter.getStatus(statusPosition)
|
||||
val quotedMedia = status.quoted_media ?: return
|
||||
IntentUtils.openMedia(activity, status.account_key, status.is_possibly_sensitive, status,
|
||||
current, quotedMedia, preferences[newDocumentApiKey],
|
||||
preferences[displaySensitiveContentsKey])
|
||||
}
|
||||
|
||||
override fun onGapClick(holder: GapViewHolder, position: Int) {
|
||||
|
@ -85,7 +85,7 @@ class ParcelableStatusLoader(
|
||||
// Delete all deleted status
|
||||
val cr = context.contentResolver
|
||||
DataStoreUtils.deleteStatus(cr, accountKey, statusId, null)
|
||||
deleteActivityStatus(cr, accountKey, statusId, null)
|
||||
cr.deleteActivityStatus(accountKey, statusId, null)
|
||||
}
|
||||
return SingleResponse(e)
|
||||
}
|
||||
|
@ -22,17 +22,14 @@ package org.mariotaku.twidere.receiver
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import edu.tsinghua.hotmobi.model.NotificationEvent
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.ktextension.toLong
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.NotificationType
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.constant.IntentConstants.BROADCAST_NOTIFICATION_DELETED
|
||||
import org.mariotaku.twidere.model.Tab
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.util.UriExtraUtils
|
||||
import org.mariotaku.twidere.task.twitter.message.BatchMarkMessageReadTask
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
|
||||
@ -50,39 +47,30 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
@NotificationType
|
||||
val notificationType = uri.getQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE)
|
||||
val accountKey = uri.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY)?.let(UserKey::valueOf)
|
||||
val itemId = UriExtraUtils.getExtra(uri, "item_id").toLong(-1)
|
||||
val itemUserId = UriExtraUtils.getExtra(uri, "item_user_id").toLong(-1)
|
||||
val itemUserFollowing = UriExtraUtils.getExtra(uri, "item_user_following")?.toBoolean() ?: false
|
||||
val timestamp = uri.getQueryParameter(QUERY_PARAM_TIMESTAMP)?.toLong() ?: -1
|
||||
if (CustomTabType.NOTIFICATIONS_TIMELINE == Tab.getTypeAlias(notificationType)
|
||||
&& accountKey != null && itemId != -1L && timestamp != -1L) {
|
||||
val logger = holder.hotMobiLogger
|
||||
logger.log(accountKey, NotificationEvent.deleted(context, timestamp, notificationType, accountKey,
|
||||
itemId, itemUserId, itemUserFollowing))
|
||||
}
|
||||
val manager = holder.readStateManager
|
||||
val paramReadPosition = uri.getQueryParameter(QUERY_PARAM_READ_POSITION)
|
||||
@ReadPositionTag
|
||||
val tag = getPositionTag(notificationType)
|
||||
|
||||
if (tag != null && !TextUtils.isEmpty(paramReadPosition)) {
|
||||
manager.setPosition(Utils.getReadPositionTagWithAccount(tag, accountKey),
|
||||
paramReadPosition.toLong(-1))
|
||||
when (notificationType) {
|
||||
NotificationType.HOME_TIMELINE -> {
|
||||
val positionTag = Utils.getReadPositionTagWithAccount(ReadPositionTag.HOME_TIMELINE,
|
||||
accountKey)
|
||||
val manager = holder.readStateManager
|
||||
manager.setPosition(positionTag, paramReadPosition.toLong(-1))
|
||||
}
|
||||
NotificationType.INTERACTIONS -> {
|
||||
val positionTag = Utils.getReadPositionTagWithAccount(ReadPositionTag.ACTIVITIES_ABOUT_ME,
|
||||
accountKey)
|
||||
val manager = holder.readStateManager
|
||||
manager.setPosition(positionTag, paramReadPosition.toLong(-1))
|
||||
}
|
||||
NotificationType.DIRECT_MESSAGES -> {
|
||||
if (accountKey == null) return
|
||||
val appContext = context.applicationContext
|
||||
val task = BatchMarkMessageReadTask(appContext, accountKey,
|
||||
paramReadPosition.toLong(-1))
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
private fun getPositionTag(@NotificationType type: String?): String? {
|
||||
if (type == null) return null
|
||||
when (type) {
|
||||
NotificationType.HOME_TIMELINE -> return ReadPositionTag.HOME_TIMELINE
|
||||
NotificationType.INTERACTIONS -> return ReadPositionTag.ACTIVITIES_ABOUT_ME
|
||||
NotificationType.DIRECT_MESSAGES -> {
|
||||
return ReadPositionTag.DIRECT_MESSAGES
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class CreateFavoriteTask(
|
||||
for (uri in DataStoreUtils.STATUSES_URIS) {
|
||||
resolver.update(uri, values, statusWhere, statusWhereArgs)
|
||||
}
|
||||
updateActivityStatus(resolver, accountKey, statusId) { activity ->
|
||||
resolver.updateActivityStatus(accountKey, statusId) { activity ->
|
||||
val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses)
|
||||
for (statusesArray in statusesMatrix) {
|
||||
if (statusesArray == null) continue
|
||||
|
@ -66,7 +66,7 @@ class DestroyFavoriteTask(
|
||||
resolver.update(uri, values, where.sql, whereArgs)
|
||||
}
|
||||
|
||||
updateActivityStatus(resolver, accountKey, statusId) { activity ->
|
||||
resolver.updateActivityStatus(accountKey, statusId) { activity ->
|
||||
val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses)
|
||||
for (statusesArray in statusesMatrix) {
|
||||
if (statusesArray == null) continue
|
||||
|
@ -46,7 +46,7 @@ class DestroyStatusTask(
|
||||
} finally {
|
||||
if (deleteStatus) {
|
||||
DataStoreUtils.deleteStatus(context.contentResolver, accountKey, statusId, status)
|
||||
deleteActivityStatus(context.contentResolver, accountKey, statusId, status)
|
||||
context.contentResolver.deleteActivityStatus(accountKey, statusId, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class RetweetStatusTask(
|
||||
for (uri in DataStoreUtils.STATUSES_URIS) {
|
||||
resolver.update(uri, values, where.sql, whereArgs)
|
||||
}
|
||||
updateActivityStatus(resolver, accountKey, statusId) { activity ->
|
||||
resolver.updateActivityStatus(accountKey, statusId) { activity ->
|
||||
val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses)
|
||||
activity.status_my_retweet_id = result.my_retweet_id
|
||||
for (statusesArray in statusesMatrix) {
|
||||
|
@ -86,8 +86,8 @@ abstract class GetActivitiesTask(
|
||||
// We should delete old activities has intersection with new items
|
||||
try {
|
||||
val activities = getActivities(microBlog, credentials, paging)
|
||||
val storeResult = storeActivities(cr, loadItemLimit, credentials, noItemsBefore, activities, sinceId,
|
||||
maxId, false)
|
||||
val storeResult = storeActivities(cr, loadItemLimit, credentials, noItemsBefore,
|
||||
activities, sinceId, maxId, false)
|
||||
if (saveReadPosition) {
|
||||
saveReadPosition(accountKey, credentials, microBlog)
|
||||
}
|
||||
|
@ -106,8 +106,8 @@ abstract class GetStatusesTask(
|
||||
sinceId = null
|
||||
}
|
||||
val statuses = getStatuses(microBlog, paging)
|
||||
val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId, sinceSortId,
|
||||
maxSortId, loadItemLimit, false)
|
||||
val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId,
|
||||
sinceSortId, maxSortId, loadItemLimit, false)
|
||||
// TODO cache related data and preload
|
||||
val cacheTask = CacheUsersStatusesTask(context, accountKey, details.type, statuses)
|
||||
TaskStarter.execute(cacheTask)
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.task.twitter.message
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Context
|
||||
import org.mariotaku.ktextension.forEachRow
|
||||
import org.mariotaku.ktextension.useCursor
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.ParcelableMessageConversation
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
|
||||
import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask
|
||||
import org.mariotaku.twidere.util.TwidereQueryBuilder
|
||||
import org.mariotaku.twidere.util.getUnreadMessagesEntriesCursor
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/16.
|
||||
*/
|
||||
|
||||
class BatchMarkMessageReadTask(
|
||||
context: Context,
|
||||
val accountKey: UserKey,
|
||||
val markTimestampBefore: Long
|
||||
) : ExceptionHandlingAbstractTask<Unit?, Boolean, MicroBlogException, Unit?>(context) {
|
||||
|
||||
override val exceptionClass = MicroBlogException::class.java
|
||||
|
||||
override fun onExecute(params: Unit?): Boolean {
|
||||
val cr = context.contentResolver
|
||||
val projection = Conversations.COLUMNS.map {
|
||||
TwidereQueryBuilder.mapConversationsProjection(it)
|
||||
}.toTypedArray()
|
||||
val cur = cr.getUnreadMessagesEntriesCursor(projection, arrayOf(accountKey),
|
||||
markTimestampBefore) ?: return false
|
||||
|
||||
val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?:
|
||||
throw MicroBlogException("No account")
|
||||
val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java)
|
||||
cur.useCursor {
|
||||
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessageConversation::class.java)
|
||||
cur.forEachRow { cur, _ ->
|
||||
val conversation = indices.newObject(cur)
|
||||
try {
|
||||
MarkMessageReadTask.performMarkRead(context, microBlog, account, conversation)
|
||||
return@forEachRow true
|
||||
} catch (e: MicroBlogException) {
|
||||
return@forEachRow false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,7 @@ package org.mariotaku.twidere.task.twitter.message
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
@ -64,7 +65,7 @@ class MarkMessageReadTask(
|
||||
val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java)
|
||||
val conversation = DataStoreUtils.findMessageConversation(context, accountKey, conversationId)
|
||||
val lastReadEvent = conversation?.let {
|
||||
return@let performMarkRead(microBlog, account, conversation)
|
||||
return@let performMarkRead(context, microBlog, account, conversation)
|
||||
} ?: return false
|
||||
val values = ContentValues()
|
||||
values.put(Conversations.LAST_READ_ID, lastReadEvent.first)
|
||||
@ -82,53 +83,59 @@ class MarkMessageReadTask(
|
||||
bus.post(UnreadCountUpdatedEvent(-1))
|
||||
}
|
||||
|
||||
private fun performMarkRead(microBlog: MicroBlog, account: AccountDetails,
|
||||
conversation: ParcelableMessageConversation): Pair<String, Long>? {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val event = (conversation.conversation_extras as? TwitterOfficialConversationExtras)?.maxReadEvent ?: run {
|
||||
val message = findRecentMessage(accountKey, conversationId) ?: return null
|
||||
return@run Pair(message.id, message.timestamp)
|
||||
}
|
||||
if (conversation.last_read_timestamp > event.second) {
|
||||
// Local is newer, ignore network request
|
||||
return event
|
||||
}
|
||||
if (microBlog.markDmRead(conversation.id, event.first).isSuccessful) {
|
||||
return event
|
||||
|
||||
companion object {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
internal fun performMarkRead(context: Context, microBlog: MicroBlog, account: AccountDetails,
|
||||
conversation: ParcelableMessageConversation): Pair<String, Long>? {
|
||||
val cr = context.contentResolver
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val event = (conversation.conversation_extras as? TwitterOfficialConversationExtras)?.maxReadEvent ?: run {
|
||||
val message = cr.findRecentMessage(account.key, conversation.id) ?: return null
|
||||
return@run Pair(message.id, message.timestamp)
|
||||
}
|
||||
if (conversation.last_read_timestamp > event.second) {
|
||||
// Local is newer, ignore network request
|
||||
return event
|
||||
}
|
||||
if (microBlog.markDmRead(conversation.id, event.first).isSuccessful) {
|
||||
return event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val message = cr.findRecentMessage(account.key, conversation.id) ?: return null
|
||||
return Pair(message.id, message.timestamp)
|
||||
}
|
||||
val message = findRecentMessage(accountKey, conversationId) ?: return null
|
||||
return Pair(message.id, message.timestamp)
|
||||
}
|
||||
|
||||
|
||||
private fun findRecentMessage(accountKey: UserKey, conversationId: String): ParcelableMessage? {
|
||||
val where = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
|
||||
val whereArgs = arrayOf(accountKey.toString(), conversationId)
|
||||
@SuppressLint("Recycle")
|
||||
val cur = context.contentResolver.query(Messages.CONTENT_URI, Messages.COLUMNS,
|
||||
where, whereArgs, OrderBy(Messages.LOCAL_TIMESTAMP, false).sql) ?: return null
|
||||
try {
|
||||
if (cur.moveToFirst()) {
|
||||
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessage::class.java)
|
||||
return indices.newObject(cur)
|
||||
private fun ContentResolver.findRecentMessage(accountKey: UserKey, conversationId: String): ParcelableMessage? {
|
||||
val where = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
|
||||
val whereArgs = arrayOf(accountKey.toString(), conversationId)
|
||||
@SuppressLint("Recycle")
|
||||
val cur = query(Messages.CONTENT_URI, Messages.COLUMNS,
|
||||
where, whereArgs, OrderBy(Messages.LOCAL_TIMESTAMP, false).sql) ?: return null
|
||||
try {
|
||||
if (cur.moveToFirst()) {
|
||||
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessage::class.java)
|
||||
return indices.newObject(cur)
|
||||
}
|
||||
} finally {
|
||||
cur.close()
|
||||
}
|
||||
} finally {
|
||||
cur.close()
|
||||
return null
|
||||
}
|
||||
return null
|
||||
|
||||
private val TwitterOfficialConversationExtras.maxReadEvent: Pair<String, Long>?
|
||||
get() {
|
||||
val id = maxEntryId ?: return null
|
||||
if (maxEntryTimestamp < 0) return null
|
||||
return Pair(id, maxEntryTimestamp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val TwitterOfficialConversationExtras.maxReadEvent: Pair<String, Long>?
|
||||
get() {
|
||||
val id = maxEntryId ?: return null
|
||||
if (maxEntryTimestamp < 0) return null
|
||||
return Pair(id, maxEntryTimestamp)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.database.Cursor
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.forEachRow
|
||||
import org.mariotaku.ktextension.isEmpty
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity
|
||||
@ -80,10 +80,10 @@ class ContentNotificationManager(
|
||||
val accountKey = pref.accountKey
|
||||
val resources = context.resources
|
||||
val selection = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||
Expression.greaterThan(Statuses.POSITION_KEY, minPositionKey))
|
||||
Expression.greaterThanArgs(Statuses.POSITION_KEY))
|
||||
val selectionArgs = arrayOf(accountKey.toString(), minPositionKey.toString())
|
||||
val filteredSelection = buildStatusFilterWhereClause(preferences, Statuses.TABLE_NAME,
|
||||
selection)
|
||||
val selectionArgs = arrayOf(accountKey.toString())
|
||||
val userProjection = arrayOf(Statuses.USER_KEY, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME)
|
||||
val statusProjection = arrayOf(Statuses.POSITION_KEY)
|
||||
|
||||
@ -105,7 +105,11 @@ class ContentNotificationManager(
|
||||
if (statusesCount == 0 || usersCount == 0) return
|
||||
val statusIndices = ObjectCursor.indicesFrom(statusCursor, ParcelableStatus::class.java)
|
||||
val userIndices = ObjectCursor.indicesFrom(userCursor, ParcelableStatus::class.java)
|
||||
val positionKey = if (statusCursor.moveToFirst()) statusCursor.getLong(statusIndices[Statuses.POSITION_KEY]) else -1L
|
||||
val positionKey = if (statusCursor.moveToFirst()) {
|
||||
statusCursor.getLong(statusIndices[Statuses.POSITION_KEY])
|
||||
} else {
|
||||
-1L
|
||||
}
|
||||
val notificationTitle = resources.getQuantityString(R.plurals.N_new_statuses,
|
||||
statusesCount, statusesCount)
|
||||
val notificationContent: String
|
||||
@ -184,12 +188,17 @@ class ContentNotificationManager(
|
||||
style.setSummaryText(accountName)
|
||||
val ci = ObjectCursor.indicesFrom(c, ParcelableActivity::class.java)
|
||||
|
||||
var timestamp: Long = -1
|
||||
var timestamp = -1L
|
||||
var newMaxPositionKey = -1L
|
||||
val filteredUserIds = DataStoreUtils.getFilteredUserIds(context)
|
||||
var consumed = 0
|
||||
val remaining = c.forEachRow(5) { cur, _ ->
|
||||
|
||||
val activity = ci.newObject(cur)
|
||||
|
||||
if (newMaxPositionKey == -1L) {
|
||||
newMaxPositionKey = activity.position_key
|
||||
}
|
||||
|
||||
if (pref.isNotificationMentionsOnly && activity.action !in Activity.Action.MENTION_ACTIONS) {
|
||||
return@forEachRow false
|
||||
}
|
||||
@ -239,6 +248,8 @@ class ContentNotificationManager(
|
||||
builder.setNumber(displayCount)
|
||||
builder.setContentIntent(getContentIntent(context, CustomTabType.NOTIFICATIONS_TIMELINE,
|
||||
NotificationType.INTERACTIONS, accountKey, timestamp))
|
||||
builder.setDeleteIntent(getMarkReadDeleteIntent(context, NotificationType.INTERACTIONS,
|
||||
accountKey, newMaxPositionKey, false))
|
||||
if (timestamp != -1L) {
|
||||
builder.setDeleteIntent(getMarkReadDeleteIntent(context,
|
||||
NotificationType.INTERACTIONS, accountKey, timestamp, false))
|
||||
@ -267,7 +278,11 @@ class ContentNotificationManager(
|
||||
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessageConversation::class.java)
|
||||
|
||||
var messageSum: Int = 0
|
||||
var newLastReadTimestamp = -1L
|
||||
cur.forEachRow { cur, _ ->
|
||||
if (newLastReadTimestamp != -1L) {
|
||||
newLastReadTimestamp = cur.getLong(indices[Conversations.LAST_READ_TIMESTAMP])
|
||||
}
|
||||
messageSum += cur.getInt(indices[Conversations.UNREAD_COUNT])
|
||||
return@forEachRow true
|
||||
}
|
||||
@ -286,6 +301,9 @@ class ContentNotificationManager(
|
||||
builder.setContentTitle(notificationTitle)
|
||||
builder.setContentIntent(getContentIntent(context, CustomTabType.DIRECT_MESSAGES,
|
||||
NotificationType.DIRECT_MESSAGES, accountKey, 0))
|
||||
builder.setDeleteIntent(getMarkReadDeleteIntent(context, NotificationType.DIRECT_MESSAGES,
|
||||
accountKey, newLastReadTimestamp, false))
|
||||
|
||||
val remaining = cur.forEachRow(5) { cur, pos ->
|
||||
val conversation = indices.newObject(cur)
|
||||
if (conversation.notificationDisabled) return@forEachRow false
|
||||
@ -311,22 +329,35 @@ class ContentNotificationManager(
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param limit -1 for no limit
|
||||
* @return Remaining count, -1 if no rows present
|
||||
*/
|
||||
private inline fun Cursor.forEachRow(limit: Int = -1, action: (cur: Cursor, pos: Int) -> Boolean): Int {
|
||||
moveToFirst()
|
||||
var current = 0
|
||||
while (!isAfterLast) {
|
||||
@Suppress("ConvertTwoComparisonsToRangeCheck")
|
||||
if (limit >= 0 && current >= limit) break
|
||||
if (action(this, current)) {
|
||||
current++
|
||||
}
|
||||
moveToNext()
|
||||
fun showUserNotification(accountKey: UserKey, status: Status, userKey: UserKey) {
|
||||
// Build favorited user notifications
|
||||
val userDisplayName = userColorNameManager.getDisplayName(status.user,
|
||||
preferences[nameFirstKey])
|
||||
val statusUri = LinkCreator.getTwidereStatusLink(accountKey, status.id)
|
||||
val builder = NotificationCompat.Builder(context)
|
||||
builder.color = userColorNameManager.getUserColor(userKey)
|
||||
builder.setAutoCancel(true)
|
||||
builder.setWhen(status.createdAt?.time ?: 0)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_twitter)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
if (status.isRetweetedByMe) {
|
||||
builder.setContentTitle(context.getString(R.string.notification_title_new_retweet_by_user, userDisplayName))
|
||||
builder.setContentText(InternalTwitterContentUtils.formatStatusTextWithIndices(status.retweetedStatus).text)
|
||||
} else {
|
||||
builder.setContentTitle(context.getString(R.string.notification_title_new_status_by_user, userDisplayName))
|
||||
builder.setContentText(InternalTwitterContentUtils.formatStatusTextWithIndices(status).text)
|
||||
}
|
||||
return count - position
|
||||
builder.setContentIntent(PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, statusUri).apply {
|
||||
setClass(context, LinkHandlerActivity::class.java)
|
||||
}, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
|
||||
val tag = "$accountKey:$userKey:${status.id}"
|
||||
notificationManager.notify(tag, NOTIFICATION_ID_USER_NOTIFICATION, builder.build())
|
||||
}
|
||||
|
||||
fun updatePreferences() {
|
||||
nameFirst = preferences[nameFirstKey]
|
||||
useStarForLikes = preferences[iWantMyStarsBackKey]
|
||||
}
|
||||
|
||||
private fun applyNotificationPreferences(builder: NotificationCompat.Builder, pref: AccountPreferences, defaultFlags: Int) {
|
||||
@ -355,7 +386,6 @@ class ContentNotificationManager(
|
||||
return !activityTracker.isHomeActivityStarted
|
||||
}
|
||||
|
||||
|
||||
private fun getContentIntent(context: Context, @CustomTabType type: String,
|
||||
@NotificationType notificationType: String, accountKey: UserKey?, readPosition: Long): PendingIntent {
|
||||
// Setup click intent
|
||||
@ -376,65 +406,28 @@ class ContentNotificationManager(
|
||||
return PendingIntent.getActivity(context, 0, homeIntent, 0)
|
||||
}
|
||||
|
||||
fun updatePreferences() {
|
||||
nameFirst = preferences[nameFirstKey]
|
||||
useStarForLikes = preferences[iWantMyStarsBackKey]
|
||||
}
|
||||
|
||||
private fun getMarkReadDeleteIntent(context: Context, @NotificationType type: String,
|
||||
accountKey: UserKey?, position: Long,
|
||||
extraUserFollowing: Boolean): PendingIntent {
|
||||
return getMarkReadDeleteIntent(context, type, accountKey, position, -1, -1, extraUserFollowing)
|
||||
return getMarkReadDeleteIntent(context, type, accountKey, position)
|
||||
}
|
||||
|
||||
private fun getMarkReadDeleteIntent(context: Context, @NotificationType type: String,
|
||||
accountKey: UserKey?, position: Long,
|
||||
extraId: Long, extraUserId: Long,
|
||||
extraUserFollowing: Boolean): PendingIntent {
|
||||
accountKey: UserKey?, position: Long): PendingIntent {
|
||||
// Setup delete intent
|
||||
val intent = Intent(context, NotificationReceiver::class.java)
|
||||
intent.action = IntentConstants.BROADCAST_NOTIFICATION_DELETED
|
||||
val linkBuilder = Uri.Builder()
|
||||
linkBuilder.scheme(SCHEME_TWIDERE)
|
||||
linkBuilder.authority(AUTHORITY_INTERACTIONS)
|
||||
linkBuilder.authority(AUTHORITY_NOTIFICATIONS)
|
||||
linkBuilder.appendPath(type)
|
||||
if (accountKey != null) {
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_KEY, accountKey.toString())
|
||||
}
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, position.toString())
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, System.currentTimeMillis().toString())
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type)
|
||||
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_id", extraId)
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_user_id", extraUserId)
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_user_following", extraUserFollowing)
|
||||
intent.data = linkBuilder.build()
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
fun showUserNotification(accountKey: UserKey, status: Status, userKey: UserKey) {
|
||||
// Build favorited user notifications
|
||||
val userDisplayName = userColorNameManager.getDisplayName(status.user,
|
||||
preferences[nameFirstKey])
|
||||
val statusUri = LinkCreator.getTwidereStatusLink(accountKey, status.id)
|
||||
val builder = NotificationCompat.Builder(context)
|
||||
builder.color = userColorNameManager.getUserColor(userKey)
|
||||
builder.setAutoCancel(true)
|
||||
builder.setWhen(status.createdAt?.time ?: 0)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_twitter)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
if (status.isRetweetedByMe) {
|
||||
builder.setContentTitle(context.getString(R.string.notification_title_new_retweet_by_user, userDisplayName))
|
||||
builder.setContentText(InternalTwitterContentUtils.formatStatusTextWithIndices(status.retweetedStatus).text)
|
||||
} else {
|
||||
builder.setContentTitle(context.getString(R.string.notification_title_new_status_by_user, userDisplayName))
|
||||
builder.setContentText(InternalTwitterContentUtils.formatStatusTextWithIndices(status).text)
|
||||
}
|
||||
builder.setContentIntent(PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, statusUri).apply {
|
||||
setClass(context, LinkHandlerActivity::class.java)
|
||||
}, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
|
||||
val tag = "$accountKey:$userKey:${status.id}"
|
||||
notificationManager.notify(tag, NOTIFICATION_ID_USER_NOTIFICATION, builder.build())
|
||||
}
|
||||
}
|
@ -32,8 +32,7 @@ import java.io.IOException
|
||||
* Created by mariotaku on 2016/12/24.
|
||||
*/
|
||||
|
||||
fun buildStatusFilterWhereClause(preferences: SharedPreferences,
|
||||
table: String,
|
||||
fun buildStatusFilterWhereClause(preferences: SharedPreferences, table: String,
|
||||
extraSelection: Expression?): Expression {
|
||||
val filteredUsersQuery = SQLQueryBuilder
|
||||
.select(Column(Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY))
|
||||
@ -116,20 +115,20 @@ fun deleteDrafts(context: Context, draftIds: LongArray): Int {
|
||||
return context.contentResolver.delete(Drafts.CONTENT_URI, where, whereArgs)
|
||||
}
|
||||
|
||||
fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) {
|
||||
fun ContentResolver.deleteAccountData(accountKey: UserKey) {
|
||||
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
||||
val whereArgs = arrayOf(accountKey.toString())
|
||||
// Also delete tweets related to the account we previously
|
||||
// deleted.
|
||||
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Activities.AboutMe.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Messages.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Messages.Conversations.CONTENT_URI, where, whereArgs)
|
||||
delete(Statuses.CONTENT_URI, where, whereArgs)
|
||||
delete(Activities.AboutMe.CONTENT_URI, where, whereArgs)
|
||||
delete(Messages.CONTENT_URI, where, whereArgs)
|
||||
delete(Conversations.CONTENT_URI, where, whereArgs)
|
||||
}
|
||||
|
||||
|
||||
fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
|
||||
statusId: String, result: ParcelableStatus?) {
|
||||
fun ContentResolver.deleteActivityStatus(accountKey: UserKey, statusId: String,
|
||||
result: ParcelableStatus?) {
|
||||
|
||||
val host = accountKey.host
|
||||
val deleteWhere: String
|
||||
@ -159,8 +158,8 @@ fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
|
||||
updateWhereArgs = arrayOf(statusId)
|
||||
}
|
||||
for (uri in ACTIVITIES_URIS) {
|
||||
cr.delete(uri, deleteWhere, deleteWhereArgs)
|
||||
updateActivity(cr, uri, updateWhere, updateWhereArgs) { activity ->
|
||||
delete(uri, deleteWhere, deleteWhereArgs)
|
||||
updateActivity(uri, updateWhere, updateWhereArgs) { activity ->
|
||||
activity.status_my_retweet_id = null
|
||||
arrayOf(activity.target_statuses, activity.target_object_statuses).filterNotNull().forEach {
|
||||
for (status in it) {
|
||||
@ -178,9 +177,7 @@ fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
|
||||
}
|
||||
}
|
||||
|
||||
fun updateActivityStatus(resolver: ContentResolver,
|
||||
accountKey: UserKey,
|
||||
statusId: String,
|
||||
fun ContentResolver.updateActivityStatus(accountKey: UserKey, statusId: String,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
val activityWhere = Expression.and(
|
||||
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
@ -191,16 +188,15 @@ fun updateActivityStatus(resolver: ContentResolver,
|
||||
).sql
|
||||
val activityWhereArgs = arrayOf(accountKey.toString(), statusId, statusId)
|
||||
for (uri in ACTIVITIES_URIS) {
|
||||
updateActivity(resolver, uri, activityWhere, activityWhereArgs, action)
|
||||
updateActivity(uri, activityWhere, activityWhereArgs, action)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@WorkerThread
|
||||
fun updateActivity(cr: ContentResolver, uri: Uri,
|
||||
where: String?, whereArgs: Array<String>?,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
val c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null) ?: return
|
||||
fun ContentResolver.updateActivity(uri: Uri, where: String?,
|
||||
whereArgs: Array<String>?, action: (ParcelableActivity) -> Unit) {
|
||||
val c = query(uri, Activities.COLUMNS, where, whereArgs, null) ?: return
|
||||
val values = LongSparseArray<ContentValues>()
|
||||
try {
|
||||
val ci = ObjectCursor.indicesFrom(c, ParcelableActivity::class.java)
|
||||
@ -221,11 +217,12 @@ fun updateActivity(cr: ContentResolver, uri: Uri,
|
||||
val updateWhereArgs = arrayOfNulls<String>(1)
|
||||
for (i in 0 until values.size()) {
|
||||
updateWhereArgs[0] = values.keyAt(i).toString()
|
||||
cr.update(uri, values.valueAt(i), updateWhere, updateWhereArgs)
|
||||
update(uri, values.valueAt(i), updateWhere, updateWhereArgs)
|
||||
}
|
||||
}
|
||||
|
||||
fun ContentResolver.getUnreadMessagesEntriesCursor(projection: Array<Columns.Column>, accountKeys: Array<UserKey>): Cursor? {
|
||||
fun ContentResolver.getUnreadMessagesEntriesCursor(projection: Array<Columns.Column>,
|
||||
accountKeys: Array<UserKey>, timestampBefore: Long = -1): Cursor? {
|
||||
val qb = SQLQueryBuilder.select(Columns(*projection))
|
||||
qb.from(Table(Conversations.TABLE_NAME))
|
||||
qb.join(Join(false, Join.Operation.LEFT_OUTER, Table(Messages.TABLE_NAME),
|
||||
@ -234,15 +231,27 @@ fun ContentResolver.getUnreadMessagesEntriesCursor(projection: Array<Columns.Col
|
||||
Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID)
|
||||
)
|
||||
))
|
||||
qb.where(Expression.and(
|
||||
val whereConditions = arrayOf(
|
||||
Expression.inArgs(Column(Table(Conversations.TABLE_NAME), Conversations.ACCOUNT_KEY),
|
||||
accountKeys.size),
|
||||
Expression.lesserThan(Column(Table(Conversations.TABLE_NAME), Conversations.LAST_READ_TIMESTAMP),
|
||||
Column(Table(Conversations.TABLE_NAME), Conversations.LOCAL_TIMESTAMP))
|
||||
))
|
||||
)
|
||||
if (timestampBefore >= 0) {
|
||||
val beforeCondition = Expression.greaterThan(Column(Table(Conversations.TABLE_NAME),
|
||||
Conversations.LAST_READ_TIMESTAMP), RawSQLLang("?"))
|
||||
qb.where(Expression.and(*(whereConditions + beforeCondition)))
|
||||
} else {
|
||||
qb.where(Expression.and(*whereConditions))
|
||||
}
|
||||
qb.groupBy(Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID))
|
||||
qb.orderBy(OrderBy(arrayOf(Column(Table(Conversations.TABLE_NAME), Conversations.LOCAL_TIMESTAMP),
|
||||
Column(Table(Conversations.TABLE_NAME), Conversations.SORT_ID)), booleanArrayOf(false, false)))
|
||||
val selectionArgs = accountKeys.toStringArray()
|
||||
|
||||
val selectionArgs = if (timestampBefore >= 0) {
|
||||
accountKeys.toStringArray() + timestampBefore.toString()
|
||||
} else {
|
||||
accountKeys.toStringArray()
|
||||
}
|
||||
return rawQuery(qb.buildSQL(), selectionArgs)
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.util.TimingLogger
|
||||
import okhttp3.Dns
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.mariotaku.ktextension.toInt
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.TwidereConstants.HOST_MAPPING_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper
|
||||
import org.xbill.DNS.*
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
import java.util.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TwidereDns(context: Context, private val preferences: SharedPreferences) : Dns {
|
||||
|
||||
private val hostMapping: SharedPreferences
|
||||
private val systemHosts: SystemHosts
|
||||
|
||||
private var resolver: Resolver? = null
|
||||
private var useResolver: Boolean = false
|
||||
|
||||
init {
|
||||
hostMapping = SharedPreferencesWrapper.getInstance(context, HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
systemHosts = SystemHosts()
|
||||
reloadDnsSettings()
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
try {
|
||||
return resolveInternal(hostname, hostname, 0, useResolver)
|
||||
} catch (e: IOException) {
|
||||
if (e is UnknownHostException) throw e
|
||||
throw UnknownHostException("Unable to resolve address " + e.message)
|
||||
} catch (e: SecurityException) {
|
||||
throw UnknownHostException("Security exception" + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
fun lookupResolver(hostname: String): List<InetAddress> {
|
||||
try {
|
||||
return resolveInternal(hostname, hostname, 0, true)
|
||||
} catch (e: IOException) {
|
||||
if (e is UnknownHostException) throw e
|
||||
throw UnknownHostException("Unable to resolve address " + e.message)
|
||||
} catch (e: SecurityException) {
|
||||
throw UnknownHostException("Security exception" + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun reloadDnsSettings() {
|
||||
this.resolver = null
|
||||
useResolver = preferences.getBoolean(KEY_BUILTIN_DNS_RESOLVER, false)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, SecurityException::class)
|
||||
private fun resolveInternal(originalHost: String, host: String, depth: Int,
|
||||
useResolver: Boolean): List<InetAddress> {
|
||||
val logger = TimingLogger(RESOLVER_LOGTAG, "resolve")
|
||||
// Return if host is an address
|
||||
val fromAddressString = fromAddressString(originalHost, host)
|
||||
if (fromAddressString != null) {
|
||||
addLogSplit(logger, host, "valid ip address", depth)
|
||||
dumpLog(logger, fromAddressString)
|
||||
return fromAddressString
|
||||
}
|
||||
// Load from custom mapping
|
||||
addLogSplit(logger, host, "start custom mapping resolve", depth)
|
||||
val fromMapping = getFromMapping(host)
|
||||
addLogSplit(logger, host, "end custom mapping resolve", depth)
|
||||
if (fromMapping != null) {
|
||||
dumpLog(logger, fromMapping)
|
||||
return fromMapping
|
||||
}
|
||||
if (useResolver) {
|
||||
// Load from /etc/hosts, since Dnsjava doesn't support hosts entry lookup
|
||||
addLogSplit(logger, host, "start /etc/hosts resolve", depth)
|
||||
val fromSystemHosts = fromSystemHosts(host)
|
||||
addLogSplit(logger, host, "end /etc/hosts resolve", depth)
|
||||
if (fromSystemHosts != null) {
|
||||
dumpLog(logger, fromSystemHosts)
|
||||
return fromSystemHosts
|
||||
}
|
||||
|
||||
// Use DNS resolver
|
||||
addLogSplit(logger, host, "start resolver resolve", depth)
|
||||
val fromResolver = fromResolver(originalHost, host)
|
||||
addLogSplit(logger, host, "end resolver resolve", depth)
|
||||
if (fromResolver != null) {
|
||||
dumpLog(logger, fromResolver)
|
||||
return fromResolver
|
||||
}
|
||||
}
|
||||
addLogSplit(logger, host, "start system default resolve", depth)
|
||||
val fromDefault = Arrays.asList(*InetAddress.getAllByName(host))
|
||||
addLogSplit(logger, host, "end system default resolve", depth)
|
||||
dumpLog(logger, fromDefault)
|
||||
return fromDefault
|
||||
}
|
||||
|
||||
private fun dumpLog(logger: TimingLogger, addresses: List<InetAddress>) {
|
||||
if (BuildConfig.DEBUG) return
|
||||
Log.v(RESOLVER_LOGTAG, "Resolved " + addresses)
|
||||
logger.dumpToLog()
|
||||
}
|
||||
|
||||
|
||||
private fun addLogSplit(logger: TimingLogger, host: String, message: String, depth: Int) {
|
||||
if (BuildConfig.DEBUG) return
|
||||
val sb = StringBuilder()
|
||||
for (i in 0..depth - 1) {
|
||||
sb.append(">")
|
||||
}
|
||||
sb.append(" ")
|
||||
sb.append(host)
|
||||
sb.append(": ")
|
||||
sb.append(message)
|
||||
logger.addSplit(sb.toString())
|
||||
}
|
||||
|
||||
private fun fromSystemHosts(host: String): List<InetAddress>? {
|
||||
try {
|
||||
return systemHosts.resolve(host)
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun fromResolver(originalHost: String, host: String): List<InetAddress>? {
|
||||
val resolver = this.getResolver()
|
||||
val records = lookupHostName(resolver, host, true)
|
||||
val addrs = ArrayList<InetAddress>(records.size)
|
||||
for (record in records) {
|
||||
addrs.add(addrFromRecord(originalHost, record))
|
||||
}
|
||||
if (addrs.isEmpty()) return null
|
||||
return addrs
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
private fun getFromMapping(host: String): List<InetAddress>? {
|
||||
return getFromMappingInternal(host, host, false)
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
private fun getFromMappingInternal(host: String, origHost: String, checkRecursive: Boolean): List<InetAddress>? {
|
||||
if (checkRecursive && hostMatches(host, origHost)) {
|
||||
// Recursive resolution, stop this call
|
||||
return null
|
||||
}
|
||||
for ((key, value1) in hostMapping.all) {
|
||||
if (hostMatches(host, key)) {
|
||||
val value = value1 as String
|
||||
val resolved = getResolvedIPAddress(origHost, value) ?: // Maybe another hostname
|
||||
return getFromMappingInternal(value, origHost, true)
|
||||
return listOf(resolved)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getResolver(): Resolver {
|
||||
return this.resolver ?: run {
|
||||
val tcp = preferences.getBoolean(KEY_TCP_DNS_QUERY, false)
|
||||
val resolvers = preferences.getString(KEY_DNS_SERVER, null)?.split(';', ',', ' ')?.mapNotNull {
|
||||
val segs = it.split("#", limit = 2)
|
||||
if (segs.isEmpty()) return@mapNotNull null
|
||||
if (!isValidIpAddress(segs[0])) return@mapNotNull null
|
||||
return@mapNotNull SimpleResolver(segs[0]).apply {
|
||||
if (segs.size == 2) {
|
||||
val port = segs[1].toInt(-1)
|
||||
if (port in 0..65535) {
|
||||
setPort(port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val resolver: Resolver
|
||||
if (resolvers != null && resolvers.isNotEmpty()) {
|
||||
resolver = ExtendedResolver(resolvers.toTypedArray())
|
||||
} else {
|
||||
resolver = SimpleResolver()
|
||||
}
|
||||
resolver.setTCP(tcp)
|
||||
this.resolver = resolver
|
||||
return@run resolver
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
private fun fromAddressString(host: String, address: String): List<InetAddress>? {
|
||||
val resolved = getResolvedIPAddress(host, address) ?: return null
|
||||
return listOf(resolved)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val RESOLVER_LOGTAG = "TwidereDns"
|
||||
|
||||
|
||||
private fun hostMatches(host: String?, rule: String?): Boolean {
|
||||
if (rule == null || host == null) return false
|
||||
if (rule.startsWith(".")) return StringUtils.endsWithIgnoreCase(host, rule)
|
||||
return host.equals(rule, ignoreCase = true)
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
fun getResolvedIPAddress(host: String,
|
||||
address: String): InetAddress? {
|
||||
var bytes = Address.toByteArray(address, Address.IPv4)
|
||||
if (bytes != null)
|
||||
return InetAddress.getByAddress(host, bytes)
|
||||
bytes = Address.toByteArray(address, Address.IPv6)
|
||||
if (bytes != null)
|
||||
return InetAddress.getByAddress(host, bytes)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getInetAddressType(address: String): Int {
|
||||
var bytes = Address.toByteArray(address, Address.IPv4)
|
||||
if (bytes != null)
|
||||
return Address.IPv4
|
||||
bytes = Address.toByteArray(address, Address.IPv6)
|
||||
if (bytes != null)
|
||||
return Address.IPv6
|
||||
return 0
|
||||
}
|
||||
|
||||
fun isValidIpAddress(address: String): Boolean {
|
||||
return getInetAddressType(address) != 0
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
private fun lookupHostName(resolver: Resolver, name: String, all: Boolean): Array<Record> {
|
||||
try {
|
||||
val lookup = newLookup(resolver, name, Type.A)
|
||||
val a = lookup.run()
|
||||
if (a == null) {
|
||||
if (lookup.result == Lookup.TYPE_NOT_FOUND) {
|
||||
val aaaa = newLookup(resolver, name, Type.AAAA).run()
|
||||
if (aaaa != null)
|
||||
return aaaa
|
||||
}
|
||||
throw UnknownHostException("unknown host")
|
||||
}
|
||||
if (!all)
|
||||
return a
|
||||
val aaaa = newLookup(resolver, name, Type.AAAA).run() ?: return a
|
||||
return a + aaaa
|
||||
} catch (e: TextParseException) {
|
||||
throw UnknownHostException("invalid name")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(TextParseException::class)
|
||||
private fun newLookup(resolver: Resolver, name: String, type: Int): Lookup {
|
||||
val lookup = Lookup(name, type)
|
||||
lookup.setResolver(resolver)
|
||||
return lookup
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
private fun addrFromRecord(name: String, r: Record): InetAddress {
|
||||
val addr: InetAddress
|
||||
if (r is ARecord) {
|
||||
addr = r.address
|
||||
} else {
|
||||
addr = (r as AAAARecord).address
|
||||
}
|
||||
return InetAddress.getByAddress(name, addr.address)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user