diff --git a/app/build.gradle b/app/build.gradle index ce9c12c..4dfbb59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 23 - buildToolsVersion '23.0.2' + buildToolsVersion '23.0.3' defaultConfig { - minSdkVersion 16 + minSdkVersion 14 targetSdkVersion 23 - versionCode 7 - versionName "1.2.1" + versionCode 8 + versionName "1.3.0" } buildTypes { @@ -27,22 +27,13 @@ android { dataBinding { enabled true } - - defaultConfig { - // Stops the Gradle’s automatic rasterization of vectors - generatedDensities = [] - } - // Flag that tells aapt to keep the attribute ids - aaptOptions { - additionalParameters "--no-version-vectors" - } } dependencies { //Android Support Libraries Version - def supportLibrary = '23.2.0' + def supportLibrary = '23.3.0' - compile 'com.github.andriydruk:rxdnssd:0.6.3' + compile 'com.github.andriydruk:rxdnssd:0.7.0' compile "com.android.support:support-v4:$supportLibrary" compile "com.android.support:appcompat-v7:$supportLibrary" compile "com.android.support:design:$supportLibrary" @@ -50,5 +41,5 @@ dependencies { compile "com.android.support:cardview-v7:$supportLibrary" compile "com.android.support:support-annotations:$supportLibrary" compile 'io.reactivex:rxandroid:1.1.0' - compile 'io.reactivex:rxjava:1.1.0' + compile 'io.reactivex:rxjava:1.1.3' } diff --git a/app/src/main/java/com/druk/bonjour/browser/BonjourApplication.java b/app/src/main/java/com/druk/bonjour/browser/BonjourApplication.java index b2016d5..c0938a9 100644 --- a/app/src/main/java/com/druk/bonjour/browser/BonjourApplication.java +++ b/app/src/main/java/com/druk/bonjour/browser/BonjourApplication.java @@ -16,13 +16,17 @@ package com.druk.bonjour.browser; import com.github.druk.rxdnssd.RxDnssd; +import com.github.druk.rxdnssd.RxDnssdBindable; +import com.github.druk.rxdnssd.RxDnssdEmbedded; import android.app.Application; import android.content.Context; +import android.os.Build; import android.os.StrictMode; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import android.widget.Toast; import java.io.BufferedReader; import java.io.IOException; @@ -34,6 +38,7 @@ public class BonjourApplication extends Application { private static final String TAG = "BonjourApplication"; private TreeMap mServiceNamesTree; + private RxDnssd mRxDnssd; @Override public void onCreate() { @@ -46,23 +51,37 @@ public void onCreate() { .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() - .penaltyDeath() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() - .penaltyDeath() .build()); } + mRxDnssd = createDnssd(); + } - RxDnssd.init(this); + public static RxDnssd getRxDnssd(@NonNull Context context){ + return ((BonjourApplication)context.getApplicationContext()).mRxDnssd; } public static String getRegTypeDescription(@NonNull Context context, String regType) { return ((BonjourApplication) context.getApplicationContext()).getRegTypeDescription(regType); } + private RxDnssd createDnssd(){ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){ + Log.i(TAG, "Using embedded version of dns sd because of API < 16"); + return new RxDnssdEmbedded(); + } + if (Build.VERSION.RELEASE.contains("4.4.2") && Build.MANUFACTURER.toLowerCase().contains("samsung")){ + Log.i(TAG, "Using embedded version of dns sd because of Samsung 4.4.2"); + return new RxDnssdEmbedded(); + } + Log.i(TAG, "Using systems dns sd daemon"); + return new RxDnssdBindable(this); + } + private String getRegTypeDescription(String regType) { if (mServiceNamesTree == null){ mServiceNamesTree = new TreeMap<>(); diff --git a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/RegTypeBrowserFragment.java b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/RegTypeBrowserFragment.java index 80f53fb..b8bf70d 100644 --- a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/RegTypeBrowserFragment.java +++ b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/RegTypeBrowserFragment.java @@ -19,7 +19,6 @@ import com.druk.bonjour.browser.Config; import com.druk.bonjour.browser.ui.adapter.ServiceAdapter; import com.github.druk.rxdnssd.BonjourService; -import com.github.druk.rxdnssd.RxDnssd; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -31,6 +30,7 @@ import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; +import rx.schedulers.Schedulers; import static com.druk.bonjour.browser.Config.EMPTY_DOMAIN; import static com.druk.bonjour.browser.Config.TCP_REG_TYPE_SUFFIX; @@ -70,7 +70,8 @@ public void onBindViewHolder(ViewHolder viewHolder, int i) { @Override protected void startDiscovery() { - mSubscription = RxDnssd.browse(Config.SERVICES_DOMAIN, "local.") + mSubscription = mRxDnssd.browse(Config.SERVICES_DOMAIN, "local.") + .subscribeOn(Schedulers.io()) .subscribe(reqTypeAction, errorAction); } @@ -86,7 +87,6 @@ protected void stopDiscovery() { private final Action1 reqTypeAction = service -> { if ((service.getFlags() & BonjourService.LOST) == BonjourService.LOST){ - Log.d("TAG", "Lose reg type: " + service); //Ignore this call return; } @@ -96,17 +96,22 @@ protected void stopDiscovery() { if (TCP_REG_TYPE_SUFFIX.equals(protocolSuffix) || UDP_REG_TYPE_SUFFIX.equals(protocolSuffix)) { String key = service.getServiceName() + "." + protocolSuffix; if (!mBrowsers.containsKey(key)) { - mBrowsers.put(key, RxDnssd.browse(key, serviceDomain) + mBrowsers.put(key, mRxDnssd.browse(key, serviceDomain) + .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(RegTypeBrowserFragment.this.servicesAction, RegTypeBrowserFragment.this.errorAction)); } mServices.put(createKey(service.getDomain(), service.getRegType(), service.getServiceName()), new BonjourDomain(service)); } else { + Log.e("TAG", "Unknown service protocol " + protocolSuffix); //Just ignore service with different protocol suffixes } }; - protected final Action1 errorAction = throwable -> Log.e("DNSSD", "Error: ", throwable); + protected final Action1 errorAction = throwable -> { + Log.e("DNSSD", "Error: ", throwable); + showError(throwable); + }; private final Action1 servicesAction = service -> { String[] regTypeParts = service.getRegType().split(Config.REG_TYPE_SEPARATOR); @@ -121,10 +126,14 @@ protected void stopDiscovery() { } else { domain.serviceCount++; } + final int itemsCount = mAdapter.getItemCount(); mAdapter.clear(); Observable.from(mServices.values()) .filter(bonjourDomain -> bonjourDomain.serviceCount > 0) - .subscribe(mAdapter::add, throwable -> {/* empty */}, mAdapter::notifyDataSetChanged); + .subscribe(mAdapter::add, throwable -> {/* empty */}, () -> { + showList(itemsCount); + mAdapter.notifyDataSetChanged(); + }); } else { Log.w(TAG, "Service from unknown service type " + key); } diff --git a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceBrowserFragment.java b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceBrowserFragment.java index c1f40c1..6434ad2 100644 --- a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceBrowserFragment.java +++ b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceBrowserFragment.java @@ -15,11 +15,14 @@ */ package com.druk.bonjour.browser.ui.fragment; +import com.druk.bonjour.browser.BonjourApplication; import com.druk.bonjour.browser.R; import com.druk.bonjour.browser.ui.adapter.ServiceAdapter; import com.github.druk.rxdnssd.BonjourService; import com.github.druk.rxdnssd.RxDnssd; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; @@ -30,9 +33,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ProgressBar; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; public class ServiceBrowserFragment extends Fragment { @@ -45,6 +53,9 @@ public class ServiceBrowserFragment extends Fragment { protected String mReqType; protected String mDomain; protected RecyclerView mRecyclerView; + protected ProgressBar mProgressView; + protected LinearLayout mErrorView; + protected RxDnssd mRxDnssd; protected View.OnClickListener mListener = new View.OnClickListener() { @Override @@ -78,6 +89,8 @@ public void onAttach(Context context) { if (!(context instanceof ServiceListener)) { throw new IllegalArgumentException("Fragment context should implement ServiceListener interface"); } + + mRxDnssd = BonjourApplication.getRxDnssd(context); } @Override @@ -110,14 +123,17 @@ else if (service.getInet6Address() != null) { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mRecyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_service_browser, container, false); + FrameLayout rootView = (FrameLayout) inflater.inflate(R.layout.fragment_service_browser, container, false); + mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view); + mProgressView = (ProgressBar) rootView.findViewById(R.id.progress); + mErrorView = (LinearLayout) rootView.findViewById(R.id.error_container); mRecyclerView.setLayoutManager(new LinearLayoutManager(mRecyclerView.getContext())); mRecyclerView.setHasFixedSize(true); mRecyclerView.setAdapter(mAdapter); if (savedInstanceState != null) { mAdapter.setSelectedItemId(savedInstanceState.getLong(KEY_SELECTED_POSITION, -1L)); } - return mRecyclerView; + return rootView; } @Override @@ -139,22 +155,75 @@ public void onSaveInstanceState(Bundle outState) { } protected void startDiscovery() { - mSubscription = RxDnssd.browse(mReqType, mDomain) - .compose(RxDnssd.resolve()) - .compose(RxDnssd.queryRecords()) + mSubscription = mRxDnssd.browse(mReqType, mDomain) + .compose(mRxDnssd.resolve()) + .compose(mRxDnssd.queryRecords()) + .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bonjourService -> { + int itemsCount = mAdapter.getItemCount(); if ((bonjourService.getFlags() & BonjourService.LOST) != BonjourService.LOST) { mAdapter.add(bonjourService); } else { mAdapter.remove(bonjourService); } + showList(itemsCount); mAdapter.notifyDataSetChanged(); }, throwable -> { Log.e("DNSSD", "Error: ", throwable); + showError(throwable); }); } + protected boolean showList(int itemsBefore){ + if (itemsBefore > 0 && mAdapter.getItemCount() == 0) { + mRecyclerView.animate().alpha(0.0f).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRecyclerView.setVisibility(View.GONE); + } + }).start(); + mProgressView.setAlpha(0.0f); + mProgressView.setVisibility(View.VISIBLE); + mProgressView.animate().alpha(1.0f).setInterpolator(new AccelerateDecelerateInterpolator()).start(); + return true; + } + if (itemsBefore == 0 && mAdapter.getItemCount() > 0) { + mProgressView.animate().alpha(0.0f).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mProgressView.setVisibility(View.GONE); + } + }).start(); + mRecyclerView.setAlpha(0.0f); + mRecyclerView.setVisibility(View.VISIBLE); + mRecyclerView.animate().alpha(1.0f).setInterpolator(new AccelerateDecelerateInterpolator()).start(); + return false; + } + return mAdapter.getItemCount() > 0; + } + + protected void showError(final Throwable e){ + getActivity().runOnUiThread(() -> { + mRecyclerView.animate().alpha(0.0f).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRecyclerView.setVisibility(View.GONE); + } + }).start(); + mProgressView.animate().alpha(0.0f).setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mProgressView.setVisibility(View.GONE); + } + }).start(); + mErrorView.setAlpha(0.0f); + mErrorView.setVisibility(View.VISIBLE); + mErrorView.animate().alpha(1.0f).setInterpolator(new AccelerateDecelerateInterpolator()).start(); + mErrorView.findViewById(R.id.send_report).setOnClickListener(v -> Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e)); + }); + } + protected void stopDiscovery() { if (mSubscription != null) { mSubscription.unsubscribe(); diff --git a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceDetailFragment.java b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceDetailFragment.java index 40eee1a..03473a5 100644 --- a/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceDetailFragment.java +++ b/app/src/main/java/com/druk/bonjour/browser/ui/fragment/ServiceDetailFragment.java @@ -1,5 +1,6 @@ package com.druk.bonjour.browser.ui.fragment; +import com.druk.bonjour.browser.BonjourApplication; import com.druk.bonjour.browser.R; import com.druk.bonjour.browser.ui.adapter.TxtRecordsAdapter; import com.github.druk.rxdnssd.BonjourService; @@ -21,8 +22,10 @@ import java.util.Map; import rx.Observable; +import rx.Scheduler; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; public class ServiceDetailFragment extends Fragment implements View.OnClickListener { @@ -63,7 +66,7 @@ public void onCreate(final Bundle savedInstanceState) { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mRecyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_service_browser, container, false); + mRecyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_service_detail, container, false); mRecyclerView.setLayoutManager(new LinearLayoutManager(mRecyclerView.getContext())); mRecyclerView.setHasFixedSize(true); mRecyclerView.setAdapter(mAdapter); @@ -103,10 +106,12 @@ private void updateUI(BonjourService service, boolean withSnakeBar) { @Override public void onClick(View v) { + RxDnssd mRxDnssd = BonjourApplication.getRxDnssd(getContext()); v.animate().rotationBy(180).start(); mResolveSubscription = Observable.just(mService) - .compose(RxDnssd.resolve()) - .compose(RxDnssd.queryRecords()) + .compose(mRxDnssd.resolve()) + .compose(mRxDnssd.queryRecords()) + .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bonjourService -> { if ((bonjourService.getFlags() & BonjourService.LOST) == BonjourService.LOST) { diff --git a/app/src/main/res/drawable/ic_report_problem.xml b/app/src/main/res/drawable/ic_report_problem.xml new file mode 100644 index 0000000..9fb5490 --- /dev/null +++ b/app/src/main/res/drawable/ic_report_problem.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/fragment_service_browser.xml b/app/src/main/res/layout/fragment_service_browser.xml index 456f357..f1fc670 100644 --- a/app/src/main/res/layout/fragment_service_browser.xml +++ b/app/src/main/res/layout/fragment_service_browser.xml @@ -14,7 +14,56 @@ v See the License for the specific language governing permissions and ~ limitations under the License. --> - \ No newline at end of file + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_service_detail.xml b/app/src/main/res/layout/fragment_service_detail.xml new file mode 100644 index 0000000..caec470 --- /dev/null +++ b/app/src/main/res/layout/fragment_service_detail.xml @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 36ae95c..d07df44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,5 +37,7 @@ local. No selected service + Error occurred\nPlease send report to developer + Send report diff --git a/build.gradle b/build.gradle index 60a5846..9cf8a03 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - classpath 'me.tatarka:gradle-retrolambda:3.2.4' + classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'me.tatarka:gradle-retrolambda:3.2.5' } } diff --git a/circle.yml b/circle.yml index d93ce4f..4255ec6 100644 --- a/circle.yml +++ b/circle.yml @@ -16,7 +16,7 @@ dependencies: override: - echo y | android update sdk --no-ui --all --filter "tools" - echo y | android update sdk --no-ui --all --filter "platform-tools" - - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.2" + - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.3" - echo y | android update sdk --no-ui --all --filter "android-23" - echo y | android update sdk --no-ui --all --filter "extra-android-m2repository" - ANDROID_HOME=/usr/local/android-sdk-linux ./gradlew dependencies diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df7d1f3..4a391a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip